Skip to content

Commit

Permalink
[Security Solution] Login refactor (#172472)
Browse files Browse the repository at this point in the history
  • Loading branch information
MadameSheema authored Dec 5, 2023
1 parent ae5e2fd commit b0219f9
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 147 deletions.
158 changes: 18 additions & 140 deletions x-pack/test/security_solution_cypress/cypress/tasks/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import * as yaml from 'js-yaml';
import { LoginState } from '@kbn/security-plugin/common/login_state';
import type { SecurityRoleName } from '@kbn/security-solution-plugin/common/test';
import { KNOWN_SERVERLESS_ROLE_DEFINITIONS } from '@kbn/security-solution-plugin/common/test';
Expand All @@ -18,160 +16,36 @@ import {
} from '../env_var_names_constants';
import { API_HEADERS, rootRequest } from './api_calls/common';

/**
* Credentials in the `kibana.dev.yml` config file will be used to authenticate
* with Kibana when credentials are not provided via environment variables
*/
const KIBANA_DEV_YML_PATH = '../../../config/kibana.dev.yml';

/**
* The configuration path in `kibana.dev.yml` to the username to be used when
* authenticating with Kibana.
*/
const ELASTICSEARCH_USERNAME_CONFIG_PATH = 'config.elasticsearch.username';

/**
* The configuration path in `kibana.dev.yml` to the password to be used when
* authenticating with Kibana.
*/
const ELASTICSEARCH_PASSWORD_CONFIG_PATH = 'config.elasticsearch.password';

/**
* Authenticates with Kibana using, if specified, credentials specified by
* environment variables. The credentials in `kibana.dev.yml` will be used
* for authentication when the environment variables are unset.
*
* To speed the execution of tests, prefer this non-interactive authentication,
* which is faster than authentication via Kibana's interactive login page.
*/
export const login = (role?: SecurityRoleName): void => {
if (role != null) {
loginWithRole(role);
} else if (credentialsProvidedByEnvironment()) {
loginViaEnvironmentCredentials();
} else {
loginViaConfig();
}
};

export interface User {
username: string;
password: string;
}

export const loginWithUser = (user: User): void => {
loginWithUsernameAndPassword(user.username, user.password);
};

/**
* Builds a URL with basic auth using the passed in user.
*
* @param user the user information to build the basic auth with
* @param route string route to visit
*/
export const constructUrlWithUser = (user: User, route: string): string => {
const url = Cypress.config().baseUrl;
const kibana = new URL(String(url));
const hostname = kibana.hostname;
const username = user.username;
const password = user.password;
const protocol = kibana.protocol.replace(':', '');
const port = kibana.port;

const path = `${route.startsWith('/') ? '' : '/'}${route}`;
const strUrl = `${protocol}://${username}:${password}@${hostname}:${port}${path}`;
const builtUrl = new URL(strUrl);

cy.log(`origin: ${builtUrl.href}`);
return builtUrl.href;
export const defaultUser: User = {
username: Cypress.env(ELASTICSEARCH_USERNAME),
password: Cypress.env(ELASTICSEARCH_PASSWORD),
};

/**
* Authenticates with a predefined role
*
* @param role role name
*/
const loginWithRole = (role: SecurityRoleName): void => {
export const getEnvAuth = (role: SecurityRoleName): User => {
if (
(Cypress.env(IS_SERVERLESS) || Cypress.env(CLOUD_SERVERLESS)) &&
!(role in KNOWN_SERVERLESS_ROLE_DEFINITIONS)
) {
throw new Error(`An attempt to log in with unsupported by Serverless role "${role}".`);
}

const password = 'changeme';

cy.log(`origin: ${Cypress.config().baseUrl}`);
loginWithUsernameAndPassword(role, password);
};

/**
* Returns `true` if the credentials used to login to Kibana are provided
* via environment variables
*/
const credentialsProvidedByEnvironment = (): boolean =>
Cypress.env(ELASTICSEARCH_USERNAME) != null && Cypress.env(ELASTICSEARCH_PASSWORD) != null;

/**
* Authenticates with Kibana by reading credentials from the
* `CYPRESS_ELASTICSEARCH_USERNAME` and `CYPRESS_ELASTICSEARCH_PASSWORD`
* environment variables, and POSTing the username and password directly to
* Kibana's `/internal/security/login` endpoint, bypassing the login page (for speed).
*/
const loginViaEnvironmentCredentials = (): void => {
cy.log(
`Authenticating via environment credentials from the \`CYPRESS_${ELASTICSEARCH_USERNAME}\` and \`CYPRESS_${ELASTICSEARCH_PASSWORD}\` environment variables`
);

const username = Cypress.env(ELASTICSEARCH_USERNAME);
const password = Cypress.env(ELASTICSEARCH_PASSWORD);
loginWithUsernameAndPassword(username, password);
};

/**
* Authenticates with Kibana by reading credentials from the
* `kibana.dev.yml` file and POSTing the username and password directly to
* Kibana's `/internal/security/login` endpoint, bypassing the login page (for speed).
*/
const loginViaConfig = (): void => {
cy.log(
`Authenticating via config credentials \`${ELASTICSEARCH_USERNAME_CONFIG_PATH}\` and \`${ELASTICSEARCH_PASSWORD_CONFIG_PATH}\` from \`${KIBANA_DEV_YML_PATH}\``
);

// read the login details from `kibana.dev.yaml`
cy.readFile(KIBANA_DEV_YML_PATH).then((kibanaDevYml) => {
const { username, password } = yaml.safeLoad(kibanaDevYml);
loginWithUsernameAndPassword(username, password);
});
};

/**
* Get the configured auth details that were used to spawn cypress
*
* @returns the default Elasticsearch username and password for this environment
*/
export const getEnvAuth = (): User => {
if (credentialsProvidedByEnvironment()) {
return {
username: Cypress.env(ELASTICSEARCH_USERNAME),
password: Cypress.env(ELASTICSEARCH_PASSWORD),
};
} else {
let user: User = { username: '', password: '' };
cy.readFile(KIBANA_DEV_YML_PATH).then((devYml) => {
const config = yaml.safeLoad(devYml);
user = { username: config.elasticsearch.username, password: config.elasticsearch.password };
});

return user;
}
const user: User = {
username: role,
password: 'changeme',
};
return user;
};

export const logout = (): void => {
cy.visit(LOGOUT_URL);
export const login = (role?: SecurityRoleName): void => {
const user = role ? getEnvAuth(role) : defaultUser;
loginWithUser(user);
};

const loginWithUsernameAndPassword = (username: string, password: string): void => {
export const loginWithUser = (user: User): void => {
const baseUrl = Cypress.config().baseUrl;
if (!baseUrl) {
throw Error(`Cypress config baseUrl not set!`);
Expand All @@ -192,9 +66,13 @@ const loginWithUsernameAndPassword = (username: string, password: string): void
providerType: basicProvider?.type,
providerName: basicProvider?.name,
currentURL: '/',
params: { username, password },
params: { username: user.username, password: user.password },
},
headers: API_HEADERS,
});
});
};

export const logout = (): void => {
cy.visit(LOGOUT_URL);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { constructUrlWithUser, getEnvAuth } from './login';
import { API_AUTH } from './api_calls/common';

interface User {
username: string;
Expand Down Expand Up @@ -183,14 +183,14 @@ const getUserInfo = (user: User): UserInfo => ({
});

export const createUsersAndRoles = (users: User[], roles: Role[]) => {
const envUser = getEnvAuth();
for (const role of roles) {
cy.log(`Creating role: ${JSON.stringify(role)}`);
cy.request({
body: role.privileges,
headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' },
method: 'PUT',
url: constructUrlWithUser(envUser, `/api/security/role/${role.name}`),
auth: API_AUTH,
url: `/api/security/role/${role.name}`,
})
.its('status')
.should('eql', 204);
Expand All @@ -209,21 +209,22 @@ export const createUsersAndRoles = (users: User[], roles: Role[]) => {
},
headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' },
method: 'POST',
url: constructUrlWithUser(envUser, `/internal/security/users/${user.username}`),
auth: API_AUTH,
url: `/internal/security/users/${user.username}`,
})
.its('status')
.should('eql', 200);
}
};

export const deleteUsersAndRoles = (users: User[], roles: Role[]) => {
const envUser = getEnvAuth();
for (const user of users) {
cy.log(`Deleting user: ${JSON.stringify(user)}`);
cy.request({
headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' },
method: 'DELETE',
url: constructUrlWithUser(envUser, `/internal/security/users/${user.username}`),
auth: API_AUTH,
url: `/internal/security/users/${user.username}`,
failOnStatusCode: false,
})
.its('status')
Expand All @@ -235,7 +236,8 @@ export const deleteUsersAndRoles = (users: User[], roles: Role[]) => {
cy.request({
headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' },
method: 'DELETE',
url: constructUrlWithUser(envUser, `/api/security/role/${role.name}`),
auth: API_AUTH,
url: `/api/security/role/${role.name}`,
failOnStatusCode: false,
})
.its('status')
Expand Down

0 comments on commit b0219f9

Please sign in to comment.