Skip to content

Commit

Permalink
Cypress login with MFA (#1070)
Browse files Browse the repository at this point in the history
  • Loading branch information
roicarrera authored Oct 28, 2024
1 parent b66b4c4 commit 310c33c
Show file tree
Hide file tree
Showing 13 changed files with 159 additions and 213 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
- Add microsoft-edge to nodejs agents for using with cypress ([#1063](https://github.com/opendevstack/ods-quickstarters/pull/1063))
- Replaced centos8 repository for AlmaLinux 8 due to deprecation ([#1063](https://github.com/opendevstack/ods-quickstarters/pull/1063))
- Improvements in the reporter for cypress ([#1042](https://github.com/opendevstack/ods-quickstarters/issues/1042))
- Added new function to cypress to log into applications using MFA ([#1070](https://github.com/opendevstack/ods-quickstarters/pull/1070))

### Added

Expand Down
14 changes: 13 additions & 1 deletion docs/modules/quickstarters/pages/e2e-cypress.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,21 @@ RUN chgrp -R 0 $HOME && \
USER 1001
----

== Configuring Microsoft Account with Google Authenticator

To use the login with MFA function, the following requirements have to be met:

1. A testing account must be created with access to the application under test.

2. The account must have MFA enabled.

3. The MFA method used must be OTP (One Time Password).

4. The secret key generated during the setup process must be used for the OTP generation. In the case of microsoft, it can be retrieved by clicking the "Can't scan image" button during the setup process.

== Cypress Cloud

To use Cypress Cloud within the Cypress QuickSarter, follow these steps:
To use Cypress Cloud within the Cypress QuickStarter, follow these steps:

1. **Create a project in Cypress Cloud.** Access Cypress Cloud by following this link (https://cloud.cypress.io/), and create a project. This project will be used to store your Cypress tests and results.

Expand Down
4 changes: 3 additions & 1 deletion e2e-cypress/Jenkinsfile.template
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def stageTest(def context) {
// azureClientId = sh(returnStdout: true, script:"oc get secret azure -o jsonpath='{.data.AZURE_CLIENT_ID}' | base64 -d")
// azureClientSecret = sh(returnStdout: true, script:"oc get secret azure -o jsonpath='{.data.AZURE_CLIENT_SECRET}' | base64 -d")
// azureTenantId = sh(returnStdout: true, script:"oc get configmaps azure -o jsonpath='{.data.AZURE_TENANT}'") // config map values are not base64 encoded
// authenticatorOTPSecret = sh(returnStdout: true, script:"oc get secret azure -o jsonpath='{.data.OTP_SECRET}' | base64 -d")

withEnv(["TAGVERSION=${context.tagversion}",
"NEXUS_HOST=${context.nexusHost}",
Expand All @@ -65,7 +66,8 @@ def stageTest(def context) {
// "CYPRESS_CLIENT_ID=${azureClientId}",
// "CYPRESS_CLIENT_SECRET=${azureClientSecret}",
// "CYPRESS_USERNAME=${cypressUser}",
// "CYPRESS_PASSWORD=${cypressPassword}"
// "CYPRESS_PASSWORD=${cypressPassword}",
// "OTP_SECRET=${authenticatorOTPSecret}",
"COMMIT_INFO_SHA=${context.gitCommit}",
"BUILD_NUMBER=${context.buildNumber}",
]) {
Expand Down
3 changes: 3 additions & 0 deletions e2e-cypress/files/cypress-acceptance.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ export default defineConfig({
return (await import('./plugins/index')).default(on, config);
},
},
// env: {
// otp_secret: process.env.OTP_SECRET
// },
})
3 changes: 3 additions & 0 deletions e2e-cypress/files/cypress-installation.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ export default defineConfig({
return (await import('./plugins/index')).default(on, config);
},
},
// env: {
// otp_secret: process.env.OTP_SECRET
// },
})
3 changes: 3 additions & 0 deletions e2e-cypress/files/cypress-integration.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ export default defineConfig({
return (await import('./plugins/index')).default(on, config);
},
},
// env: {
// otp_secret: process.env.OTP_SECRET
// },
})
3 changes: 3 additions & 0 deletions e2e-cypress/files/cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@ export default defineConfig({
return (await import('./plugins/index')).default(on, config);
},
},
// env: {
// otp_secret: process.env.OTP_SECRET
// },
})
1 change: 1 addition & 0 deletions e2e-cypress/files/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"junit-report-merger": "^7.0.0",
"mocha-junit-reporter": "^2.2.1",
"npm-run-all": "^4.1.5",
"otplib": "^12.0.1",
"rimraf": "^6.0.1",
"sharp": "^0.33.5",
"typescript": "^5.5.4"
Expand Down
7 changes: 5 additions & 2 deletions e2e-cypress/files/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
// ***********************************************

import { addGenericLoginCommands } from './generic-login';
import { addMsalv2LoginCommand } from './msalv2-login';
import { addLoginToAADWithMFA, addLoginToAAD, addGetTOTP, addSessionLoginWithMFA } from './login-functions';

addGenericLoginCommands();
addMsalv2LoginCommand();
addGetTOTP();
addSessionLoginWithMFA();
addLoginToAAD();
addLoginToAADWithMFA();
70 changes: 1 addition & 69 deletions e2e-cypress/files/support/e2e.ts
Original file line number Diff line number Diff line change
@@ -1,72 +1,4 @@
//For more details, you can follow this link:
//https://docs.cypress.io/guides/end-to-end-testing/azure-active-directory-authentication#Microsoft-AAD-Application-Setup
function loginViaAAD(username: string, password: string) {

//Go to your application URL and trigger the login.
cy.visit('')

//If needed, navigate and click on the login button.
//As an example:
//cy.get('button#signIn').click()

//Login to your AAD tenant.
//ATENTION: The redirection can happen at the 'login.microsoftonline.com' and also it might redirect as well to 'login.live.com'
cy.origin(
'https://login.microsoftonline.com',
{
args: {
username,
password,
},
},
({ username, password }) => {
cy.get('input[type="email"]').type(username, {
log: false,
})
cy.get('input[type="submit"]').click()
cy.get('input[type="password"]').type(password, {
log: false,
})
cy.get('input[type="submit"]').click()
}
)

//Depending on the user and how they are registered with Microsoft, the origin may go to live.com
//cy.origin(
// 'login.live.com',
// {
// args: {
// password,
// },
// },
// ({ password }) => {
// cy.get('input[type="password"]').type(password, {
// log: false,
// })
// cy.get('input[type="submit"]').click()
// cy.get('#idBtn_Back').click()
// }
//)

// Ensure Microsoft has redirected us back to the sample app with our logged in user.
cy.url().should('equal', Cypress.config().baseUrl)
}

//See how to use it at:
//tests/acceptance/acceptance.spec.cy.ts
Cypress.Commands.add('loginToAAD', (username: string, password: string) => {
const log = Cypress.log({
displayName: 'Azure Active Directory Login',
message: [`🔐 Authenticating | ${username}`],
autoEnd: false,
})
log.snapshot('before')

loginViaAAD(username, password)

log.snapshot('after')
log.end()
})
import './commands';

export const consoleLogs: string[] = [];

Expand Down
120 changes: 120 additions & 0 deletions e2e-cypress/files/support/login-functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
//For more details, you can follow this link:
//https://docs.cypress.io/guides/end-to-end-testing/azure-active-directory-authentication#Microsoft-AAD-Application-Setup

import { authenticator } from 'otplib';

function loginViaAAD(username: string, password: string) {

//Go to your application URL and trigger the login.
cy.visit('')

//If needed, navigate and click on the login button.
//As an example:
//cy.get('button#signIn').click()

//Login to your AAD tenant.
//ATENTION: The redirection can happen at the 'login.microsoftonline.com' and also it might redirect as well to 'login.live.com'
cy.origin(
'https://login.microsoftonline.com',
{
args: {
username,
password,
},
},
({ username, password }) => {
cy.get('input[type="email"]').type(username, {
log: false,
})
cy.get('input[type="submit"]').click()
cy.get('input[type="password"]').type(password, {
log: false,
})
cy.get('input[type="submit"]').click()
}
)

//Depending on the user and how they are registered with Microsoft, the origin may go to live.com
//cy.origin(
// 'login.live.com',
// {
// args: {
// password,
// },
// },
// ({ password }) => {
// cy.get('input[type="password"]').type(password, {
// log: false,
// })
// cy.get('input[type="submit"]').click()
// cy.get('#idBtn_Back').click()
// }
//)
}

//See how to use it at:
//tests/acceptance/acceptance.spec.cy.ts
export function addLoginToAAD() {
Cypress.Commands.add('loginToAAD', (username: string, password: string) => {
cy.session([username], () => {
const log = Cypress.log({
autoEnd: false,
displayName: 'Azure Active Directory Login',
message: [`🔐 Authenticating | ${username}`],
});
log.snapshot('before');

loginViaAAD(username, password);

// Ensure Microsoft has redirected us back to the sample app with our logged in user.
cy.url().should('equal', Cypress.config().baseUrl)

log.snapshot('after');
log.end();
});
});
}

export function addSessionLoginWithMFA() {
Cypress.Commands.add('sessionLoginWithMFA', (username: string, password: string) => {
cy.session('login', () => cy.loginToAADWithMFA(username, password), {
validate: () => cy.getAllLocalStorage().should(validateLocalStorage),
cacheAcrossSpecs: true
})
})
}

export function addLoginToAADWithMFA() {
Cypress.Commands.add('loginToAADWithMFA', (username: string, password: string) => {
loginViaAAD(username, password);
cy.getTOTP().then((otp1) => {
const objOTP = { otp: otp1 }
cy.origin('https://login.microsoftonline.com/', { args: objOTP }, ({ otp }) => {
cy.get("[name='otc']").type(otp)
cy.get('input[type="submit"]').click()
})
})
})
}

export function addGetTOTP() {
Cypress.Commands.add('getTOTP', () => {
return authenticator.generate(Cypress.env('otp_secret'))
})
}

const validateLocalStorage = (localStorage: Record<string, unknown>) =>
Cypress._.some(localStorage, (value: unknown, key: string) =>
key.includes('CognitoIdentityServiceProvider'),
)

declare global {
namespace Cypress {
interface Chainable<> {
loginToAAD(username: string, password: string);
loginToAADWithMFA(username: string, password: string);
sessionLoginWithMFA(username: string, password: string);
getTOTP();
}
}
}
Loading

0 comments on commit 310c33c

Please sign in to comment.