From 5c20bddbd00ef12449dba3f3a0c0390b4ee530e9 Mon Sep 17 00:00:00 2001 From: Yonas Berhe Date: Wed, 25 Sep 2024 13:42:29 -0700 Subject: [PATCH] github auth test --- ...ig.jenkins.ts => cypress.config.jenkins.ts | 36 +- cypress.config.ts | 11 +- cypress/e2e/po/edit/auth/github.po.ts | 80 +++++ cypress/e2e/po/pages/login-page.po.ts | 14 +- .../pages/users-and-auth/authProvider.po.ts | 9 + cypress/e2e/po/side-bars/user-menu.po.ts | 6 +- cypress/e2e/po/third-party-apps/github.po.ts | 42 +++ .../pages/users-and-auth/email-test.spec.ts | 25 ++ .../pages/users-and-auth/github-auth.spec.ts | 314 ++++++++++++++++++ .../pages/users-and-auth/github-auth2.spec.ts | 146 ++++++++ cypress/globals.d.ts | 6 +- cypress/jenkins/Jenkinsfile | 11 +- cypress/jenkins/cypress.sh | 2 +- cypress/jenkins/init.sh | 26 +- cypress/support/commands/commands.ts | 50 +++ .../support/commands/rancher-api-commands.ts | 270 +++++++++++++-- package.json | 1 + shell/components/auth/AllowedPrincipals.vue | 1 + shell/edit/auth/github.vue | 2 + yarn.lock | 130 +++++++- 20 files changed, 1124 insertions(+), 58 deletions(-) rename cypress/jenkins/cypress.config.jenkins.ts => cypress.config.jenkins.ts (76%) create mode 100644 cypress/e2e/po/edit/auth/github.po.ts create mode 100644 cypress/e2e/po/third-party-apps/github.po.ts create mode 100644 cypress/e2e/tests/pages/users-and-auth/email-test.spec.ts create mode 100644 cypress/e2e/tests/pages/users-and-auth/github-auth.spec.ts create mode 100644 cypress/e2e/tests/pages/users-and-auth/github-auth2.spec.ts diff --git a/cypress/jenkins/cypress.config.jenkins.ts b/cypress.config.jenkins.ts similarity index 76% rename from cypress/jenkins/cypress.config.jenkins.ts rename to cypress.config.jenkins.ts index 444b035c69f..f8e512c728f 100644 --- a/cypress/jenkins/cypress.config.jenkins.ts +++ b/cypress.config.jenkins.ts @@ -9,14 +9,14 @@ require('dotenv').config(); */ const testDirs = [ - 'cypress/e2e/tests/priority/**/*.spec.ts', - 'cypress/e2e/tests/components/**/*.spec.ts', - 'cypress/e2e/tests/setup/**/*.spec.ts', - 'cypress/e2e/tests/pages/**/*.spec.ts', - 'cypress/e2e/tests/navigation/**/*.spec.ts', - 'cypress/e2e/tests/global-ui/**/*.spec.ts', - 'cypress/e2e/tests/features/**/*.spec.ts', - 'cypress/e2e/tests/extensions/**/*.spec.ts' + // 'cypress/e2e/tests/priority/**/*.spec.ts', + // 'cypress/e2e/tests/components/**/*.spec.ts', + // 'cypress/e2e/tests/setup/**/*.spec.ts', + 'cypress/e2e/tests/pages/users-and-auth/*.spec.ts', + // 'cypress/e2e/tests/navigation/**/*.spec.ts', + // 'cypress/e2e/tests/global-ui/**/*.spec.ts', + // 'cypress/e2e/tests/features/**/*.spec.ts', + // 'cypress/e2e/tests/extensions/**/*.spec.ts' ]; const skipSetup = process.env.TEST_SKIP?.includes('setup'); const baseUrl = (process.env.TEST_BASE_URL || 'https://localhost:8005').replace(/\/$/, ''); @@ -67,7 +67,7 @@ export default defineConfig({ trashAssetsBeforeRuns: true, chromeWebSecurity: false, retries: { - runMode: 2, + runMode: 0, openMode: 0 }, env: { @@ -86,7 +86,16 @@ export default defineConfig({ azureClientId: process.env.AZURE_CLIENT_ID, azureClientSecret: process.env.AZURE_CLIENT_SECRET, customNodeIp: process.env.CUSTOM_NODE_IP, - customNodeKey: process.env.CUSTOM_NODE_KEY + customNodeKey: process.env.CUSTOM_NODE_KEY, + githubUser1: process.env.GITHUB_USER1, + githubPassword1: process.env.GITHUB_PASSWORD1, + githubUser2: process.env.GITHUB_USER2, + githubPassword2: process.env.GITHUB_PASSWORD2, + googleClientId: process.env.GOOGLE_CLIENT_ID, + googleClientSecret: process.env.GOOGLE_CLIENT_SECRET, + googleRefreshToken: process.env.GOOGLE_REFRESH_TOKEN, + githubClientId: process.env.GITHUB_CLIENT_ID, + githubClientSecret: process.env.GITHUB_CLIENT_SECRET }, // Jenkins reporters configuration jUnit and HTML reporter: 'cypress-multi-reporters', @@ -106,9 +115,10 @@ export default defineConfig({ require('@cypress/grep/src/plugin')(config); on('task', { removeDirectory }); }, - fixturesFolder: 'cypress/e2e/blueprints', - experimentalSessionAndOrigin: true, - specPattern: testDirs, + fixturesFolder: 'cypress/e2e/blueprints', + experimentalSessionAndOrigin: true, + experimentalModifyObstructiveThirdPartyCode: true, + specPattern: testDirs, baseUrl }, video: false, diff --git a/cypress.config.ts b/cypress.config.ts index dcf8600021b..55f0f4d70b9 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -94,7 +94,16 @@ export default defineConfig({ azureClientId: process.env.AZURE_CLIENT_ID, azureClientSecret: process.env.AZURE_CLIENT_SECRET, customNodeIp: process.env.CUSTOM_NODE_IP, - customNodeKey: process.env.CUSTOM_NODE_KEY + customNodeKey: process.env.CUSTOM_NODE_KEY, + githubUser1: process.env.GITHUB_USER1, + githubPassword1: process.env.GITHUB_PASSWORD1, + githubUser2: process.env.GITHUB_USER2, + githubPassword2: process.env.GITHUB_PASSWORD2, + googleClientId: process.env.GOOGLE_CLIENT_ID, + googleClientSecret: process.env.GOOGLE_CLIENT_SECRET, + googleRefreshToken: process.env.GOOGLE_REFRESH_TOKEN, + githubClientId: process.env.GITHUB_CLIENT_ID, + githubClientSecret: process.env.GITHUB_CLIENT_SECRET }, e2e: { fixturesFolder: 'cypress/e2e/blueprints', diff --git a/cypress/e2e/po/edit/auth/github.po.ts b/cypress/e2e/po/edit/auth/github.po.ts new file mode 100644 index 00000000000..c7a74daabb4 --- /dev/null +++ b/cypress/e2e/po/edit/auth/github.po.ts @@ -0,0 +1,80 @@ +import PagePo from '@/cypress/e2e/po/pages/page.po'; +import RadioGroupInputPo from '@/cypress/e2e/po/components/radio-group-input.po'; +import LabeledInputPo from '@/cypress/e2e/po/components/labeled-input.po'; +import AsyncButtonPo from '@/cypress/e2e/po/components/async-button.po'; +import ProductNavPo from '@/cypress/e2e/po/side-bars/product-side-nav.po'; +import BurgerMenuPo from '@/cypress/e2e/po/side-bars/burger-side-menu.po'; +import BannersPo from '@/cypress/e2e/po/components/banners.po'; +import LabeledSelectPo from '@/cypress/e2e/po/components/labeled-select.po'; + +export default class GitHubPo extends PagePo { + private static createPath(clusterId: string) { + return `/c/${ clusterId }/auth/config/github?mode=edit`; + } + + static goTo(clusterId: string): Cypress.Chainable { + return super.goTo(GitHubPo.createPath(clusterId)); + } + + constructor(clusterId: string) { + super(GitHubPo.createPath(clusterId)); + } + + static navTo() { + const sideNav = new ProductNavPo(); + + BurgerMenuPo.burgerMenuNavToMenubyLabel('Users & Authentication'); + sideNav.navToSideMenuEntryByLabel('Auth Provider'); + } + + githubAppLink() { + return this.self().get('ul.step-list [href="/settings/developers"]').then((el) => { + expect(el).to.have.attr('target'); + }) + .invoke('removeAttr', 'target'); + } + + authConfigRadioBtn(): RadioGroupInputPo { + return new RadioGroupInputPo('[data-testid="access-mode-options"]'); + } + + selectLoginCofigOption(index: number): Cypress.Chainable { + return this.authConfigRadioBtn().set(index); + } + + bannerContent(element:string) { + return new BannersPo('[data-testid="banner-content"]', this.self()).bannerElement(element); + } + + clientId() { + return new LabeledInputPo('[data-testid="input-github-clientId"]'); + } + + clientSecret() { + return new LabeledInputPo('[data-testid="input-github-clientSecret"]'); + } + + disable() { + return this.bannerContent('button').contains('Disable').click(); + } + + usersAndGroupsArrayListItem(index: number) { + return this.self().find(`[data-testid="array-list-box${ index }"]`); + } + + addMemberSearch() { + return new LabeledSelectPo('.labeled-select.select-principal', this.self()); + } + + cancelButton(): AsyncButtonPo { + return new AsyncButtonPo('[data-testid="form-cancel"]', this.self()); + } + + saveButton(): AsyncButtonPo { + return new AsyncButtonPo('[data-testid="form-save"]', this.self()); + } + + save() { + return new AsyncButtonPo('[data-testid="form-save"]').click(); + } +} diff --git a/cypress/e2e/po/pages/login-page.po.ts b/cypress/e2e/po/pages/login-page.po.ts index b0d6644095e..9cf0dd4f5c4 100644 --- a/cypress/e2e/po/pages/login-page.po.ts +++ b/cypress/e2e/po/pages/login-page.po.ts @@ -30,18 +30,12 @@ export class LoginPagePo extends PagePo { } switchToLocal(): Cypress.Chainable { - const useLocal = this.useLocal(); - - // TODO: We should have control over this instead of using a condition, as we want deterministic tests - return useLocal ? useLocal.click() : cy; + return cy.getId('login-useLocal'); } - useLocal() { - return this.self().then(($page) => { - const elements = $page.find('[data-testid="login-useLocal"]'); - - return elements?.[0]; - }); + useAuthProvider(text: string) { + return this.self().find('button.btn').contains(text); + // return cy.getId('login-userAuthProvider').contains(text); } submitButton(): AsyncButtonPo { diff --git a/cypress/e2e/po/pages/users-and-auth/authProvider.po.ts b/cypress/e2e/po/pages/users-and-auth/authProvider.po.ts index 0e8f1636194..5b15e2f9f5d 100644 --- a/cypress/e2e/po/pages/users-and-auth/authProvider.po.ts +++ b/cypress/e2e/po/pages/users-and-auth/authProvider.po.ts @@ -1,6 +1,7 @@ import PagePo from '@/cypress/e2e/po/pages/page.po'; import ProductNavPo from '@/cypress/e2e/po/side-bars/product-side-nav.po'; import BurgerMenuPo from '@/cypress/e2e/po/side-bars/burger-side-menu.po'; +import GenericPrompt from '@/cypress/e2e/po/prompts/genericPrompt.po'; export default class AuthProviderPo extends PagePo { private static createPath(clusterId: string, id?: string ) { @@ -29,4 +30,12 @@ export default class AuthProviderPo extends PagePo { selectAzureAd() { return this.self().find('[data-testid="select-icon-grid-AzureAD"]').click(); } + + selectGit() { + return this.self().find('[data-testid="select-icon-grid-GitHub"]').click(); + } + + disableAuthProviderModal(): GenericPrompt { + return new GenericPrompt('.prompt-restore'); + } } diff --git a/cypress/e2e/po/side-bars/user-menu.po.ts b/cypress/e2e/po/side-bars/user-menu.po.ts index 5f0df55b77d..1ee66e9b284 100644 --- a/cypress/e2e/po/side-bars/user-menu.po.ts +++ b/cypress/e2e/po/side-bars/user-menu.po.ts @@ -27,6 +27,10 @@ export default class UserMenuPo extends ComponentPo { return this.self().getId(`user-menu-dropdown`); } + userImage() { + return this.self().getId('nav_header_showUserMenu'); + } + /** * Open the user menu * @@ -92,7 +96,7 @@ export default class UserMenuPo extends ComponentPo { * @returns */ getMenuItems(): Cypress.Chainable { - return this.userMenu().find('li').should('be.visible').and('have.length', 4); + return this.userMenu().find('li'); } /** diff --git a/cypress/e2e/po/third-party-apps/github.po.ts b/cypress/e2e/po/third-party-apps/github.po.ts new file mode 100644 index 00000000000..1b06ded87ab --- /dev/null +++ b/cypress/e2e/po/third-party-apps/github.po.ts @@ -0,0 +1,42 @@ +/* eslint-disable cypress/no-unnecessary-waiting */ +export default class GitHubThirdPartyPo { + usernameField(text: string) { + return cy.get('#login_field').should('be.visible').clear().type(text, { force: true, delay: 100 }); + } + + passwordField(text: string) { + return cy.get('#password').should('be.visible').clear().type(text, { force: true, delay: 100 }); + } + + signInButton() { + return cy.get('input').contains('Sign in'); + } + + appNameField(text: string) { + return cy.get('#oauth_application_name').type(text); + } + + oauthApplicationUrl(text: string) { + return cy.get('#oauth_application_url').type(text); + } + + oauthApplicationCallbackUrl(text: string) { + return cy.get('#oauth_application_callback_url').type(text); + } + + registerAppButton() { + return cy.get('button.btn').contains('Register application'); + } + + generateClientSecret() { + return cy.get('input.btn').contains('Generate a new client secret'); + } + + newOauthToken() { + return cy.get('#new-oauth-token'); + } + + authorizeButton(options: any) { + return cy.get('button[name="authorize"][value="1"]', options).should('be.enabled').click(); + } +} diff --git a/cypress/e2e/tests/pages/users-and-auth/email-test.spec.ts b/cypress/e2e/tests/pages/users-and-auth/email-test.spec.ts new file mode 100644 index 00000000000..56f00f4ddbe --- /dev/null +++ b/cypress/e2e/tests/pages/users-and-auth/email-test.spec.ts @@ -0,0 +1,25 @@ + +describe('Email Code Extraction', () => { + it('should extract code from the Gmail email body', () => { + const emailSearchQuery = 'subject:[GitHub] Please verify your device'; + + // Call the fetchGmailMessages custom command to get the email messages + cy.fetchGmailMessage(emailSearchQuery).then((resp: Cypress.Response) => { + const emailBody = resp.body.payload.body.data; + + // Decode the base64 content (Gmail API returns email body in base64url encoding) + const decodedBody = Cypress.Buffer.from(emailBody, 'base64').toString('utf-8'); + + // Regex to extract the 6-digit code + const match = decodedBody.match(/(\d{6})/); + + if (match) { + const code = match[1]; // Extracted code + + cy.log('Extracted Code:', code); + } else { + cy.log('No code found'); + } + }); + }); +}); diff --git a/cypress/e2e/tests/pages/users-and-auth/github-auth.spec.ts b/cypress/e2e/tests/pages/users-and-auth/github-auth.spec.ts new file mode 100644 index 00000000000..e95d165f681 --- /dev/null +++ b/cypress/e2e/tests/pages/users-and-auth/github-auth.spec.ts @@ -0,0 +1,314 @@ +import GitHubPo from '@/cypress/e2e/po/edit/auth/github.po'; +import AuthProviderPo from '@/cypress/e2e/po/pages/users-and-auth/authProvider.po'; +import { LoginPagePo } from '@/cypress/e2e/po/pages/login-page.po'; +import HomePagePo from '@/cypress/e2e/po/pages/home.po'; +import UserMenuPo from '@/cypress/e2e/po/side-bars/user-menu.po'; +import GitHubThirdPartyPo from '@/cypress/e2e/po/third-party-apps/github.po'; +import { MEDIUM_TIMEOUT_OPT } from '@/cypress/support/utils/timeouts'; + +const authProviderPo = new AuthProviderPo('local'); +const githubPo = new GitHubPo('local'); +const loginPage = new LoginPagePo(); +const homePage = new HomePagePo(); +const userMenu = new UserMenuPo(); +const gitHubThirdPartyPo = new GitHubThirdPartyPo(); + +let clientSecret: string; +let clientId: string; +let appName = ''; +let disableAuth = false; + +const userInfo = { + user1: { username: Cypress.env('githubUser1'), password: Cypress.env('githubPassword1') }, + user2: { username: Cypress.env('githubUser2'), password: Cypress.env('githubPassword2') } +}; + +function githubSignin(username: string, password: string) { + cy.origin('https://github.com', { args: { username, password } }, ({ username, password }) => { + cy.url().should('include', 'https://github.com/login'); + cy.get('#login_field').type(username, { delay: 100 }); + cy.get('#password').type(password, { delay: 100 }); + cy.get('input').contains('Sign in').click(); + // gitHubThirdPartyPo.usernameField(username); + // gitHubThirdPartyPo.passwordField(password); + // gitHubThirdPartyPo.signInButton().click(); + }); +} + +describe('GitHub', { tags: ['@adminUser', '@usersAndAuths', '@jenkins', '@debug'] }, () => { + before(() => { + cy.createE2EResourceName('github').then((name) => { + appName = name; + }); + }); + + it('Configure OAuth App in GitHub', () => { + cy.login(); + + authProviderPo.goTo(); + authProviderPo.waitForPage(); + authProviderPo.selectGit(); + githubPo.waitForPage(); + + githubPo.githubAppLink().click(); + + githubSignin(userInfo.user1.username, userInfo.user1.password); + + cy.url().then((currentUrl) => { + if (currentUrl.indexOf('https://github.com/sessions') !== -1) { + cy.log('User is on the sessions page'); + cy.wait(30000); // wait for email to reach the inbox + + const emailSearchQuery = 'subject:[GitHub] Please verify your device'; + + cy.fetchGmailMessage(emailSearchQuery).then((resp: Cypress.Response) => { + const emailBody = resp.body.payload.body.data; + const decodedBody = Cypress.Buffer.from(emailBody, 'base64').toString('utf-8'); + const match = decodedBody.match(/(\d{6})/); + + if (match) { + const code = match[1]; + + cy.log('Code:', code); + cy.get("input[type='text']").type(code); + } else { + throw new Error('Verification code not found in email body.'); + } + }); + } else { + cy.log('User is NOT on the sessions page'); + } + }); + + cy.url().should('include', 'github.com/settings/developers'); + cy.visit('https://github.com/settings/applications/new'); + cy.url().should('include', 'https://github.com/settings/applications/new'); + gitHubThirdPartyPo.appNameField(appName); + gitHubThirdPartyPo.oauthApplicationUrl(`${ Cypress.env('api') }`); + gitHubThirdPartyPo.oauthApplicationCallbackUrl(`${ Cypress.env('api') }`); + + cy.intercept('POST', 'https://github.com/settings/applications').as('createGitApp'); + gitHubThirdPartyPo.registerAppButton().click(); + cy.wait('@createGitApp'); + + cy.url().should('include', 'github.com/settings/applications'); + + cy.contains('div', 'Client ID').next('code').invoke('text').then((text) => { + clientId = text; + }); + + gitHubThirdPartyPo.generateClientSecret().click(); + gitHubThirdPartyPo.newOauthToken().invoke('text').then((text) => { + clientSecret = text; + }); + }); + + it('Can enabled GitHub authentication provider in Rancher', () => { + cy.login(); + + cy.intercept('POST', 'v3/githubConfigs/github?action=configureTest').as('configureTest'); + cy.intercept('PUT', 'v3/githubConfigs/github').as('saveConfig'); + cy.intercept('GET', '/login/oauth/authorize').as('authPopup'); + + authProviderPo.goTo(); + authProviderPo.waitForPage(); + authProviderPo.selectGit(); + githubPo.waitForPage(); + + githubPo.mastheadTitle().should('contain', 'GitHub'); + githubPo.mastheadTitle().should('contain', 'Inactive'); + githubPo.bannerContent('span').should('contain.text', 'The GitHub authentication provider is currently disabled.'); + + githubPo.saveButton().checkVisible(); + // githubPo.clientId().set(clientId); + // githubPo.clientSecret().set(clientSecret); + cy.get('input[type="text"]').type(clientId); + cy.get('input[type="password"]').type(clientSecret); + + githubPo.saveButton().expectToBeEnabled(); + + cy.window().then((win) => { + cy.spy(win, 'open').as('windowOpen'); + }); + + githubPo.save(); + + cy.wait('@configureTest'); + cy.wait(3000); + + // github auth popup - sign in + // cy.get('@windowOpen') + // .its('firstCall.returnValue.location.href') + // .should('contain', '/login'); + cy.get('@windowOpen') + .its('firstCall.returnValue.location.href') + .then((url) => { + cy.log('Sign in window URL:', url); + }); + cy.get('@windowOpen') + .its('firstCall.returnValue.document') + .then((newWindow) => { + cy.wrap(newWindow.body).find('#login_field').type(userInfo.user1.username, { delay: 100 }); + cy.wrap(newWindow.body).find('#password').type(userInfo.user1.password, { delay: 100 }); + cy.wrap(newWindow.body).find('input[type="submit"]').contains('Sign in').click(); + }); + + cy.wait(3000); + // cy.wait('@authPopup'); + // github auth popup - authorize app + + // cy.get('@windowOpen') + // .its('firstCall.returnValue.location.href') + // .should('contain', '/login/oauth/authorize'); + cy.get('@windowOpen') + .its('firstCall.returnValue.location.href') + .then((url) => { + cy.log('Authorize app window URL:', url); + }); + cy.get('@windowOpen') + .its('firstCall.returnValue.document') + .then((newWindow) => { + cy.wrap(newWindow.body) + .find('button[name="authorize"][value="1"]', MEDIUM_TIMEOUT_OPT) + .should('be.enabled') + .click(); + }); + + cy.wait('@saveConfig', MEDIUM_TIMEOUT_OPT).then(({ response }) => { + expect(response?.statusCode).to.eq(200); + disableAuth = true; + }); + + githubPo.mastheadTitle().should('contain', 'GitHub'); + githubPo.mastheadTitle().should('contain', 'Active'); + githubPo.bannerContent('div.text').should('contain.text', 'The GitHub authentication provider is currently enabled.'); + + // TODO + // verify server/client id values display + }); + + it('GitHub Auth Login/Logout flow', () => { + cy.login(); + + authProviderPo.goTo(); + githubPo.waitForPage(); + + githubPo.mastheadTitle().should('contain', 'GitHub'); + githubPo.mastheadTitle().should('contain', 'Active'); + githubPo.bannerContent('div.text').should('contain.text', 'The GitHub authentication provider is currently enabled.'); + + userMenu.clickMenuItem('Log Out'); + loginPage.waitForPage(); + + // check 'Log in with Github' and 'Use a local user' options available + loginPage.useAuthProvider('Log in with GitHub').isVisible(); + loginPage.switchToLocal().isVisible(); + + // 'Log in with Github' - user lands on github auth page + loginPage.useAuthProvider('Log in with GitHub').click(); + githubSignin(userInfo.user1.username, userInfo.user1.password); + homePage.waitForPage(); + + // check github avatar and username in user menu + userMenu.userImage().find('.user-image img').then((el) => { + expect(el).to.have.attr('src').to.include('https://avatars.githubusercontent.com'); + expect(el).to.have.class('avatar-round'); + }); + userMenu.open(); + userMenu.userMenu().find('li.user-info .user-name').should('contain.text', userInfo.user1.username); + + // logout - check success message on screen + userMenu.clickMenuItem('Log Out'); + loginPage.waitForPage(); + loginPage.loginPageMessage().should('contain.text', 'You\'ve been logged out of Rancher, however you may still be logged in to your single sign-on identity provider.'); + }); + + it('Can set restrictions and add restricted user', () => { + cy.login(undefined, undefined, false, false, false, 'GitHub'); + + githubSignin(userInfo.user1.username, userInfo.user1.password); + + homePage.waitForPage(); + + authProviderPo.goTo(); + githubPo.waitForPage(); + + githubPo.mastheadTitle().should('contain', 'GitHub'); + githubPo.mastheadTitle().should('contain', 'Active'); + githubPo.bannerContent('div.text').should('contain.text', 'The GitHub authentication provider is currently enabled.'); + + // set restriction + // githubPo.selectLoginCofigOption(2); + cy.get('span').contains('Restrict access to only the authorized users & groups').click(); + + // add github user + cy.intercept('PUT', '/v3/githubConfigs/github').as('addUser'); + githubPo.addMemberSearch().setOptionAndClick(userInfo.user2.username); + githubPo.save(); + cy.wait('@addUser').its('response.statusCode').should('eq', 200); + githubPo.usersAndGroupsArrayListItem(1).should('include.text', userInfo.user2.username); + + userMenu.clickMenuItem('Log Out'); + loginPage.waitForPage(); + }); + + it('Can signin as restricted user', () => { + cy.login(undefined, undefined, false, false, false, 'GitHub'); + + githubSignin(userInfo.user2.username, userInfo.user2.password); + + gitHubThirdPartyPo.authorizeButton(MEDIUM_TIMEOUT_OPT); + + // check github avatar and username in user menu + homePage.waitForPage(); + userMenu.userImage().find('.user-image img').then((el) => { + expect(el).to.have.attr('src').to.include('https://avatars.githubusercontent.com'); + expect(el).to.have.class('avatar-round'); + }); + userMenu.open(); + userMenu.userMenu().find('li.user-info .user-name').should('contain.text', userInfo.user2.username); + + userMenu.clickMenuItem('Log Out'); + loginPage.waitForPage(); + }); + + it('Can disable GitHub authentication provider in Rancher', () => { + cy.login(undefined, undefined, false, false, false, 'GitHub'); + + githubSignin(userInfo.user1.username, userInfo.user1.password); + + cy.intercept('POST', 'v3/githubConfigs/github?action=disable').as('disableAuth'); + cy.intercept('POST', 'v3/tokens?action=logout').as('authError'); + + homePage.waitForPage(); + authProviderPo.goTo(); + githubPo.waitForPage(); + + githubPo.mastheadTitle().should('contain', 'GitHub'); + githubPo.mastheadTitle().should('contain', 'Active'); + githubPo.bannerContent('div.text').should('contain.text', 'The GitHub authentication provider is currently enabled.'); + + githubPo.disable(); + authProviderPo.disableAuthProviderModal().submit('Disable'); + cy.wait('@disableAuth').then(({ response }) => { + expect(response?.statusCode).to.eq(200); + disableAuth = false; + }); + cy.wait('@authError').its('response.statusCode').should('eq', 401); + + githubPo.bannerContent('span').should('contain.text', 'Unauthorized 401: must authenticate'); + + // user should land on login page + loginPage.waitForPage(); + + // 'Log in with Github' option NOT available + loginPage.useAuthProvider('Log in with GitHub').should('not.exist'); + }); + + after('clean up', () => { + if (disableAuth) { + // ensure GitHub auth is disabled + cy.disableAuth('v3', 'githubConfigs', 'github'); + } + }); +}); diff --git a/cypress/e2e/tests/pages/users-and-auth/github-auth2.spec.ts b/cypress/e2e/tests/pages/users-and-auth/github-auth2.spec.ts new file mode 100644 index 00000000000..d46c172b795 --- /dev/null +++ b/cypress/e2e/tests/pages/users-and-auth/github-auth2.spec.ts @@ -0,0 +1,146 @@ +import GitHubPo from '@/cypress/e2e/po/edit/auth/github.po'; +import AuthProviderPo from '@/cypress/e2e/po/pages/users-and-auth/authProvider.po'; +import { LoginPagePo } from '@/cypress/e2e/po/pages/login-page.po'; +import HomePagePo from '@/cypress/e2e/po/pages/home.po'; +import UserMenuPo from '@/cypress/e2e/po/side-bars/user-menu.po'; +import GitHubThirdPartyPo from '@/cypress/e2e/po/third-party-apps/github.po'; + +const authProviderPo = new AuthProviderPo('local'); +const githubPo = new GitHubPo('local'); +const loginPage = new LoginPagePo(); +const homePage = new HomePagePo(); +const userMenu = new UserMenuPo(); +const gitHubThirdPartyPo = new GitHubThirdPartyPo(); + +let disableAuth = false; + +const userInfo = { + user1: { username: Cypress.env('githubUser1'), password: Cypress.env('githubPassword1') }, + user2: { username: Cypress.env('githubUser2'), password: Cypress.env('githubPassword2') } +}; + +function githubSignin(username: string, password: string) { + cy.origin('https://github.com', { args: { username, password } }, ({ username, password }) => { + cy.url().should('include', 'https://github.com/login'); + cy.get('#login_field').type(username); + cy.get('#password').type(password); + cy.get('input').contains('Sign in').click(); + // gitHubThirdPartyPo.usernameField(username); + // gitHubThirdPartyPo.passwordField(password); + // gitHubThirdPartyPo.signInButton().click(); + }); +} + +describe('GitHub Auth', { tags: ['@adminUser', '@usersAndAuths', '@jenkins'] }, () => { + it('Form Validation: Enable GitHub authentication provider with valid credentials', () => { + cy.login(); + authProviderPo.goTo(); + authProviderPo.waitForPage(); + authProviderPo.selectGit(); + githubPo.waitForPage(); + + githubPo.mastheadTitle().should('contain', 'GitHub'); + githubPo.mastheadTitle().should('contain', 'Inactive'); + githubPo.bannerContent('span').should('contain.text', 'The GitHub authentication provider is currently disabled.'); + + githubPo.saveButton().checkVisible(); + githubPo.saveButton().isDisabled(); + // githubPo.clientId().set(clientId); + // githubPo.clientSecret().set(clientSecret); + cy.get('input[type="text"]').type(Cypress.env('githubClientId')); + cy.get('input[type="password"]').type(Cypress.env('githubClientSecret')); + githubPo.saveButton().expectToBeEnabled(); + }); + + it('Can enabled GitHub authentication provider in Rancher', () => { + cy.login(); + cy.enableGithubAuth().then(() => { + disableAuth = true; + }); + + authProviderPo.goTo(); + authProviderPo.waitForPage(); + githubPo.mastheadTitle().should('contain', 'GitHub'); + githubPo.mastheadTitle().should('contain', 'Active'); + githubPo.bannerContent('div.text').should('contain.text', 'The GitHub authentication provider is currently enabled.'); + + // cy.wait(10000); + // TODO + // verify server/client id values display + }); + + // let code; + + it('GitHub Auth Login/Logout flow', () => { + cy.login(undefined, undefined, false, true, true); + + authProviderPo.goTo(); + githubPo.waitForPage(); + + githubPo.mastheadTitle().should('contain', 'GitHub'); + githubPo.mastheadTitle().should('contain', 'Active'); + githubPo.bannerContent('div.text').should('contain.text', 'The GitHub authentication provider is currently enabled.'); + + userMenu.clickMenuItem('Log Out'); + loginPage.waitForPage(); + + // check 'Log in with Github' and 'Use a local user' options available + loginPage.useAuthProvider('Log in with GitHub').isVisible(); + loginPage.switchToLocal().isVisible(); + + // 'Log in with Github' - user lands on github auth page + loginPage.useAuthProvider('Log in with GitHub').click(); + // login to rancher via api + // cy.loginViaGithubAuth(); + + githubSignin(userInfo.user1.username, userInfo.user1.password); + + cy.url().then((currentUrl) => { + if (currentUrl.indexOf('https://github.com/sessions') !== -1) { + cy.log('User is on the sessions page'); + cy.wait(30000); // wait for email to reach the inbox + + const emailSearchQuery = 'subject:[GitHub] Please verify your device'; + + cy.fetchGmailMessage(emailSearchQuery).then((resp: Cypress.Response) => { + const emailBody = resp.body.payload.body.data; + const decodedBody = Cypress.Buffer.from(emailBody, 'base64').toString('utf-8'); + const match = decodedBody.match(/(\d{6})/); + + if (match) { + const code = match[1]; + + cy.log('Code:', code); + cy.get("input[type='text']").type(code); + } else { + throw new Error('Verification code not found in email body.'); + } + }); + } else { + cy.log('User is NOT on the sessions page'); + } + }); + + homePage.waitForPage(); + + // check github avatar and username in user menu + userMenu.userImage().find('.user-image img').then((el) => { + expect(el).to.have.attr('src').to.include('https://avatars.githubusercontent.com'); + expect(el).to.have.class('avatar-round'); + }); + userMenu.open(); + userMenu.userMenu().find('li.user-info .user-name').should('contain.text', userInfo.user1.username); + + // logout - check success message on screen + userMenu.clickMenuItem('Log Out'); + loginPage.waitForPage(); + loginPage.loginPageMessage().should('contain.text', 'You\'ve been logged out of Rancher, however you may still be logged in to your single sign-on identity provider.'); + }); + + after('clean up', () => { + if (disableAuth) { + // ensure GitHub auth is disabled + cy.disableAuth('v3', 'githubConfigs', 'github'); + } + }); +}); diff --git a/cypress/globals.d.ts b/cypress/globals.d.ts index 8d5b99566de..7c2d76bba0a 100644 --- a/cypress/globals.d.ts +++ b/cypress/globals.d.ts @@ -68,7 +68,7 @@ declare global { state(state: any): any; - login(username?: string, password?: string, cacheSession?: boolean): Chainable; + login(username?: string, password?: string, cacheSession?: boolean, localAuth?: boolean, switchToLocalAuth?: boolean, authProvider?: string): Chainable; logout(): Chainable; byLabel(label: string): Chainable; getRootE2EResourceName(): Chainable; @@ -98,7 +98,9 @@ declare global { waitForRancherResources(prefix: 'v3' | 'v1', resourceType: string, expectedResourcesTotal: number, greaterThan: boolean): Chainable; deleteRancherResource(prefix: 'v3' | 'v1' | 'k8s', resourceType: string, resourceId: string, failOnStatusCode?: boolean): Chainable; deleteNodeTemplate(nodeTemplateId: string, timeout?: number, failOnStatusCode?: boolean) - + enableGithubAuth() + fetchGmailMessage(emailSearchQuery: string) + disableAuth(prefix: 'v3' | 'v1', resourceType: string, resourceName: string) tableRowsPerPageAndNamespaceFilter(rows: number, cluster: string, groupBy: string, namespacefilter: string, interation?: number) /** diff --git a/cypress/jenkins/Jenkinsfile b/cypress/jenkins/Jenkinsfile index f1206e18147..c0847ade188 100644 --- a/cypress/jenkins/Jenkinsfile +++ b/cypress/jenkins/Jenkinsfile @@ -27,7 +27,16 @@ node { string(credentialsId: 'AWS_SECRET_ACCESS_KEY', variable: 'AWS_SECRET_ACCESS_KEY'), string(credentialsId: 'AZURE_AKS_SUBSCRIPTION_ID', variable: 'AZURE_AKS_SUBSCRIPTION_ID'), string(credentialsId: 'AZURE_CLIENT_ID', variable: 'AZURE_CLIENT_ID'), - string(credentialsId: 'AZURE_CLIENT_SECRET', variable: 'AZURE_CLIENT_SECRET')]) { + string(credentialsId: 'AZURE_CLIENT_SECRET', variable: 'AZURE_CLIENT_SECRET'), + string(credentialsId: 'GITHUB_USER1', variable: 'GITHUB_USER1'), + string(credentialsId: 'GITHUB_PASSWORD1', variable: 'GITHUB_PASSWORD1'), + string(credentialsId: 'GITHUB_USER2', variable: 'GITHUB_USER2'), + string(credentialsId: 'GITHUB_PASSWORD2', variable: 'GITHUB_PASSWORD2'), + string(credentialsId: 'GITHUB_CLIENT_ID', variable: 'GITHUB_CLIENT_ID'), + string(credentialsId: 'GITHUB_CLIENT_SECRET', variable: 'GITHUB_CLIENT_SECRET'), + string(credentialsId: 'GOOGLE_CLIENT_ID', variable: 'GOOGLE_CLIENT_ID'), + string(credentialsId: 'GOOGLE_CLIENT_SECRET', variable: 'GOOGLE_CLIENT_SECRET'), + string(credentialsId: 'GOOGLE_REFRESH_TOKEN', variable: 'GOOGLE_REFRESH_TOKEN')]) { withEnv(paramsMap) { stage('Checkout') { deleteDir() diff --git a/cypress/jenkins/cypress.sh b/cypress/jenkins/cypress.sh index a44a703eb28..72fb1188bd4 100755 --- a/cypress/jenkins/cypress.sh +++ b/cypress/jenkins/cypress.sh @@ -19,6 +19,6 @@ yarn add -W mocha cypress-mochawesome-reporter cypress-multi-reporters cypress-c yarn add -W https://github.com/elaichenkov/cypress-delete-downloads-folder -NO_COLOR=1 CYPRESS_grepTags="CYPRESSTAGS" cypress run --browser chrome --config-file cypress/jenkins/cypress.config.jenkins.ts +NO_COLOR=1 CYPRESS_grepTags="CYPRESSTAGS" cypress run --browser chrome --headed --config-file cypress.config.jenkins.ts echo "CYPRESS EXIT CODE: $?" diff --git a/cypress/jenkins/init.sh b/cypress/jenkins/init.sh index ea8b9e1a39e..829017d6e93 100755 --- a/cypress/jenkins/init.sh +++ b/cypress/jenkins/init.sh @@ -93,6 +93,15 @@ corral config vars set azure_subscription_id "${AZURE_AKS_SUBSCRIPTION_ID}" corral config vars set azure_client_id "${AZURE_CLIENT_ID}" corral config vars set azure_client_secret "${AZURE_CLIENT_SECRET}" corral config vars set create_initial_clusters "${CREATE_INITIAL_CLUSTERS}" +corral config vars set github_user1 "${GITHUB_USER1}" +corral config vars set github_password1 "${GITHUB_PASSWORD1}" +corral config vars set github_user2 "${GITHUB_USER2}" +corral config vars set github_password2 "${GITHUB_PASSWORD2}" +corral config vars set github_client_id "${GITHUB_CLIENT_ID}" +corral config vars set github_client_secret "${GITHUB_CLIENT_SECRET}" +corral config vars set google_client_id "${GOOGLE_CLIENT_ID}" +corral config vars set google_client_secret "${GOOGLE_CLIENT_SECRET}" +corral config vars set google_refresh_token "${GOOGLE_REFRESH_TOKEN}" create_initial_clusters() { shopt -u nocasematch @@ -211,10 +220,23 @@ if [[ "${JOB_TYPE}" == "existing" ]]; then shopt -u nocasematch fi +if [[ "${JOB_TYPE}" == "local" ]]; then + RANCHER_TYPE="local" + corral config vars set rancher_version "${RANCHER_VERSION}" + shopt -s nocasematch + if [[ "${CREATE_INITIAL_CLUSTERS}" == "yes" ]]; then + create_initial_clusters + fi + shopt -u nocasematch +fi + echo "Rancher type: ${RANCHER_TYPE}" -override_node=$(semver lt "${RANCHER_VERSION}" "2.9.99") -if [[ ${override_node} -eq 0 && "${RANCHER_IMAGE_TAG}" != "head" ]]; then NODEJS_VERSION="16.20.2"; fi +if semver lt "${RANCHER_VERSION}" "2.9.99" && [[ "${RANCHER_IMAGE_TAG}" != "head" ]]; then + NODEJS_VERSION="16.20.2" +else + NODEJS_VERSION="20.17.0" +fi corral config vars set rancher_type "${RANCHER_TYPE}" corral config vars set nodejs_version "${NODEJS_VERSION}" diff --git a/cypress/support/commands/commands.ts b/cypress/support/commands/commands.ts index 9d0ecee30bc..1702756008c 100644 --- a/cypress/support/commands/commands.ts +++ b/cypress/support/commands/commands.ts @@ -98,3 +98,53 @@ Cypress.Commands.add('shouldHaveCssVar', { prevSubject: true }, (subject, styleN .should('eq', evaluatedStyle); }); }); + +let accessToken = ''; + +Cypress.Commands.add('fetchGmailMessage', (query) => { + const clientId = Cypress.env('googleClientId'); + const clientSecret = Cypress.env('googleClientSecret'); + const refreshToken = Cypress.env('googleRefreshToken'); + + // get a new access token + return cy.request({ + method: 'POST', + url: 'https://oauth2.googleapis.com/token', + form: true, + body: { + client_id: clientId, + client_secret: clientSecret, + refresh_token: refreshToken, + grant_type: 'refresh_token', + } + }).then((resp) => { + expect(resp.status).to.eq(200); + + accessToken = resp.body.access_token; + + // use the access token to query the Gmail API + return cy.request({ + method: 'GET', + url: 'https://gmail.googleapis.com/gmail/v1/users/me/messages', + headers: { Authorization: `Bearer ${ accessToken }` }, + qs: { q: query } // use Gmail search syntax, e.g., "subject:test" + }).then((resp) => { + expect(resp.status).to.eq(200); + + const messages = resp.body.messages; + + expect(messages).to.not.equal(undefined); + + // get the message ID of the first email + const messageId = messages[0].id; + + cy.request({ + method: 'GET', + url: `https://gmail.googleapis.com/gmail/v1/users/me/messages/${ messageId }`, + headers: { Authorization: `Bearer ${ accessToken }` } + }).then(() => { + expect(resp.status).to.eq(200); + }); + }); + }); +}); diff --git a/cypress/support/commands/rancher-api-commands.ts b/cypress/support/commands/rancher-api-commands.ts index 70a09fdb084..011f822d402 100644 --- a/cypress/support/commands/rancher-api-commands.ts +++ b/cypress/support/commands/rancher-api-commands.ts @@ -14,41 +14,50 @@ Cypress.Commands.add('login', ( username = Cypress.env('username'), password = Cypress.env('password'), cacheSession = true, + localAuth = true, + switchToLocalAuth = false, + authProvider = 'GitHub' ) => { const login = () => { cy.intercept('POST', '/v3-public/localProviders/local*').as('loginReq'); + cy.intercept('GET', '/v3-public/authProviders').as('authReq'); LoginPagePo.goTo(); // Needs to happen before the page element is created/located const loginPage = new LoginPagePo(); - loginPage - .checkIsCurrentPage(); + loginPage.waitForPage(); - loginPage.switchToLocal(); + if (localAuth) { + if (switchToLocalAuth) { + loginPage.switchToLocal().click({ force: true }); + } - loginPage.canSubmit() - .should('eq', true); + loginPage.canSubmit() + .should('eq', true); - loginPage.username() - .set(username); + loginPage.username() + .set(username); - loginPage.password() - .set(password); + loginPage.password() + .set(password); - loginPage.canSubmit() - .should('eq', true); - loginPage.submit(); + loginPage.canSubmit() + .should('eq', true); + loginPage.submit(); - cy.wait('@loginReq').its('request.body') - .should( - 'deep.equal', - { - username, - password, - description: 'UI session', - responseType: 'cookie' - } - ); + cy.wait('@loginReq').its('request.body') + .should( + 'deep.equal', + { + username, + password, + description: 'UI session', + responseType: 'cookie' + } + ); + } else { + loginPage.useAuthProvider(`Log in with ${ authProvider }`).click({ force: true }); + } }; if (cacheSession) { @@ -1060,3 +1069,220 @@ Cypress.Commands.add('tableRowsPerPageAndNamespaceFilter', (rows: number, cluste }); }); }); + +Cypress.Commands.add('disableAuth', (prefix, resourceType, resourceName) => { + cy.getCookie('CSRF').then((token) => { + return cy.request({ + method: 'POST', + url: `${ Cypress.env('api') }/${ prefix }/${ resourceType }/${ resourceName }?action=disable`, + headers: { + 'x-api-csrf': token.value, + Accept: 'application/json' + }, + failOnStatusCode: false, + }); + }) + .then((resp) => { + if (resp.status === 404 && resp.body.code === 'ActionNotAvailable') { + cy.log('Auth already disabled.'); + + return ''; + } else { + expect(resp.status).to.eq(200); + } + }); +}); + +let principalId; +let uuid; + +Cypress.Commands.add('enableGithubAuth', () => { + const githubClientId = Cypress.env('githubClientId'); + const githubClientSecret = Cypress.env('githubClientSecret'); + + return cy.request({ + method: 'GET', + url: `${ Cypress.env('api') }/v3/authConfigs`, + headers: { + 'x-api-csrf': token.value, + Accept: 'application/json' + } + }).then((response) => { + expect(response.status).to.eq(200); + + response.body.data.forEach((item: any) => { + if (item.id === 'github') { + uuid = item.uuid; + + cy.log(uuid); + } + }); + + // Step 1: Configure GitHub auth + return cy.request({ + method: 'POST', + url: `${ Cypress.env('api') }/v3/githubConfigs/github?action=configureTest`, + headers: { + 'x-api-csrf': token.value, + Accept: 'application/json' + }, + body: { + actions: { + configureTest: 'https://yb211.qa.rancher.space/v3/githubConfigs/github?action=configureTest', + testAndApply: 'https://yb211.qa.rancher.space/v3/githubConfigs/github?action=testAndApply' + }, + annotations: { 'management.cattle.io/auth-provider-cleanup': 'rancher-locked' }, + baseType: 'authConfig', + // created: '2024-11-24T18:15:51Z', + // createdTS: 1732472151000, + creatorId: null, + enabled: false, + hostname: 'github.com', + id: 'github', + labels: { 'cattle.io/creator': 'norman' }, + links: { self: 'https://yb211.qa.rancher.space/v3/githubConfigs/github', update: 'https://yb211.qa.rancher.space/v3/githubConfigs/github' }, + logoutAllSupported: false, + name: 'github', + tls: true, + type: 'githubConfig', + uuid, + __clone: true, + clientSecret: githubClientSecret, + clientId: githubClientId + } + }).then((response) => { + expect(response.status).to.eq(200); + cy.log('GitHub Auth configured successfully.'); + + // Step 2: Save GitHub auth configuration + return cy.request({ + method: 'PUT', + url: `${ Cypress.env('api') }/v3/githubConfigs/github`, + headers: { + 'x-api-csrf': token.value, + Accept: 'application/json' + }, + body: { + enabled: true, + githubConfig: { + actions: { + configureTest: 'https://yb211.qa.rancher.space/v3/githubConfigs/github?action=configureTest', + testAndApply: 'https://yb211.qa.rancher.space/v3/githubConfigs/github?action=testAndApply' + }, + annotations: { 'management.cattle.io/auth-provider-cleanup': 'rancher-locked' }, + baseType: 'authConfig', + // created: '2024-11-24T18:15:51Z', + // createdTS: 1732472151000, + creatorId: null, + enabled: true, + hostname: 'github.com', + id: 'github', + labels: { 'cattle.io/creator': 'norman' }, + links: { self: 'https://yb211.qa.rancher.space/v3/githubConfigs/github', update: 'https://yb211.qa.rancher.space/v3/githubConfigs/github' }, + logoutAllSupported: false, + name: 'github', + tls: true, + type: 'githubConfig', + uuid, + __clone: true, + clientSecret: githubClientSecret, + clientId: githubClientId, + accessMode: 'restricted', + allowedPrincipalIds: ['github_user://188918568'] + + }, + description: 'Enable GitHub', + // code: '' + } + }).then((saveResponse) => { + expect(saveResponse.status).to.eq(200); + cy.log('GitHub Auth configuration saved successfully.'); + + // Step 3: Apply GitHub auth configuration + return cy.request({ + method: 'PUT', + url: `${ Cypress.env('api') }/v3/githubConfigs/github?action=testAndApply`, + headers: { + 'x-api-csrf': token.value, + Accept: 'application/json' + }, + body: { + enabled: true, + githubConfig: { + actions: { + configureTest: 'https://yb211.qa.rancher.space/v3/githubConfigs/github?action=configureTest', + testAndApply: 'https://yb211.qa.rancher.space/v3/githubConfigs/github?action=testAndApply' + }, + annotations: { 'management.cattle.io/auth-provider-cleanup': 'rancher-locked' }, + baseType: 'authConfig', + created: '2024-11-24T18:15:51Z', + createdTS: 1732472151000, + creatorId: null, + enabled: true, + hostname: 'github.com', + id: 'github', + labels: { 'cattle.io/creator': 'norman' }, + links: { self: 'https://yb211.qa.rancher.space/v3/githubConfigs/github', update: 'https://yb211.qa.rancher.space/v3/githubConfigs/github' }, + logoutAllSupported: false, + name: 'github', + tls: true, + type: 'githubConfig', + uuid, + __clone: true, + clientId: githubClientId, + clientSecret: githubClientSecret, + accessMode: 'restricted', + allowedPrincipalIds: ['github_user://188918568'], + // oauthToken: '' + }, + description: 'Enable GitHub', + // code: '' + } + }).then((saveResponse) => { + expect(saveResponse.status).to.eq(200); + cy.log('GitHub Auth configuration applied successfully.?????????????????????????????????'); + + // Step 4: apply to UI????????????? + return cy.request({ + method: 'PUT', + url: `${ Cypress.env('api') }/v3/githubConfigs/github`, + headers: { + 'x-api-csrf': token.value, + Accept: 'application/json' + }, + body: { + accessMode: 'restricted', + actions: { + configureTest: 'https://yb211.qa.rancher.space/v3/githubConfigs/github?action=configureTest', + disable: 'https://yb211.qa.rancher.space/v3/githubConfigs/github?action=disable', + testAndApply: 'https://yb211.qa.rancher.space/v3/githubConfigs/github?action=testAndApply' + }, + annotations: { 'management.cattle.io/auth-provider-cleanup': 'unlocked' }, + baseType: 'authConfig', + clientId: githubClientId, + created: '2024-11-24T18:15:51Z', + createdTS: 1732472151000, + creatorId: null, + enabled: true, + hostname: 'github.com', + id: 'github', + labels: { 'cattle.io/creator': 'norman' }, + links: { self: 'https://yb211.qa.rancher.space/v3/githubConfigs/github', update: 'https://yb211.qa.rancher.space/v3/githubConfigs/github' }, + logoutAllSupported: false, + name: 'github', + status: { conditions: [{ status: 'True', type: 'SecretsMigrated' }], type: '/v3/schemas/authConfigStatus' }, + tls: true, + type: 'githubConfig', + uuid, + clientSecret: githubClientSecret, + allowedPrincipalIds: ['github_user://188918568'] + } + }).then((saveResponse) => { + expect(saveResponse.status).to.eq(200); + cy.log('GitHub Auth configuration applied to UI successfully.???????????????????'); + }); + }); + }); + }); + }); +}); diff --git a/package.json b/package.json index 1e8ab5ede69..30e649f8f3f 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "express": "4.17.1", "file-saver": "2.0.2", "floating-vue": "5.2.2", + "googleapis": "^144.0.0", "https": "1.0.0", "identicon.js": "2.3.3", "intl-messageformat": "7.8.4", diff --git a/shell/components/auth/AllowedPrincipals.vue b/shell/components/auth/AllowedPrincipals.vue index 890baa4b1ed..d430770cae9 100644 --- a/shell/components/auth/AllowedPrincipals.vue +++ b/shell/components/auth/AllowedPrincipals.vue @@ -73,6 +73,7 @@ export default { name="accessMode" :mode="mode" :options="accessModeOptions" + data-testid="access-mode-options" />
diff --git a/shell/edit/auth/github.vue b/shell/edit/auth/github.vue index 574310183a3..1ac29a12d7c 100644 --- a/shell/edit/auth/github.vue +++ b/shell/edit/auth/github.vue @@ -236,6 +236,7 @@ export default {
@@ -243,6 +244,7 @@ export default {