From 199683c28ee4e9e7afdfb2b101b694afe55e2a20 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Tue, 5 Mar 2024 22:59:56 -0500 Subject: [PATCH 01/27] Fixes anonymous auth flow to work with SAML Signed-off-by: Darshit Chanpura --- public/apps/login/login-page.tsx | 2 +- server/auth/types/authentication_type.ts | 11 +++++++++++ server/auth/types/basic/basic_auth.ts | 22 +++++++--------------- server/auth/types/basic/routes.ts | 7 ++++++- server/auth/types/multiple/multi_auth.ts | 9 +-------- 5 files changed, 26 insertions(+), 25 deletions(-) diff --git a/public/apps/login/login-page.tsx b/public/apps/login/login-page.tsx index 70d894781..cf350a7c1 100644 --- a/public/apps/login/login-page.tsx +++ b/public/apps/login/login-page.tsx @@ -212,7 +212,7 @@ export function LoginPage(props: LoginPageDeps) { ); - if (authOpts.length > 1) { + if (authOpts.length > 0) { if (props.config.auth.anonymous_auth_enabled) { const anonymousConfig = props.config.ui[AuthType.ANONYMOUS].login; formBody.push( diff --git a/server/auth/types/authentication_type.ts b/server/auth/types/authentication_type.ts index 8bde376ac..b5e1a0cb3 100755 --- a/server/auth/types/authentication_type.ts +++ b/server/auth/types/authentication_type.ts @@ -113,6 +113,12 @@ export abstract class AuthenticationType implements IAuthenticationType { const authHeaders = {}; let cookie: SecuritySessionCookie | null | undefined; let authInfo: any | undefined; + + if (this.config.auth.anonymous_auth_enabled) { + const anonymousAuthHeaders = { _auth_request_type_: 'anonymous' }; + Object.assign(authHeaders, anonymousAuthHeaders); + } + // if this is an REST API call, suppose the request includes necessary auth header // see https://www.elastic.co/guide/en/opensearch-dashboards/master/using-api.html if (this.requestIncludesAuthInfo(request)) { @@ -153,10 +159,14 @@ export abstract class AuthenticationType implements IAuthenticationType { if (request.url.pathname && request.url.pathname.startsWith('/bundles/')) { return toolkit.notHandled(); } + console.log('Request is unauthorized'); + console.log(request.url); + console.log(request.route); // send to auth workflow return this.handleUnauthedRequest(request, response, toolkit); } + console.log('we have a cookie: ' + JSON.stringify(cookie)); // extend session expiration time if (this.config.session.keepalive) { @@ -211,6 +221,7 @@ export abstract class AuthenticationType implements IAuthenticationType { } if (!authInfo) { authInfo = await this.securityClient.authinfo(request, authHeaders); + console.log(authInfo); } authState.authInfo = authInfo; diff --git a/server/auth/types/basic/basic_auth.ts b/server/auth/types/basic/basic_auth.ts index f21f86827..5a3e69c4e 100644 --- a/server/auth/types/basic/basic_auth.ts +++ b/server/auth/types/basic/basic_auth.ts @@ -111,21 +111,13 @@ export class BasicAuthentication extends AuthenticationType { request, this.coreSetup.http.basePath.serverBasePath ); - if (this.config.auth.anonymous_auth_enabled) { - const redirectLocation = `${this.coreSetup.http.basePath.serverBasePath}${ANONYMOUS_AUTH_LOGIN}?${nextUrlParam}`; - return response.redirected({ - headers: { - location: `${redirectLocation}`, - }, - }); - } else { - const redirectLocation = `${this.coreSetup.http.basePath.serverBasePath}${LOGIN_PAGE_URI}?${nextUrlParam}`; - return response.redirected({ - headers: { - location: `${redirectLocation}`, - }, - }); - } + + const redirectLocation = `${this.coreSetup.http.basePath.serverBasePath}${LOGIN_PAGE_URI}?${nextUrlParam}`; + return response.redirected({ + headers: { + location: `${redirectLocation}`, + }, + }); } else { return response.unauthorized({ body: `Authentication required`, diff --git a/server/auth/types/basic/routes.ts b/server/auth/types/basic/routes.ts index bae3e338c..a45179b6f 100755 --- a/server/auth/types/basic/routes.ts +++ b/server/auth/types/basic/routes.ts @@ -186,7 +186,9 @@ export class BasicAuthRoutes { } context.security_plugin.logger.info('The Redirect Path is ' + redirectUrl); try { - user = await this.securityClient.authenticateWithHeaders(request, {}); + user = await this.securityClient.authenticateWithHeaders(request, { + _auth_request_type_: 'anonymous', + }); } catch (error) { context.security_plugin.logger.error( `Failed authentication: ${error}. Redirecting to Login Page` @@ -200,6 +202,8 @@ export class BasicAuthRoutes { }); } + console.log('Anon user: ' + JSON.stringify(user)); + this.sessionStorageFactory.asScoped(request).clear(); const sessionStorage: SecuritySessionCookie = { username: user.username, @@ -209,6 +213,7 @@ export class BasicAuthRoutes { }; if (user.multitenancy_enabled) { + request.headers._auth_request_type_ = 'anonymous'; const selectTenant = resolveTenant({ request, username: user.username, diff --git a/server/auth/types/multiple/multi_auth.ts b/server/auth/types/multiple/multi_auth.ts index 2bcc5e1ba..80dc66fec 100644 --- a/server/auth/types/multiple/multi_auth.ts +++ b/server/auth/types/multiple/multi_auth.ts @@ -166,20 +166,13 @@ export class MultipleAuthentication extends AuthenticationType { this.coreSetup.http.basePath.serverBasePath ); - if (this.config.auth.anonymous_auth_enabled) { - const redirectLocation = `${this.coreSetup.http.basePath.serverBasePath}${ANONYMOUS_AUTH_LOGIN}?${nextUrlParam}`; - return response.redirected({ - headers: { - location: `${redirectLocation}`, - }, - }); - } return response.redirected({ headers: { location: `${this.coreSetup.http.basePath.serverBasePath}${LOGIN_PAGE_URI}?${nextUrlParam}`, }, }); } else { + console.log('not a page request'); return response.unauthorized(); } } From cbdf7a6af5d2b7c0a35c945747286022be448f22 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Mon, 11 Mar 2024 16:40:21 -0400 Subject: [PATCH 02/27] Adds hardcoded credentials for anonymous user Signed-off-by: Darshit Chanpura --- server/auth/types/authentication_type.ts | 6 +----- server/auth/types/basic/routes.ts | 10 +++++++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/server/auth/types/authentication_type.ts b/server/auth/types/authentication_type.ts index b5e1a0cb3..66b4ce14a 100755 --- a/server/auth/types/authentication_type.ts +++ b/server/auth/types/authentication_type.ts @@ -115,7 +115,7 @@ export abstract class AuthenticationType implements IAuthenticationType { let authInfo: any | undefined; if (this.config.auth.anonymous_auth_enabled) { - const anonymousAuthHeaders = { _auth_request_type_: 'anonymous' }; + const anonymousAuthHeaders = { _auth_request_type_: 'anonymous', authorization: 'Basic b3BlbmRpc3Ryb19zZWN1cml0eV9hbm9ueW1vdXM6b3BlbmRpc3Ryb19zZWN1cml0eV9hbm9ueW1vdXM=' }; Object.assign(authHeaders, anonymousAuthHeaders); } @@ -159,10 +159,6 @@ export abstract class AuthenticationType implements IAuthenticationType { if (request.url.pathname && request.url.pathname.startsWith('/bundles/')) { return toolkit.notHandled(); } - console.log('Request is unauthorized'); - console.log(request.url); - console.log(request.route); - // send to auth workflow return this.handleUnauthedRequest(request, response, toolkit); } diff --git a/server/auth/types/basic/routes.ts b/server/auth/types/basic/routes.ts index a45179b6f..2328c8c35 100755 --- a/server/auth/types/basic/routes.ts +++ b/server/auth/types/basic/routes.ts @@ -186,8 +186,13 @@ export class BasicAuthRoutes { } context.security_plugin.logger.info('The Redirect Path is ' + redirectUrl); try { + // user = await this.securityClient.authenticateWithHeaders(request, { + // _auth_request_type_: 'anonymous', + // }); + // opendistro_security_anonymous:opendistro_security_anonymous + const authHeader = "b3BlbmRpc3Ryb19zZWN1cml0eV9hbm9ueW1vdXM6b3BlbmRpc3Ryb19zZWN1cml0eV9hbm9ueW1vdXM="; user = await this.securityClient.authenticateWithHeaders(request, { - _auth_request_type_: 'anonymous', + authorization: `Basic ${authHeader}`, }); } catch (error) { context.security_plugin.logger.error( @@ -214,6 +219,7 @@ export class BasicAuthRoutes { if (user.multitenancy_enabled) { request.headers._auth_request_type_ = 'anonymous'; + request.headers.authorization = 'Basic b3BlbmRpc3Ryb19zZWN1cml0eV9hbm9ueW1vdXM6b3BlbmRpc3Ryb19zZWN1cml0eV9hbm9ueW1vdXM='; const selectTenant = resolveTenant({ request, username: user.username, @@ -232,6 +238,8 @@ export class BasicAuthRoutes { return response.redirected({ headers: { location: `${redirectUrl}`, + _auth_request_type_: "anonymous", + authorization: "Basic b3BlbmRpc3Ryb19zZWN1cml0eV9hbm9ueW1vdXM6b3BlbmRpc3Ryb19zZWN1cml0eV9hbm9ueW1vdXM=" }, }); } else { From a0f2db473e49655d05089731273a9ee98653c5fe Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Mon, 11 Mar 2024 16:56:01 -0400 Subject: [PATCH 03/27] Updates basic auth header to be a config constant Signed-off-by: Darshit Chanpura --- common/index.ts | 2 ++ server/auth/types/authentication_type.ts | 6 ++---- server/auth/types/basic/routes.ts | 16 ++++------------ server/auth/types/multiple/multi_auth.ts | 1 - 4 files changed, 8 insertions(+), 17 deletions(-) diff --git a/common/index.ts b/common/index.ts index b5e6a475d..6b3df18c5 100644 --- a/common/index.ts +++ b/common/index.ts @@ -34,6 +34,8 @@ export const OPENID_AUTH_LOGIN_WITH_FRAGMENT = '/auth/openid/captureUrlFragment' export const SAML_AUTH_LOGIN = '/auth/saml/login'; export const SAML_AUTH_LOGIN_WITH_FRAGMENT = '/auth/saml/captureUrlFragment'; export const ANONYMOUS_AUTH_LOGIN = '/auth/anonymous'; +export const ANONYMOUS_AUTH_HEADER = + 'Basic b3BlbmRpc3Ryb19zZWN1cml0eV9hbm9ueW1vdXM6b3BlbmRpc3Ryb19zZWN1cnV0eV9hbm9ueW1vdXM='; export const OPENID_AUTH_LOGOUT = '/auth/openid/logout'; export const SAML_AUTH_LOGOUT = '/auth/saml/logout'; diff --git a/server/auth/types/authentication_type.ts b/server/auth/types/authentication_type.ts index 66b4ce14a..1b7d87cf6 100755 --- a/server/auth/types/authentication_type.ts +++ b/server/auth/types/authentication_type.ts @@ -31,7 +31,7 @@ import { SecuritySessionCookie } from '../../session/security_cookie'; import { SecurityClient } from '../../backend/opensearch_security_client'; import { resolveTenant, isValidTenant } from '../../multitenancy/tenant_resolver'; import { UnauthenticatedError } from '../../errors'; -import { GLOBAL_TENANT_SYMBOL } from '../../../common'; +import { ANONYMOUS_AUTH_HEADER, GLOBAL_TENANT_SYMBOL } from '../../../common'; export interface IAuthenticationType { type: string; @@ -115,7 +115,7 @@ export abstract class AuthenticationType implements IAuthenticationType { let authInfo: any | undefined; if (this.config.auth.anonymous_auth_enabled) { - const anonymousAuthHeaders = { _auth_request_type_: 'anonymous', authorization: 'Basic b3BlbmRpc3Ryb19zZWN1cml0eV9hbm9ueW1vdXM6b3BlbmRpc3Ryb19zZWN1cml0eV9hbm9ueW1vdXM=' }; + const anonymousAuthHeaders = { authorization: ANONYMOUS_AUTH_HEADER }; Object.assign(authHeaders, anonymousAuthHeaders); } @@ -162,7 +162,6 @@ export abstract class AuthenticationType implements IAuthenticationType { // send to auth workflow return this.handleUnauthedRequest(request, response, toolkit); } - console.log('we have a cookie: ' + JSON.stringify(cookie)); // extend session expiration time if (this.config.session.keepalive) { @@ -217,7 +216,6 @@ export abstract class AuthenticationType implements IAuthenticationType { } if (!authInfo) { authInfo = await this.securityClient.authinfo(request, authHeaders); - console.log(authInfo); } authState.authInfo = authInfo; diff --git a/server/auth/types/basic/routes.ts b/server/auth/types/basic/routes.ts index 2328c8c35..aed5cf587 100755 --- a/server/auth/types/basic/routes.ts +++ b/server/auth/types/basic/routes.ts @@ -23,6 +23,7 @@ import { SecurityPluginConfigType } from '../../..'; import { User } from '../../user'; import { SecurityClient } from '../../../backend/opensearch_security_client'; import { + ANONYMOUS_AUTH_HEADER, ANONYMOUS_AUTH_LOGIN, API_AUTH_LOGIN, API_AUTH_LOGOUT, @@ -186,13 +187,8 @@ export class BasicAuthRoutes { } context.security_plugin.logger.info('The Redirect Path is ' + redirectUrl); try { - // user = await this.securityClient.authenticateWithHeaders(request, { - // _auth_request_type_: 'anonymous', - // }); - // opendistro_security_anonymous:opendistro_security_anonymous - const authHeader = "b3BlbmRpc3Ryb19zZWN1cml0eV9hbm9ueW1vdXM6b3BlbmRpc3Ryb19zZWN1cml0eV9hbm9ueW1vdXM="; user = await this.securityClient.authenticateWithHeaders(request, { - authorization: `Basic ${authHeader}`, + authorization: ANONYMOUS_AUTH_HEADER, }); } catch (error) { context.security_plugin.logger.error( @@ -207,8 +203,6 @@ export class BasicAuthRoutes { }); } - console.log('Anon user: ' + JSON.stringify(user)); - this.sessionStorageFactory.asScoped(request).clear(); const sessionStorage: SecuritySessionCookie = { username: user.username, @@ -218,8 +212,7 @@ export class BasicAuthRoutes { }; if (user.multitenancy_enabled) { - request.headers._auth_request_type_ = 'anonymous'; - request.headers.authorization = 'Basic b3BlbmRpc3Ryb19zZWN1cml0eV9hbm9ueW1vdXM6b3BlbmRpc3Ryb19zZWN1cml0eV9hbm9ueW1vdXM='; + request.headers.authorization = ANONYMOUS_AUTH_HEADER; const selectTenant = resolveTenant({ request, username: user.username, @@ -238,8 +231,7 @@ export class BasicAuthRoutes { return response.redirected({ headers: { location: `${redirectUrl}`, - _auth_request_type_: "anonymous", - authorization: "Basic b3BlbmRpc3Ryb19zZWN1cml0eV9hbm9ueW1vdXM6b3BlbmRpc3Ryb19zZWN1cml0eV9hbm9ueW1vdXM=" + authorization: ANONYMOUS_AUTH_HEADER, }, }); } else { diff --git a/server/auth/types/multiple/multi_auth.ts b/server/auth/types/multiple/multi_auth.ts index 80dc66fec..840bcdcf1 100644 --- a/server/auth/types/multiple/multi_auth.ts +++ b/server/auth/types/multiple/multi_auth.ts @@ -172,7 +172,6 @@ export class MultipleAuthentication extends AuthenticationType { }, }); } else { - console.log('not a page request'); return response.unauthorized(); } } From 3c0c2f981dcf4d0bea26ae25ac8d20e02911f9a2 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Mon, 11 Mar 2024 18:01:23 -0400 Subject: [PATCH 04/27] Removes unneeded usage of anonymous auth header constant Signed-off-by: Darshit Chanpura --- server/auth/types/authentication_type.ts | 2 ++ server/auth/types/basic/routes.ts | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/auth/types/authentication_type.ts b/server/auth/types/authentication_type.ts index 1b7d87cf6..14ca8ec27 100755 --- a/server/auth/types/authentication_type.ts +++ b/server/auth/types/authentication_type.ts @@ -114,6 +114,7 @@ export abstract class AuthenticationType implements IAuthenticationType { let cookie: SecuritySessionCookie | null | undefined; let authInfo: any | undefined; + // Adds a basic auth credentials headers to requests originated as anonymous user if (this.config.auth.anonymous_auth_enabled) { const anonymousAuthHeaders = { authorization: ANONYMOUS_AUTH_HEADER }; Object.assign(authHeaders, anonymousAuthHeaders); @@ -159,6 +160,7 @@ export abstract class AuthenticationType implements IAuthenticationType { if (request.url.pathname && request.url.pathname.startsWith('/bundles/')) { return toolkit.notHandled(); } + // send to auth workflow return this.handleUnauthedRequest(request, response, toolkit); } diff --git a/server/auth/types/basic/routes.ts b/server/auth/types/basic/routes.ts index aed5cf587..4370c63a3 100755 --- a/server/auth/types/basic/routes.ts +++ b/server/auth/types/basic/routes.ts @@ -212,7 +212,6 @@ export class BasicAuthRoutes { }; if (user.multitenancy_enabled) { - request.headers.authorization = ANONYMOUS_AUTH_HEADER; const selectTenant = resolveTenant({ request, username: user.username, @@ -231,7 +230,6 @@ export class BasicAuthRoutes { return response.redirected({ headers: { location: `${redirectUrl}`, - authorization: ANONYMOUS_AUTH_HEADER, }, }); } else { From 8322a23df4f3e3c2fdec9e8baa71e367c75d4daf Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Mon, 11 Mar 2024 18:41:59 -0400 Subject: [PATCH 05/27] Updates logic to display anonymous auth login button Signed-off-by: Darshit Chanpura --- public/apps/login/login-page.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/public/apps/login/login-page.tsx b/public/apps/login/login-page.tsx index cf350a7c1..9057e452e 100644 --- a/public/apps/login/login-page.tsx +++ b/public/apps/login/login-page.tsx @@ -212,14 +212,14 @@ export function LoginPage(props: LoginPageDeps) { ); - if (authOpts.length > 0) { - if (props.config.auth.anonymous_auth_enabled) { - const anonymousConfig = props.config.ui[AuthType.ANONYMOUS].login; - formBody.push( - renderLoginButton(AuthType.ANONYMOUS, ANONYMOUS_AUTH_LOGIN, anonymousConfig) - ); - } + if (props.config.auth.anonymous_auth_enabled) { + const anonymousConfig = props.config.ui[AuthType.ANONYMOUS].login; + formBody.push( + renderLoginButton(AuthType.ANONYMOUS, ANONYMOUS_AUTH_LOGIN, anonymousConfig) + ); + } + if (authOpts.length > 1) { formBody.push(); formBody.push(); formBody.push(); From f33137774fbc8df7b1cd2e3e6499225dc8a8c106 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Mon, 11 Mar 2024 19:03:00 -0400 Subject: [PATCH 06/27] Adds test to check whether anonymous auth login button is displayed correctly Signed-off-by: Darshit Chanpura --- .../__snapshots__/login-page.test.tsx.snap | 413 ++++++++++++++++++ public/apps/login/test/login-page.test.tsx | 54 +++ 2 files changed, 467 insertions(+) diff --git a/public/apps/login/test/__snapshots__/login-page.test.tsx.snap b/public/apps/login/test/__snapshots__/login-page.test.tsx.snap index b8a1e1182..de432dcca 100644 --- a/public/apps/login/test/__snapshots__/login-page.test.tsx.snap +++ b/public/apps/login/test/__snapshots__/login-page.test.tsx.snap @@ -153,6 +153,419 @@ exports[`Login page renders renders with config value for multiauth 1`] = ` `; +exports[`Login page renders renders with config value for multiauth with anonymous auth enabled 1`] = ` + + + + + Title1 + + + + SubTitle1 + + + + + + } + value="" + /> + + + + } + type="password" + value="" + /> + + + + Log in + + + + + + + + + + + Button1 + + + + + Button2 + + + + +`; + +exports[`Login page renders renders with config value with anonymous auth enabled: string 1`] = ` + + + + + Title1 + + + + SubTitle1 + + + + + + } + value="" + /> + + + + } + type="password" + value="" + /> + + + + Log in + + + + + + + +`; + +exports[`Login page renders renders with config value with anonymous auth enabled: string array 1`] = ` + + + + + Title1 + + + + SubTitle1 + + + + + + } + value="" + /> + + + + } + type="password" + value="" + /> + + + + Log in + + + + + + + +`; + exports[`Login page renders renders with config value: string 1`] = ` { expect(component).toMatchSnapshot(); }); + it('renders with config value with anonymous auth enabled: string array', () => { + const config: ClientConfigType = { + ui: configUI, + auth: { + type: [AuthType.BASIC], + logout_url: API_AUTH_LOGOUT, + anonymous_auth_enabled: true, + }, + }; + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + }); + it('renders with config value: string', () => { const config: ClientConfigType = { ui: configUI, @@ -126,6 +150,21 @@ describe('Login page', () => { expect(component).toMatchSnapshot(); }); + it('renders with config value with anonymous auth enabled: string', () => { + const config: ClientConfigType = { + ui: configUI, + auth: { + type: AuthType.BASIC, + logout_url: API_AUTH_LOGOUT, + anonymous_auth_enabled: true, + }, + }; + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + }); + it('renders with config value for multiauth', () => { const config: ClientConfigType = { ui: configUI, @@ -140,6 +179,21 @@ describe('Login page', () => { expect(component).toMatchSnapshot(); }); + it('renders with config value for multiauth with anonymous auth enabled', () => { + const config: ClientConfigType = { + ui: configUI, + auth: { + type: [AuthType.BASIC, 'openid', AuthType.SAML], + logout_url: API_AUTH_LOGOUT, + anonymous_auth_enabled: true, + }, + }; + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + }); + it('renders with default value: string array', () => { const config: ClientConfigType = { ui: configUiDefault, From 63abb41a31ababb8828bba7faf5ebe72592b592b Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Mon, 11 Mar 2024 23:57:00 -0400 Subject: [PATCH 07/27] Fixes integrationtests Signed-off-by: Darshit Chanpura --- server/auth/types/authentication_type.ts | 5 ++++- test/constant.ts | 5 ----- test/jest_integration/basic_auth.test.ts | 18 +++++------------- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/server/auth/types/authentication_type.ts b/server/auth/types/authentication_type.ts index 14ca8ec27..e58b2a5ce 100755 --- a/server/auth/types/authentication_type.ts +++ b/server/auth/types/authentication_type.ts @@ -115,7 +115,10 @@ export abstract class AuthenticationType implements IAuthenticationType { let authInfo: any | undefined; // Adds a basic auth credentials headers to requests originated as anonymous user - if (this.config.auth.anonymous_auth_enabled) { + if ( + this.config.auth.anonymous_auth_enabled && + !request.headers.hasOwnProperty('authorization') + ) { const anonymousAuthHeaders = { authorization: ANONYMOUS_AUTH_HEADER }; Object.assign(authHeaders, anonymousAuthHeaders); } diff --git a/test/constant.ts b/test/constant.ts index 713ea05de..7987d5adf 100644 --- a/test/constant.ts +++ b/test/constant.ts @@ -13,14 +13,9 @@ * permissions and limitations under the License. */ -import { version, opensearchDashboards } from '../package.json'; - export const OPENSEARCH_DASHBOARDS_SERVER_USER: string = 'kibanaserver'; export const OPENSEARCH_DASHBOARDS_SERVER_PASSWORD: string = 'kibanaserver'; -export const ELASTICSEARCH_VERSION: string = opensearchDashboards.version; -export const SECURITY_ES_PLUGIN_VERSION: string = version; - export const ADMIN_USER: string = 'admin'; export const ADMIN_PASSWORD: string = 'admin'; const ADMIN_USER_PASS: string = `${ADMIN_USER}:${ADMIN_PASSWORD}`; diff --git a/test/jest_integration/basic_auth.test.ts b/test/jest_integration/basic_auth.test.ts index b4bbd3e55..9e1e7ae04 100644 --- a/test/jest_integration/basic_auth.test.ts +++ b/test/jest_integration/basic_auth.test.ts @@ -27,6 +27,7 @@ import { } from '../constant'; import { getAuthCookie, extractAuthCookie } from '../helper/cookie'; import wreck from '@hapi/wreck'; +import { ANONYMOUS_AUTH_HEADER } from '../../common'; describe('start OpenSearch Dashboards server', () => { let root: Root; @@ -226,16 +227,11 @@ describe('start OpenSearch Dashboards server', () => { .unset(AUTHORIZATION_HEADER_NAME); expect(response.status).toEqual(302); - expect(response.header.location).toEqual('/auth/anonymous?nextUrl=%2Fapp%2Fhome'); + expect(response.header.location).toEqual('/app/login?nextUrl=%2Fapp%2Fhome'); const response2 = await osdTestServer.request.get(root, response.header.location); - expect(response2.status).toEqual(302); - expect(response2.header.location).toEqual('/app/login?nextUrl=%2Fapp%2Fhome'); - - const response3 = await osdTestServer.request.get(root, response2.header.location); - - expect(response3.status).toEqual(200); + expect(response2.status).toEqual(200); }); it('redirect for home follows login for anonymous auth disabled', async () => { @@ -264,14 +260,10 @@ describe('start OpenSearch Dashboards server', () => { .unset(AUTHORIZATION_HEADER_NAME); expect(response.status).toEqual(302); + expect(response.header.location).toEqual(expectedPath); const response2 = await osdTestServer.request.get(root, response.header.location); - expect(response2.status).toEqual(302); - expect(response2.header.location).toEqual(expectedPath); - - const response3 = await osdTestServer.request.get(root, response2.header.location); - - expect(response3.status).toEqual(200); + expect(response2.status).toEqual(200); }); }); From 5492d2a373a577075eb42c5813f6d0539e77181b Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Tue, 12 Mar 2024 00:33:54 -0400 Subject: [PATCH 08/27] Adds integration tests for anonymous auth login with basic authorization header Signed-off-by: Darshit Chanpura --- server/auth/types/basic/basic_auth.ts | 4 ++-- server/readonly/readonly_service.ts | 1 + test/jest_integration/basic_auth.test.ts | 23 ++++++++++++++++++++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/server/auth/types/basic/basic_auth.ts b/server/auth/types/basic/basic_auth.ts index 5a3e69c4e..539257bf3 100644 --- a/server/auth/types/basic/basic_auth.ts +++ b/server/auth/types/basic/basic_auth.ts @@ -28,7 +28,7 @@ import { SecurityPluginConfigType } from '../../..'; import { SecuritySessionCookie } from '../../../session/security_cookie'; import { BasicAuthRoutes } from './routes'; import { AuthenticationType } from '../authentication_type'; -import { LOGIN_PAGE_URI, ANONYMOUS_AUTH_LOGIN } from '../../../../common'; +import { LOGIN_PAGE_URI, ANONYMOUS_AUTH_HEADER } from '../../../../common'; import { composeNextUrlQueryParam } from '../../../utils/next_url'; import { AUTH_HEADER_NAME, AuthType, OPENDISTRO_SECURITY_ANONYMOUS } from '../../../../common'; @@ -130,7 +130,7 @@ export class BasicAuthentication extends AuthenticationType { request: OpenSearchDashboardsRequest ): any { if (this.config.auth.anonymous_auth_enabled && cookie.isAnonymousAuth) { - return {}; + return { authorization: ANONYMOUS_AUTH_HEADER }; } const headers: any = {}; Object.assign(headers, { authorization: cookie.credentials?.authHeaderValue }); diff --git a/server/readonly/readonly_service.ts b/server/readonly/readonly_service.ts index 6e690b5f7..337d04812 100644 --- a/server/readonly/readonly_service.ts +++ b/server/readonly/readonly_service.ts @@ -24,6 +24,7 @@ import { isPrivateTenant, LOGIN_PAGE_URI, CUSTOM_ERROR_PAGE_URI, + ANONYMOUS_AUTH_HEADER, } from '../../common'; import { SecurityClient } from '../backend/opensearch_security_client'; import { IAuthenticationType, OpenSearchAuthInfo } from '../auth/types/authentication_type'; diff --git a/test/jest_integration/basic_auth.test.ts b/test/jest_integration/basic_auth.test.ts index 9e1e7ae04..00605adc9 100644 --- a/test/jest_integration/basic_auth.test.ts +++ b/test/jest_integration/basic_auth.test.ts @@ -132,6 +132,20 @@ describe('start OpenSearch Dashboards server', () => { expect(response.status).toEqual(200); }); + it('cannot access home page as anonymous user if no credentials are supplied', async () => { + const response = await osdTestServer.request + .get(root, '/app/home#/') + .unset(AUTHORIZATION_HEADER_NAME); + expect(response.status).toEqual(302); + }); + + it('can access home page as anonymous user', async () => { + const response = await osdTestServer.request + .get(root, '/app/home#/') + .set(AUTHORIZATION_HEADER_NAME, ANONYMOUS_AUTH_HEADER); + expect(response.status).toEqual(200); + }); + it('call authinfo API as admin', async () => { const testUserCredentials = Buffer.from(ADMIN_CREDENTIALS); const response = await osdTestServer.request @@ -143,10 +157,17 @@ describe('start OpenSearch Dashboards server', () => { it('call authinfo API without credentials', async () => { const response = await osdTestServer.request .get(root, '/api/v1/auth/authinfo') - .unset('Authorization'); + .unset(AUTHORIZATION_HEADER_NAME); expect(response.status).toEqual(401); }); + it('call authinfo API as anonymous user', async () => { + const response = await osdTestServer.request + .get(root, '/api/v1/auth/authinfo') + .set(AUTHORIZATION_HEADER_NAME, ANONYMOUS_AUTH_HEADER); + expect(response.status).toEqual(200); + }); + it('call authinfo API with cookie', async () => { const authCookie = await getAuthCookie(root, ADMIN_USER, ADMIN_PASSWORD); From 9d4726194ec25e9c1fc3cdb08e10014f89843bc1 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Tue, 12 Mar 2024 00:47:56 -0400 Subject: [PATCH 09/27] Generates random password for anonymous user Signed-off-by: Darshit Chanpura --- common/index.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/common/index.ts b/common/index.ts index 6b3df18c5..c7d5288cb 100644 --- a/common/index.ts +++ b/common/index.ts @@ -13,6 +13,8 @@ * permissions and limitations under the License. */ +import { randomString } from "@hapi/cryptiles"; + export const PLUGIN_ID = 'opensearchDashboardsSecurity'; export const PLUGIN_NAME = 'security-dashboards-plugin'; @@ -34,8 +36,10 @@ export const OPENID_AUTH_LOGIN_WITH_FRAGMENT = '/auth/openid/captureUrlFragment' export const SAML_AUTH_LOGIN = '/auth/saml/login'; export const SAML_AUTH_LOGIN_WITH_FRAGMENT = '/auth/saml/captureUrlFragment'; export const ANONYMOUS_AUTH_LOGIN = '/auth/anonymous'; -export const ANONYMOUS_AUTH_HEADER = - 'Basic b3BlbmRpc3Ryb19zZWN1cml0eV9hbm9ueW1vdXM6b3BlbmRpc3Ryb19zZWN1cnV0eV9hbm9ueW1vdXM='; +const ANONYMOUS_AUTH_USER: string = 'opendistro_security_anonymous'; +const RANDOM_PASS: string = randomString(12); +const ANONYMOUS_USER_PASS: string = `${ANONYMOUS_AUTH_USER}:${RANDOM_PASS}`; +export const ANONYMOUS_AUTH_HEADER = `Basic ${Buffer.from(ANONYMOUS_USER_PASS).toString('base64')}` export const OPENID_AUTH_LOGOUT = '/auth/openid/logout'; export const SAML_AUTH_LOGOUT = '/auth/saml/logout'; From d6827017d4c7138b3a613d17f0469012a303bac1 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Tue, 12 Mar 2024 01:12:16 -0400 Subject: [PATCH 10/27] Fixes lint errors Signed-off-by: Darshit Chanpura --- common/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/index.ts b/common/index.ts index c7d5288cb..a758ca00e 100644 --- a/common/index.ts +++ b/common/index.ts @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -import { randomString } from "@hapi/cryptiles"; +import { randomString } from '@hapi/cryptiles'; export const PLUGIN_ID = 'opensearchDashboardsSecurity'; export const PLUGIN_NAME = 'security-dashboards-plugin'; @@ -39,7 +39,7 @@ export const ANONYMOUS_AUTH_LOGIN = '/auth/anonymous'; const ANONYMOUS_AUTH_USER: string = 'opendistro_security_anonymous'; const RANDOM_PASS: string = randomString(12); const ANONYMOUS_USER_PASS: string = `${ANONYMOUS_AUTH_USER}:${RANDOM_PASS}`; -export const ANONYMOUS_AUTH_HEADER = `Basic ${Buffer.from(ANONYMOUS_USER_PASS).toString('base64')}` +export const ANONYMOUS_AUTH_HEADER = `Basic ${Buffer.from(ANONYMOUS_USER_PASS).toString('base64')}`; export const OPENID_AUTH_LOGOUT = '/auth/openid/logout'; export const SAML_AUTH_LOGOUT = '/auth/saml/logout'; From ca8b4ef9133ceabaa13c557f340ceb6a0dbcfc44 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 20 Mar 2024 17:50:57 -0400 Subject: [PATCH 11/27] Adds saml auth header to differentiate saml requests Signed-off-by: Darshit Chanpura --- common/index.ts | 7 +++---- server/auth/types/authentication_type.ts | 11 +---------- server/auth/types/basic/basic_auth.ts | 4 ++-- server/auth/types/basic/routes.ts | 5 +---- server/auth/types/openid/routes.ts | 3 ++- server/auth/types/saml/routes.ts | 15 ++++++++++----- server/backend/opensearch_security_client.ts | 15 +++++++++++---- server/readonly/readonly_service.ts | 1 - test/jest_integration/basic_auth.test.ts | 7 ++----- 9 files changed, 32 insertions(+), 36 deletions(-) diff --git a/common/index.ts b/common/index.ts index a758ca00e..482ce6ecb 100644 --- a/common/index.ts +++ b/common/index.ts @@ -36,10 +36,9 @@ export const OPENID_AUTH_LOGIN_WITH_FRAGMENT = '/auth/openid/captureUrlFragment' export const SAML_AUTH_LOGIN = '/auth/saml/login'; export const SAML_AUTH_LOGIN_WITH_FRAGMENT = '/auth/saml/captureUrlFragment'; export const ANONYMOUS_AUTH_LOGIN = '/auth/anonymous'; -const ANONYMOUS_AUTH_USER: string = 'opendistro_security_anonymous'; -const RANDOM_PASS: string = randomString(12); -const ANONYMOUS_USER_PASS: string = `${ANONYMOUS_AUTH_USER}:${RANDOM_PASS}`; -export const ANONYMOUS_AUTH_HEADER = `Basic ${Buffer.from(ANONYMOUS_USER_PASS).toString('base64')}`; +export const AUTH_REQUEST_TYPE_HEADER = 'auth_request_type'; +export const SAML_AUTH_REQUEST_TYPE = 'saml'; + export const OPENID_AUTH_LOGOUT = '/auth/openid/logout'; export const SAML_AUTH_LOGOUT = '/auth/saml/logout'; diff --git a/server/auth/types/authentication_type.ts b/server/auth/types/authentication_type.ts index e58b2a5ce..b516e3942 100755 --- a/server/auth/types/authentication_type.ts +++ b/server/auth/types/authentication_type.ts @@ -31,7 +31,7 @@ import { SecuritySessionCookie } from '../../session/security_cookie'; import { SecurityClient } from '../../backend/opensearch_security_client'; import { resolveTenant, isValidTenant } from '../../multitenancy/tenant_resolver'; import { UnauthenticatedError } from '../../errors'; -import { ANONYMOUS_AUTH_HEADER, GLOBAL_TENANT_SYMBOL } from '../../../common'; +import { GLOBAL_TENANT_SYMBOL } from '../../../common'; export interface IAuthenticationType { type: string; @@ -114,15 +114,6 @@ export abstract class AuthenticationType implements IAuthenticationType { let cookie: SecuritySessionCookie | null | undefined; let authInfo: any | undefined; - // Adds a basic auth credentials headers to requests originated as anonymous user - if ( - this.config.auth.anonymous_auth_enabled && - !request.headers.hasOwnProperty('authorization') - ) { - const anonymousAuthHeaders = { authorization: ANONYMOUS_AUTH_HEADER }; - Object.assign(authHeaders, anonymousAuthHeaders); - } - // if this is an REST API call, suppose the request includes necessary auth header // see https://www.elastic.co/guide/en/opensearch-dashboards/master/using-api.html if (this.requestIncludesAuthInfo(request)) { diff --git a/server/auth/types/basic/basic_auth.ts b/server/auth/types/basic/basic_auth.ts index 539257bf3..4bea50c15 100644 --- a/server/auth/types/basic/basic_auth.ts +++ b/server/auth/types/basic/basic_auth.ts @@ -28,7 +28,7 @@ import { SecurityPluginConfigType } from '../../..'; import { SecuritySessionCookie } from '../../../session/security_cookie'; import { BasicAuthRoutes } from './routes'; import { AuthenticationType } from '../authentication_type'; -import { LOGIN_PAGE_URI, ANONYMOUS_AUTH_HEADER } from '../../../../common'; +import { LOGIN_PAGE_URI } from '../../../../common'; import { composeNextUrlQueryParam } from '../../../utils/next_url'; import { AUTH_HEADER_NAME, AuthType, OPENDISTRO_SECURITY_ANONYMOUS } from '../../../../common'; @@ -130,7 +130,7 @@ export class BasicAuthentication extends AuthenticationType { request: OpenSearchDashboardsRequest ): any { if (this.config.auth.anonymous_auth_enabled && cookie.isAnonymousAuth) { - return { authorization: ANONYMOUS_AUTH_HEADER }; + return { }; } const headers: any = {}; Object.assign(headers, { authorization: cookie.credentials?.authHeaderValue }); diff --git a/server/auth/types/basic/routes.ts b/server/auth/types/basic/routes.ts index 4370c63a3..0319ac2ea 100755 --- a/server/auth/types/basic/routes.ts +++ b/server/auth/types/basic/routes.ts @@ -23,7 +23,6 @@ import { SecurityPluginConfigType } from '../../..'; import { User } from '../../user'; import { SecurityClient } from '../../../backend/opensearch_security_client'; import { - ANONYMOUS_AUTH_HEADER, ANONYMOUS_AUTH_LOGIN, API_AUTH_LOGIN, API_AUTH_LOGOUT, @@ -187,9 +186,7 @@ export class BasicAuthRoutes { } context.security_plugin.logger.info('The Redirect Path is ' + redirectUrl); try { - user = await this.securityClient.authenticateWithHeaders(request, { - authorization: ANONYMOUS_AUTH_HEADER, - }); + user = await this.securityClient.authenticateWithHeaders(request, { }); } catch (error) { context.security_plugin.logger.error( `Failed authentication: ${error}. Redirecting to Login Page` diff --git a/server/auth/types/openid/routes.ts b/server/auth/types/openid/routes.ts index a8dc2e2b1..7309690de 100644 --- a/server/auth/types/openid/routes.ts +++ b/server/auth/types/openid/routes.ts @@ -188,7 +188,8 @@ export class OpenIdAuthRoutes { const user = await this.securityClient.authenticateWithHeader( request, this.openIdAuthConfig.authHeaderName as string, - `Bearer ${tokenResponse.idToken}` + `Bearer ${tokenResponse.idToken}`, + undefined ); // set to cookie diff --git a/server/auth/types/saml/routes.ts b/server/auth/types/saml/routes.ts index 87605d65e..dc0208057 100644 --- a/server/auth/types/saml/routes.ts +++ b/server/auth/types/saml/routes.ts @@ -20,7 +20,7 @@ import { SecurityPluginConfigType } from '../../..'; import { SecurityClient } from '../../../backend/opensearch_security_client'; import { CoreSetup } from '../../../../../../src/core/server'; import { validateNextUrl } from '../../../utils/next_url'; -import { AuthType, SAML_AUTH_LOGIN, SAML_AUTH_LOGOUT } from '../../../../common'; +import { AuthType, SAML_AUTH_LOGIN, SAML_AUTH_LOGOUT, SAML_AUTH_REQUEST_TYPE } from '../../../../common'; import { clearSplitCookies, @@ -120,6 +120,7 @@ export class SamlAuthRoutes { `${this.coreSetup.http.basePath.serverBasePath}/app/opensearch-dashboards`; redirectHash = cookie.saml?.redirectHash || false; } + console.log(requestId); if (!requestId) { return response.badRequest({ body: 'Invalid requestId', @@ -134,12 +135,14 @@ export class SamlAuthRoutes { const credentials = await this.securityClient.authToken( requestId, request.body.SAMLResponse, - undefined + undefined, + SAML_AUTH_REQUEST_TYPE ); const user = await this.securityClient.authenticateWithHeader( request, 'authorization', - credentials.authorization + credentials.authorization, + SAML_AUTH_REQUEST_TYPE ); let expiryTime = Date.now() + this.config.session.ttl; @@ -211,12 +214,14 @@ export class SamlAuthRoutes { const credentials = await this.securityClient.authToken( undefined, request.body.SAMLResponse, - acsEndpoint + acsEndpoint, + SAML_AUTH_REQUEST_TYPE ); const user = await this.securityClient.authenticateWithHeader( request, 'authorization', - credentials.authorization + credentials.authorization, + SAML_AUTH_REQUEST_TYPE ); let expiryTime = Date.now() + this.config.session.ttl; diff --git a/server/backend/opensearch_security_client.ts b/server/backend/opensearch_security_client.ts index 7897444e4..70965acc2 100755 --- a/server/backend/opensearch_security_client.ts +++ b/server/backend/opensearch_security_client.ts @@ -15,8 +15,9 @@ import { ILegacyClusterClient, OpenSearchDashboardsRequest } from '../../../../src/core/server'; import { User } from '../auth/user'; -import { getAuthInfo } from '../../public/utils/auth-info-utils'; import { TenancyConfigSettings } from '../../public/apps/configuration/panels/tenancy-config/types'; +import { AUTH_REQUEST_TYPE_HEADER, SAML_AUTH_REQUEST_TYPE } from '../../common'; + export class SecurityClient { constructor(private readonly esClient: ILegacyClusterClient) {} @@ -52,8 +53,9 @@ export class SecurityClient { request: OpenSearchDashboardsRequest, headerName: string, headerValue: string, + authRequestType: string | undefined, whitelistedHeadersAndValues: any = {}, - additionalAuthHeaders: any = {} + additionalAuthHeaders: any = {}, ): Promise { try { const credentials: any = { @@ -64,6 +66,7 @@ export class SecurityClient { if (headerValue) { headers[headerName] = headerValue; } + headers.AUTH_REQUEST_TYPE_HEADER = authRequestType; // cannot get config elasticsearch.requestHeadersWhitelist from kibana.yml file in new platfrom // meanwhile, do we really need to save all headers in cookie? @@ -183,7 +186,9 @@ export class SecurityClient { public async getSamlHeader(request: OpenSearchDashboardsRequest) { try { // response is expected to be an error - await this.esClient.asScoped(request).callAsCurrentUser('opensearch_security.authinfo'); + await this.esClient.asScoped(request).callAsCurrentUser('opensearch_security.authinfo', { + [AUTH_REQUEST_TYPE_HEADER]: SAML_AUTH_REQUEST_TYPE, + }); } catch (error: any) { // the error looks like // wwwAuthenticateDirective: @@ -221,7 +226,8 @@ export class SecurityClient { public async authToken( requestId: string | undefined, samlResponse: any, - acsEndpoint: any | undefined = undefined + acsEndpoint: any | undefined = undefined, + authRequestType: string | undefined ) { const body = { RequestId: requestId, @@ -231,6 +237,7 @@ export class SecurityClient { try { return await this.esClient.asScoped().callAsCurrentUser('opensearch_security.authtoken', { body, + [AUTH_REQUEST_TYPE_HEADER]: authRequestType, }); } catch (error: any) { console.log(error); diff --git a/server/readonly/readonly_service.ts b/server/readonly/readonly_service.ts index 337d04812..6e690b5f7 100644 --- a/server/readonly/readonly_service.ts +++ b/server/readonly/readonly_service.ts @@ -24,7 +24,6 @@ import { isPrivateTenant, LOGIN_PAGE_URI, CUSTOM_ERROR_PAGE_URI, - ANONYMOUS_AUTH_HEADER, } from '../../common'; import { SecurityClient } from '../backend/opensearch_security_client'; import { IAuthenticationType, OpenSearchAuthInfo } from '../auth/types/authentication_type'; diff --git a/test/jest_integration/basic_auth.test.ts b/test/jest_integration/basic_auth.test.ts index 00605adc9..f1f5ae16f 100644 --- a/test/jest_integration/basic_auth.test.ts +++ b/test/jest_integration/basic_auth.test.ts @@ -27,7 +27,6 @@ import { } from '../constant'; import { getAuthCookie, extractAuthCookie } from '../helper/cookie'; import wreck from '@hapi/wreck'; -import { ANONYMOUS_AUTH_HEADER } from '../../common'; describe('start OpenSearch Dashboards server', () => { let root: Root; @@ -141,8 +140,7 @@ describe('start OpenSearch Dashboards server', () => { it('can access home page as anonymous user', async () => { const response = await osdTestServer.request - .get(root, '/app/home#/') - .set(AUTHORIZATION_HEADER_NAME, ANONYMOUS_AUTH_HEADER); + .get(root, '/app/home#/'); expect(response.status).toEqual(200); }); @@ -163,8 +161,7 @@ describe('start OpenSearch Dashboards server', () => { it('call authinfo API as anonymous user', async () => { const response = await osdTestServer.request - .get(root, '/api/v1/auth/authinfo') - .set(AUTHORIZATION_HEADER_NAME, ANONYMOUS_AUTH_HEADER); + .get(root, '/api/v1/auth/authinfo'); expect(response.status).toEqual(200); }); From f63bbf2e58c6d3abea2fc103dd4733e0ad4d9822 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 20 Mar 2024 21:36:07 -0400 Subject: [PATCH 12/27] Fixes linter errors Signed-off-by: Darshit Chanpura --- common/index.ts | 1 - server/auth/types/basic/basic_auth.ts | 2 +- server/auth/types/basic/routes.ts | 2 +- server/auth/types/saml/routes.ts | 7 ++++++- server/backend/opensearch_security_client.ts | 3 +-- test/jest_integration/basic_auth.test.ts | 6 ++---- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/common/index.ts b/common/index.ts index 482ce6ecb..f08bb0fa0 100644 --- a/common/index.ts +++ b/common/index.ts @@ -39,7 +39,6 @@ export const ANONYMOUS_AUTH_LOGIN = '/auth/anonymous'; export const AUTH_REQUEST_TYPE_HEADER = 'auth_request_type'; export const SAML_AUTH_REQUEST_TYPE = 'saml'; - export const OPENID_AUTH_LOGOUT = '/auth/openid/logout'; export const SAML_AUTH_LOGOUT = '/auth/saml/logout'; export const ANONYMOUS_AUTH_LOGOUT = '/auth/anonymous/logout'; diff --git a/server/auth/types/basic/basic_auth.ts b/server/auth/types/basic/basic_auth.ts index 4bea50c15..2838047f5 100644 --- a/server/auth/types/basic/basic_auth.ts +++ b/server/auth/types/basic/basic_auth.ts @@ -130,7 +130,7 @@ export class BasicAuthentication extends AuthenticationType { request: OpenSearchDashboardsRequest ): any { if (this.config.auth.anonymous_auth_enabled && cookie.isAnonymousAuth) { - return { }; + return {}; } const headers: any = {}; Object.assign(headers, { authorization: cookie.credentials?.authHeaderValue }); diff --git a/server/auth/types/basic/routes.ts b/server/auth/types/basic/routes.ts index 0319ac2ea..bae3e338c 100755 --- a/server/auth/types/basic/routes.ts +++ b/server/auth/types/basic/routes.ts @@ -186,7 +186,7 @@ export class BasicAuthRoutes { } context.security_plugin.logger.info('The Redirect Path is ' + redirectUrl); try { - user = await this.securityClient.authenticateWithHeaders(request, { }); + user = await this.securityClient.authenticateWithHeaders(request, {}); } catch (error) { context.security_plugin.logger.error( `Failed authentication: ${error}. Redirecting to Login Page` diff --git a/server/auth/types/saml/routes.ts b/server/auth/types/saml/routes.ts index dc0208057..5e3016765 100644 --- a/server/auth/types/saml/routes.ts +++ b/server/auth/types/saml/routes.ts @@ -20,7 +20,12 @@ import { SecurityPluginConfigType } from '../../..'; import { SecurityClient } from '../../../backend/opensearch_security_client'; import { CoreSetup } from '../../../../../../src/core/server'; import { validateNextUrl } from '../../../utils/next_url'; -import { AuthType, SAML_AUTH_LOGIN, SAML_AUTH_LOGOUT, SAML_AUTH_REQUEST_TYPE } from '../../../../common'; +import { + AuthType, + SAML_AUTH_LOGIN, + SAML_AUTH_LOGOUT, + SAML_AUTH_REQUEST_TYPE, +} from '../../../../common'; import { clearSplitCookies, diff --git a/server/backend/opensearch_security_client.ts b/server/backend/opensearch_security_client.ts index 70965acc2..6e4f356fb 100755 --- a/server/backend/opensearch_security_client.ts +++ b/server/backend/opensearch_security_client.ts @@ -18,7 +18,6 @@ import { User } from '../auth/user'; import { TenancyConfigSettings } from '../../public/apps/configuration/panels/tenancy-config/types'; import { AUTH_REQUEST_TYPE_HEADER, SAML_AUTH_REQUEST_TYPE } from '../../common'; - export class SecurityClient { constructor(private readonly esClient: ILegacyClusterClient) {} @@ -55,7 +54,7 @@ export class SecurityClient { headerValue: string, authRequestType: string | undefined, whitelistedHeadersAndValues: any = {}, - additionalAuthHeaders: any = {}, + additionalAuthHeaders: any = {} ): Promise { try { const credentials: any = { diff --git a/test/jest_integration/basic_auth.test.ts b/test/jest_integration/basic_auth.test.ts index f1f5ae16f..24c1021a8 100644 --- a/test/jest_integration/basic_auth.test.ts +++ b/test/jest_integration/basic_auth.test.ts @@ -139,8 +139,7 @@ describe('start OpenSearch Dashboards server', () => { }); it('can access home page as anonymous user', async () => { - const response = await osdTestServer.request - .get(root, '/app/home#/'); + const response = await osdTestServer.request.get(root, '/app/home#/'); expect(response.status).toEqual(200); }); @@ -160,8 +159,7 @@ describe('start OpenSearch Dashboards server', () => { }); it('call authinfo API as anonymous user', async () => { - const response = await osdTestServer.request - .get(root, '/api/v1/auth/authinfo'); + const response = await osdTestServer.request.get(root, '/api/v1/auth/authinfo'); expect(response.status).toEqual(200); }); From 573c715bfc6e25b73d27bbec7d266ea78b49bce0 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 20 Mar 2024 23:31:35 -0400 Subject: [PATCH 13/27] Fixes basic auth tests Signed-off-by: Darshit Chanpura --- test/jest_integration/basic_auth.test.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/test/jest_integration/basic_auth.test.ts b/test/jest_integration/basic_auth.test.ts index 24c1021a8..39ac79618 100644 --- a/test/jest_integration/basic_auth.test.ts +++ b/test/jest_integration/basic_auth.test.ts @@ -131,20 +131,7 @@ describe('start OpenSearch Dashboards server', () => { expect(response.status).toEqual(200); }); - it('cannot access home page as anonymous user if no credentials are supplied', async () => { - const response = await osdTestServer.request - .get(root, '/app/home#/') - .unset(AUTHORIZATION_HEADER_NAME); - expect(response.status).toEqual(302); - }); - - it('can access home page as anonymous user', async () => { - const response = await osdTestServer.request.get(root, '/app/home#/'); - expect(response.status).toEqual(200); - }); - it('call authinfo API as admin', async () => { - const testUserCredentials = Buffer.from(ADMIN_CREDENTIALS); const response = await osdTestServer.request .get(root, '/api/v1/auth/authinfo') .set(AUTHORIZATION_HEADER_NAME, ADMIN_CREDENTIALS); @@ -158,11 +145,6 @@ describe('start OpenSearch Dashboards server', () => { expect(response.status).toEqual(401); }); - it('call authinfo API as anonymous user', async () => { - const response = await osdTestServer.request.get(root, '/api/v1/auth/authinfo'); - expect(response.status).toEqual(200); - }); - it('call authinfo API with cookie', async () => { const authCookie = await getAuthCookie(root, ADMIN_USER, ADMIN_PASSWORD); From b9b9911efacad51807202c6b64144b1ea81ccfbf Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 21 Mar 2024 00:11:58 -0400 Subject: [PATCH 14/27] Removes console loggers Signed-off-by: Darshit Chanpura --- common/index.ts | 3 +-- server/auth/types/authentication_type.ts | 1 - server/auth/types/openid/routes.ts | 3 ++- server/auth/types/saml/routes.ts | 1 - 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/common/index.ts b/common/index.ts index f08bb0fa0..0773f4691 100644 --- a/common/index.ts +++ b/common/index.ts @@ -13,8 +13,6 @@ * permissions and limitations under the License. */ -import { randomString } from '@hapi/cryptiles'; - export const PLUGIN_ID = 'opensearchDashboardsSecurity'; export const PLUGIN_NAME = 'security-dashboards-plugin'; @@ -38,6 +36,7 @@ export const SAML_AUTH_LOGIN_WITH_FRAGMENT = '/auth/saml/captureUrlFragment'; export const ANONYMOUS_AUTH_LOGIN = '/auth/anonymous'; export const AUTH_REQUEST_TYPE_HEADER = 'auth_request_type'; export const SAML_AUTH_REQUEST_TYPE = 'saml'; +export const OPEN_ID_AUTH_REQUEST_TYPE = 'openid'; export const OPENID_AUTH_LOGOUT = '/auth/openid/logout'; export const SAML_AUTH_LOGOUT = '/auth/saml/logout'; diff --git a/server/auth/types/authentication_type.ts b/server/auth/types/authentication_type.ts index b516e3942..8bde376ac 100755 --- a/server/auth/types/authentication_type.ts +++ b/server/auth/types/authentication_type.ts @@ -113,7 +113,6 @@ export abstract class AuthenticationType implements IAuthenticationType { const authHeaders = {}; let cookie: SecuritySessionCookie | null | undefined; let authInfo: any | undefined; - // if this is an REST API call, suppose the request includes necessary auth header // see https://www.elastic.co/guide/en/opensearch-dashboards/master/using-api.html if (this.requestIncludesAuthInfo(request)) { diff --git a/server/auth/types/openid/routes.ts b/server/auth/types/openid/routes.ts index 7309690de..048e7bad1 100644 --- a/server/auth/types/openid/routes.ts +++ b/server/auth/types/openid/routes.ts @@ -44,6 +44,7 @@ import { AUTH_RESPONSE_TYPE, OPENID_AUTH_LOGOUT, LOGIN_PAGE_URI, + OPEN_ID_AUTH_REQUEST_TYPE, } from '../../../../common'; import { @@ -189,7 +190,7 @@ export class OpenIdAuthRoutes { request, this.openIdAuthConfig.authHeaderName as string, `Bearer ${tokenResponse.idToken}`, - undefined + OPEN_ID_AUTH_REQUEST_TYPE ); // set to cookie diff --git a/server/auth/types/saml/routes.ts b/server/auth/types/saml/routes.ts index 5e3016765..944e8f67d 100644 --- a/server/auth/types/saml/routes.ts +++ b/server/auth/types/saml/routes.ts @@ -125,7 +125,6 @@ export class SamlAuthRoutes { `${this.coreSetup.http.basePath.serverBasePath}/app/opensearch-dashboards`; redirectHash = cookie.saml?.redirectHash || false; } - console.log(requestId); if (!requestId) { return response.badRequest({ body: 'Invalid requestId', From 366f413f6141f551bd75f494598e4c9a2c1dce50 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 27 Mar 2024 20:15:35 -0400 Subject: [PATCH 15/27] Fixes lint error Signed-off-by: Darshit Chanpura --- server/auth/types/multiple/multi_auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/auth/types/multiple/multi_auth.ts b/server/auth/types/multiple/multi_auth.ts index 7f273183b..b00b3d154 100644 --- a/server/auth/types/multiple/multi_auth.ts +++ b/server/auth/types/multiple/multi_auth.ts @@ -25,7 +25,7 @@ import { import { OpenSearchDashboardsResponse } from '../../../../../../src/core/server/http/router'; import { SecurityPluginConfigType } from '../../..'; import { AuthenticationType } from '../authentication_type'; -import { ANONYMOUS_AUTH_LOGIN, AuthType, LOGIN_PAGE_URI } from '../../../../common'; +import { AuthType, LOGIN_PAGE_URI } from '../../../../common'; import { composeNextUrlQueryParam } from '../../../utils/next_url'; import { MultiAuthRoutes } from './routes'; import { SecuritySessionCookie } from '../../../session/security_cookie'; From 43179014c21669ff750ff33872a1ad2950d7ec55 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Mon, 1 Apr 2024 16:12:11 -0400 Subject: [PATCH 16/27] Addresses feedback Signed-off-by: Darshit Chanpura --- public/apps/login/test/login-page.test.tsx | 2 +- server/auth/types/saml/routes.ts | 20 ++++++++++---------- server/backend/opensearch_security_client.ts | 19 +++++++++++++------ 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/public/apps/login/test/login-page.test.tsx b/public/apps/login/test/login-page.test.tsx index b03debbbb..4d7c80cf7 100644 --- a/public/apps/login/test/login-page.test.tsx +++ b/public/apps/login/test/login-page.test.tsx @@ -209,7 +209,7 @@ describe('Login page', () => { const config: ClientConfigType = { ui: configUI, auth: { - type: [AuthType.BASIC, 'openid', AuthType.SAML], + type: [AuthType.BASIC, AuthType.OPEN_ID, AuthType.SAML], logout_url: API_AUTH_LOGOUT, }, }; diff --git a/server/auth/types/saml/routes.ts b/server/auth/types/saml/routes.ts index 944e8f67d..9d42c6506 100644 --- a/server/auth/types/saml/routes.ts +++ b/server/auth/types/saml/routes.ts @@ -136,12 +136,12 @@ export class SamlAuthRoutes { } try { - const credentials = await this.securityClient.authToken( + const credentials = await this.securityClient.authToken({ requestId, - request.body.SAMLResponse, - undefined, - SAML_AUTH_REQUEST_TYPE - ); + samlResponse: request.body.SAMLResponse, + acsEndpoint: undefined, + authRequestType: SAML_AUTH_REQUEST_TYPE, + }); const user = await this.securityClient.authenticateWithHeader( request, 'authorization', @@ -215,12 +215,12 @@ export class SamlAuthRoutes { async (context, request, response) => { const acsEndpoint = `${this.coreSetup.http.basePath.serverBasePath}/_opendistro/_security/saml/acs/idpinitiated`; try { - const credentials = await this.securityClient.authToken( - undefined, - request.body.SAMLResponse, + const credentials = await this.securityClient.authToken({ + requestId: undefined, + samlResponse: request.body.SAMLResponse, acsEndpoint, - SAML_AUTH_REQUEST_TYPE - ); + authRequestType: SAML_AUTH_REQUEST_TYPE, + }); const user = await this.securityClient.authenticateWithHeader( request, 'authorization', diff --git a/server/backend/opensearch_security_client.ts b/server/backend/opensearch_security_client.ts index 6e4f356fb..f77af4958 100755 --- a/server/backend/opensearch_security_client.ts +++ b/server/backend/opensearch_security_client.ts @@ -222,12 +222,12 @@ export class SecurityClient { throw new Error(`Invalid SAML configuration.`); } - public async authToken( - requestId: string | undefined, - samlResponse: any, - acsEndpoint: any | undefined = undefined, - authRequestType: string | undefined - ) { + public async authToken({ + requestId, + samlResponse, + acsEndpoint = undefined, + authRequestType, + }: AuthTokenParams) { const body = { RequestId: requestId, SAMLResponse: samlResponse, @@ -244,3 +244,10 @@ export class SecurityClient { } } } + +interface AuthTokenParams { + requestId?: string; + samlResponse: any; + acsEndpoint?: any; + authRequestType?: string; +} From 2724e031a0f9fe0c1aca4d6bbd5c5cdb6118604c Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Tue, 2 Apr 2024 11:32:14 -0400 Subject: [PATCH 17/27] Resolves #1840 Signed-off-by: Darshit Chanpura --- server/backend/opensearch_security_client.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/backend/opensearch_security_client.ts b/server/backend/opensearch_security_client.ts index f77af4958..109fad52c 100755 --- a/server/backend/opensearch_security_client.ts +++ b/server/backend/opensearch_security_client.ts @@ -16,7 +16,7 @@ import { ILegacyClusterClient, OpenSearchDashboardsRequest } from '../../../../src/core/server'; import { User } from '../auth/user'; import { TenancyConfigSettings } from '../../public/apps/configuration/panels/tenancy-config/types'; -import { AUTH_REQUEST_TYPE_HEADER, SAML_AUTH_REQUEST_TYPE } from '../../common'; +import { AUTH_REQUEST_TYPE_HEADER, AuthType, SAML_AUTH_REQUEST_TYPE } from '../../common'; export class SecurityClient { constructor(private readonly esClient: ILegacyClusterClient) {} @@ -32,6 +32,7 @@ export class SecurityClient { headers: { authorization: `Basic ${authHeader}`, }, + AUTH_REQUEST_TYPE_HEADER: AuthType.BASIC, }); return { username: credentials.username, From 932fd5b94ac4a4f2f9242dfda13e166212ab4c9b Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Tue, 2 Apr 2024 11:33:46 -0400 Subject: [PATCH 18/27] Replace magic value with constant Signed-off-by: Darshit Chanpura --- public/apps/login/test/login-page.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/apps/login/test/login-page.test.tsx b/public/apps/login/test/login-page.test.tsx index 4d7c80cf7..d764d56fa 100644 --- a/public/apps/login/test/login-page.test.tsx +++ b/public/apps/login/test/login-page.test.tsx @@ -223,7 +223,7 @@ describe('Login page', () => { const config: ClientConfigType = { ui: configUI, auth: { - type: [AuthType.BASIC, 'openid', AuthType.SAML], + type: [AuthType.BASIC, AuthType.OPEN_ID, AuthType.SAML], logout_url: API_AUTH_LOGOUT, anonymous_auth_enabled: true, }, From 1f1ecfa5b151d31dcf54e53819d99051d4d3410c Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Mon, 8 Apr 2024 17:18:09 -0400 Subject: [PATCH 19/27] Renames query param and removes unused variables Signed-off-by: Darshit Chanpura --- common/index.ts | 4 +-- server/auth/types/authentication_type.ts | 8 +++-- server/auth/types/basic/routes.ts | 2 +- server/auth/types/openid/routes.ts | 3 +- server/auth/types/saml/routes.ts | 17 ++++------- server/backend/opensearch_security_client.ts | 21 ++++++++----- server/backend/opensearch_security_plugin.ts | 31 +++++++++++++++++++- server/readonly/readonly_service.ts | 3 +- test/jest_integration/basic_auth.test.ts | 4 ++- 9 files changed, 63 insertions(+), 30 deletions(-) diff --git a/common/index.ts b/common/index.ts index 0773f4691..1a0eb3ff5 100644 --- a/common/index.ts +++ b/common/index.ts @@ -34,9 +34,7 @@ export const OPENID_AUTH_LOGIN_WITH_FRAGMENT = '/auth/openid/captureUrlFragment' export const SAML_AUTH_LOGIN = '/auth/saml/login'; export const SAML_AUTH_LOGIN_WITH_FRAGMENT = '/auth/saml/captureUrlFragment'; export const ANONYMOUS_AUTH_LOGIN = '/auth/anonymous'; -export const AUTH_REQUEST_TYPE_HEADER = 'auth_request_type'; -export const SAML_AUTH_REQUEST_TYPE = 'saml'; -export const OPEN_ID_AUTH_REQUEST_TYPE = 'openid'; +export const AUTH_TYPE_PARAM = 'auth_type'; export const OPENID_AUTH_LOGOUT = '/auth/openid/logout'; export const SAML_AUTH_LOGOUT = '/auth/saml/logout'; diff --git a/server/auth/types/authentication_type.ts b/server/auth/types/authentication_type.ts index 8bde376ac..f504656d0 100755 --- a/server/auth/types/authentication_type.ts +++ b/server/auth/types/authentication_type.ts @@ -119,7 +119,7 @@ export abstract class AuthenticationType implements IAuthenticationType { try { const additionalAuthHeader = await this.getAdditionalAuthHeader(request); Object.assign(authHeaders, additionalAuthHeader); - authInfo = await this.securityClient.authinfo(request, additionalAuthHeader); + authInfo = await this.securityClient.authinfo(request, '', additionalAuthHeader); cookie = this.getCookie(request, authInfo); // set tenant from cookie if exist @@ -210,7 +210,8 @@ export abstract class AuthenticationType implements IAuthenticationType { } } if (!authInfo) { - authInfo = await this.securityClient.authinfo(request, authHeaders); + const authRequestType = cookie.isAnonymousAuth ? 'anonymous' : cookie.authType; + authInfo = await this.securityClient.authinfo(request, authRequestType, authHeaders); } authState.authInfo = authInfo; @@ -243,9 +244,10 @@ export abstract class AuthenticationType implements IAuthenticationType { authHeader: any, authInfo: any ): Promise { + const authType = cookie.isAnonymousAuth ? 'anonymous' : cookie.authType; if (!authInfo) { try { - authInfo = await this.securityClient.authinfo(request, authHeader); + authInfo = await this.securityClient.authinfo(request, authType, authHeader); } catch (error: any) { throw new UnauthenticatedError(error); } diff --git a/server/auth/types/basic/routes.ts b/server/auth/types/basic/routes.ts index bae3e338c..3f622b4c2 100755 --- a/server/auth/types/basic/routes.ts +++ b/server/auth/types/basic/routes.ts @@ -186,7 +186,7 @@ export class BasicAuthRoutes { } context.security_plugin.logger.info('The Redirect Path is ' + redirectUrl); try { - user = await this.securityClient.authenticateWithHeaders(request, {}); + user = await this.securityClient.authenticateWithHeaders(request, 'anonymous', {}); } catch (error) { context.security_plugin.logger.error( `Failed authentication: ${error}. Redirecting to Login Page` diff --git a/server/auth/types/openid/routes.ts b/server/auth/types/openid/routes.ts index 862a51bb3..9b87e81ea 100644 --- a/server/auth/types/openid/routes.ts +++ b/server/auth/types/openid/routes.ts @@ -43,7 +43,6 @@ import { AUTH_GRANT_TYPE, AUTH_RESPONSE_TYPE, OPENID_AUTH_LOGOUT, - OPEN_ID_AUTH_REQUEST_TYPE, } from '../../../../common'; import { @@ -189,7 +188,7 @@ export class OpenIdAuthRoutes { request, this.openIdAuthConfig.authHeaderName as string, `Bearer ${tokenResponse.idToken}`, - OPEN_ID_AUTH_REQUEST_TYPE + AuthType.OPEN_ID ); // set to cookie diff --git a/server/auth/types/saml/routes.ts b/server/auth/types/saml/routes.ts index 9d42c6506..7e717cf0a 100644 --- a/server/auth/types/saml/routes.ts +++ b/server/auth/types/saml/routes.ts @@ -20,12 +20,7 @@ import { SecurityPluginConfigType } from '../../..'; import { SecurityClient } from '../../../backend/opensearch_security_client'; import { CoreSetup } from '../../../../../../src/core/server'; import { validateNextUrl } from '../../../utils/next_url'; -import { - AuthType, - SAML_AUTH_LOGIN, - SAML_AUTH_LOGOUT, - SAML_AUTH_REQUEST_TYPE, -} from '../../../../common'; +import { AuthType, SAML_AUTH_LOGIN, SAML_AUTH_LOGOUT } from '../../../../common'; import { clearSplitCookies, @@ -140,13 +135,13 @@ export class SamlAuthRoutes { requestId, samlResponse: request.body.SAMLResponse, acsEndpoint: undefined, - authRequestType: SAML_AUTH_REQUEST_TYPE, + authRequestType: AuthType.SAML, }); const user = await this.securityClient.authenticateWithHeader( request, 'authorization', credentials.authorization, - SAML_AUTH_REQUEST_TYPE + AuthType.SAML ); let expiryTime = Date.now() + this.config.session.ttl; @@ -219,13 +214,13 @@ export class SamlAuthRoutes { requestId: undefined, samlResponse: request.body.SAMLResponse, acsEndpoint, - authRequestType: SAML_AUTH_REQUEST_TYPE, + authRequestType: AuthType.SAML, }); const user = await this.securityClient.authenticateWithHeader( request, 'authorization', credentials.authorization, - SAML_AUTH_REQUEST_TYPE + AuthType.SAML ); let expiryTime = Date.now() + this.config.session.ttl; @@ -387,7 +382,7 @@ export class SamlAuthRoutes { }, async (context, request, response) => { try { - const authInfo = await this.securityClient.authinfo(request); + const authInfo = await this.securityClient.authinfo(request, AuthType.SAML); await clearSplitCookies( request, this.getExtraAuthStorageOptions(context.security_plugin.logger) diff --git a/server/backend/opensearch_security_client.ts b/server/backend/opensearch_security_client.ts index 109fad52c..1aa50004b 100755 --- a/server/backend/opensearch_security_client.ts +++ b/server/backend/opensearch_security_client.ts @@ -16,7 +16,7 @@ import { ILegacyClusterClient, OpenSearchDashboardsRequest } from '../../../../src/core/server'; import { User } from '../auth/user'; import { TenancyConfigSettings } from '../../public/apps/configuration/panels/tenancy-config/types'; -import { AUTH_REQUEST_TYPE_HEADER, AuthType, SAML_AUTH_REQUEST_TYPE } from '../../common'; +import { AUTH_TYPE_PARAM, AuthType } from '../../common'; export class SecurityClient { constructor(private readonly esClient: ILegacyClusterClient) {} @@ -32,7 +32,7 @@ export class SecurityClient { headers: { authorization: `Basic ${authHeader}`, }, - AUTH_REQUEST_TYPE_HEADER: AuthType.BASIC, + [AUTH_TYPE_PARAM]: AuthType.BASIC, }); return { username: credentials.username, @@ -53,7 +53,7 @@ export class SecurityClient { request: OpenSearchDashboardsRequest, headerName: string, headerValue: string, - authRequestType: string | undefined, + authRequestType: string, whitelistedHeadersAndValues: any = {}, additionalAuthHeaders: any = {} ): Promise { @@ -66,13 +66,13 @@ export class SecurityClient { if (headerValue) { headers[headerName] = headerValue; } - headers.AUTH_REQUEST_TYPE_HEADER = authRequestType; // cannot get config elasticsearch.requestHeadersWhitelist from kibana.yml file in new platfrom // meanwhile, do we really need to save all headers in cookie? const esResponse = await this.esClient .asScoped(request) .callAsCurrentUser('opensearch_security.authinfo', { + [AUTH_TYPE_PARAM]: authRequestType, headers, }); return { @@ -90,12 +90,14 @@ export class SecurityClient { public async authenticateWithHeaders( request: OpenSearchDashboardsRequest, + authRequestType: string, additionalAuthHeaders: any = {} ): Promise { try { const esResponse = await this.esClient .asScoped(request) .callAsCurrentUser('opensearch_security.authinfo', { + [AUTH_TYPE_PARAM]: authRequestType, headers: additionalAuthHeaders, }); return { @@ -110,11 +112,16 @@ export class SecurityClient { } } - public async authinfo(request: OpenSearchDashboardsRequest, headers: any = {}) { + public async authinfo( + request: OpenSearchDashboardsRequest, + authRequestType: string = '', + headers: any = {} + ) { try { return await this.esClient .asScoped(request) .callAsCurrentUser('opensearch_security.authinfo', { + [AUTH_TYPE_PARAM]: authRequestType, headers, }); } catch (error: any) { @@ -187,7 +194,7 @@ export class SecurityClient { try { // response is expected to be an error await this.esClient.asScoped(request).callAsCurrentUser('opensearch_security.authinfo', { - [AUTH_REQUEST_TYPE_HEADER]: SAML_AUTH_REQUEST_TYPE, + [AUTH_TYPE_PARAM]: AuthType.SAML, }); } catch (error: any) { // the error looks like @@ -237,7 +244,7 @@ export class SecurityClient { try { return await this.esClient.asScoped().callAsCurrentUser('opensearch_security.authtoken', { body, - [AUTH_REQUEST_TYPE_HEADER]: authRequestType, + [AUTH_TYPE_PARAM]: authRequestType, }); } catch (error: any) { console.log(error); diff --git a/server/backend/opensearch_security_plugin.ts b/server/backend/opensearch_security_plugin.ts index 33b72cc0d..ab5264835 100644 --- a/server/backend/opensearch_security_plugin.ts +++ b/server/backend/opensearch_security_plugin.ts @@ -13,6 +13,8 @@ * permissions and limitations under the License. */ +import { AUTH_TYPE_PARAM } from '../../common'; + // eslint-disable-next-line import/no-default-export export default function (Client: any, config: any, components: any) { const ca = components.clientAction.factory; @@ -26,7 +28,34 @@ export default function (Client: any, config: any, components: any) { */ Client.prototype.opensearch_security.prototype.authinfo = ca({ url: { - fmt: '/_plugins/_security/authinfo', + fmt: `/_plugins/_security/authinfo`, + opt: { + [AUTH_TYPE_PARAM]: { + type: 'string', + required: false, + }, + }, + template: (requestObj) => { + const obj = requestObj || (requestObj = {}); + let __p = '/_plugins/_security/authinfo'; + const __q = []; // Array to hold query string components + + // Iterate over the object properties to construct the query string + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + // Ensure the value is a string to avoid URL encoding issues + const value = String(obj[key]); + __q.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); + } + } + + // If there are query parameters, append them to the URL + if (__q.length > 0) { + __p += '?' + __q.join('&'); + } + + return __p; + }, }, }); diff --git a/server/readonly/readonly_service.ts b/server/readonly/readonly_service.ts index 6e690b5f7..622d8ab8d 100644 --- a/server/readonly/readonly_service.ts +++ b/server/readonly/readonly_service.ts @@ -104,7 +104,8 @@ export class ReadonlyService extends BaseReadonlyService { return false; } - const authInfo = await this.securityClient.authinfo(request, headers); + const authType = cookie ? (cookie.isAnonymousAuth ? 'anonymous' : cookie.authType) : ''; + const authInfo = await this.securityClient.authinfo(request, authType, headers); if (!authInfo.user_requested_tenant && cookie) { authInfo.user_requested_tenant = cookie.tenant; diff --git a/test/jest_integration/basic_auth.test.ts b/test/jest_integration/basic_auth.test.ts index 39ac79618..421b76a83 100644 --- a/test/jest_integration/basic_auth.test.ts +++ b/test/jest_integration/basic_auth.test.ts @@ -208,7 +208,9 @@ describe('start OpenSearch Dashboards server', () => { }); it('enforce authentication on api/status route', async () => { - const response = await osdTestServer.request.get(root, '/api/status'); + const response = await osdTestServer.request + .get(root, '/api/status') + .unset(AUTHORIZATION_HEADER_NAME); expect(response.status).toEqual(401); }); From 2a1289da186ba85eecbda07f2aaaaf663ed4cada Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Tue, 9 Apr 2024 14:26:09 -0400 Subject: [PATCH 20/27] Uses enum instead of magic constant Signed-off-by: Darshit Chanpura --- server/auth/types/authentication_type.ts | 6 +++--- server/auth/types/basic/routes.ts | 6 +++++- server/readonly/readonly_service.ts | 7 ++++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/server/auth/types/authentication_type.ts b/server/auth/types/authentication_type.ts index f504656d0..4bc013b21 100755 --- a/server/auth/types/authentication_type.ts +++ b/server/auth/types/authentication_type.ts @@ -31,7 +31,7 @@ import { SecuritySessionCookie } from '../../session/security_cookie'; import { SecurityClient } from '../../backend/opensearch_security_client'; import { resolveTenant, isValidTenant } from '../../multitenancy/tenant_resolver'; import { UnauthenticatedError } from '../../errors'; -import { GLOBAL_TENANT_SYMBOL } from '../../../common'; +import { AuthType, GLOBAL_TENANT_SYMBOL } from '../../../common'; export interface IAuthenticationType { type: string; @@ -210,7 +210,7 @@ export abstract class AuthenticationType implements IAuthenticationType { } } if (!authInfo) { - const authRequestType = cookie.isAnonymousAuth ? 'anonymous' : cookie.authType; + const authRequestType = cookie.isAnonymousAuth ? AuthType.ANONYMOUS : cookie.authType; authInfo = await this.securityClient.authinfo(request, authRequestType, authHeaders); } authState.authInfo = authInfo; @@ -244,7 +244,7 @@ export abstract class AuthenticationType implements IAuthenticationType { authHeader: any, authInfo: any ): Promise { - const authType = cookie.isAnonymousAuth ? 'anonymous' : cookie.authType; + const authType = cookie.isAnonymousAuth ? AuthType.ANONYMOUS : cookie.authType; if (!authInfo) { try { authInfo = await this.securityClient.authinfo(request, authType, authHeader); diff --git a/server/auth/types/basic/routes.ts b/server/auth/types/basic/routes.ts index 3f622b4c2..23e3d71c6 100755 --- a/server/auth/types/basic/routes.ts +++ b/server/auth/types/basic/routes.ts @@ -186,7 +186,11 @@ export class BasicAuthRoutes { } context.security_plugin.logger.info('The Redirect Path is ' + redirectUrl); try { - user = await this.securityClient.authenticateWithHeaders(request, 'anonymous', {}); + user = await this.securityClient.authenticateWithHeaders( + request, + AuthType.ANONYMOUS, + {} + ); } catch (error) { context.security_plugin.logger.error( `Failed authentication: ${error}. Redirecting to Login Page` diff --git a/server/readonly/readonly_service.ts b/server/readonly/readonly_service.ts index 622d8ab8d..901594812 100644 --- a/server/readonly/readonly_service.ts +++ b/server/readonly/readonly_service.ts @@ -24,6 +24,7 @@ import { isPrivateTenant, LOGIN_PAGE_URI, CUSTOM_ERROR_PAGE_URI, + AuthType, } from '../../common'; import { SecurityClient } from '../backend/opensearch_security_client'; import { IAuthenticationType, OpenSearchAuthInfo } from '../auth/types/authentication_type'; @@ -104,7 +105,11 @@ export class ReadonlyService extends BaseReadonlyService { return false; } - const authType = cookie ? (cookie.isAnonymousAuth ? 'anonymous' : cookie.authType) : ''; + const authType = cookie + ? cookie.isAnonymousAuth + ? AuthType.ANONYMOUS + : cookie.authType + : ''; const authInfo = await this.securityClient.authinfo(request, authType, headers); if (!authInfo.user_requested_tenant && cookie) { From 35a7f7e76a31a0d2d5174a6ad81ff81c1f4208df Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Tue, 9 Apr 2024 16:30:43 -0400 Subject: [PATCH 21/27] Extracts template function to a separate util file Signed-off-by: Darshit Chanpura --- server/backend/opensearch_security_plugin.ts | 23 ++-------------- server/backend/test/utils.test.ts | 29 ++++++++++++++++++++ server/backend/utils.ts | 27 ++++++++++++++++++ 3 files changed, 58 insertions(+), 21 deletions(-) create mode 100644 server/backend/test/utils.test.ts create mode 100644 server/backend/utils.ts diff --git a/server/backend/opensearch_security_plugin.ts b/server/backend/opensearch_security_plugin.ts index ab5264835..9894a7efc 100644 --- a/server/backend/opensearch_security_plugin.ts +++ b/server/backend/opensearch_security_plugin.ts @@ -14,6 +14,7 @@ */ import { AUTH_TYPE_PARAM } from '../../common'; +import { addQueryParamsToURLIfAny } from './utils'; // eslint-disable-next-line import/no-default-export export default function (Client: any, config: any, components: any) { @@ -35,27 +36,7 @@ export default function (Client: any, config: any, components: any) { required: false, }, }, - template: (requestObj) => { - const obj = requestObj || (requestObj = {}); - let __p = '/_plugins/_security/authinfo'; - const __q = []; // Array to hold query string components - - // Iterate over the object properties to construct the query string - for (const key in obj) { - if (obj.hasOwnProperty(key)) { - // Ensure the value is a string to avoid URL encoding issues - const value = String(obj[key]); - __q.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); - } - } - - // If there are query parameters, append them to the URL - if (__q.length > 0) { - __p += '?' + __q.join('&'); - } - - return __p; - }, + template: (params) => addQueryParamsToURLIfAny(params, '/_plugins/_security/authinfo'), }, }); diff --git a/server/backend/test/utils.test.ts b/server/backend/test/utils.test.ts new file mode 100644 index 000000000..1942b9197 --- /dev/null +++ b/server/backend/test/utils.test.ts @@ -0,0 +1,29 @@ +/* + * Copyright OpenSearch Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import { addQueryParamsToURLIfAny } from '../utils'; + +describe('Test backend utils', () => { + it('checks isAnonymousPage', () => { + const url = '/_plugins/_security/abc'; + + const requestObject = { foo: 'bar', por: 'que' }; + + const expected = url + '?foo=bar&por=que'; + const actual = addQueryParamsToURLIfAny(requestObject, url); + + expect(actual).toEqual(expected); + }); +}); diff --git a/server/backend/utils.ts b/server/backend/utils.ts new file mode 100644 index 000000000..8b0516bf0 --- /dev/null +++ b/server/backend/utils.ts @@ -0,0 +1,27 @@ +/* + * Copyright OpenSearch Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +export function addQueryParamsToURLIfAny(params: any = {}, url: string) { + // fetches all String components of query params and joins them by `&` + const queryParams = Object.entries(params) + .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`) + .join('&'); + + if (queryParams) { + url += `?${queryParams}`; + } + + return url; +} From 77608a009ab44714c8527caaaf6de7cb65cfb88f Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Tue, 9 Apr 2024 16:32:36 -0400 Subject: [PATCH 22/27] Renames test Signed-off-by: Darshit Chanpura --- server/backend/test/utils.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/backend/test/utils.test.ts b/server/backend/test/utils.test.ts index 1942b9197..47ad18e74 100644 --- a/server/backend/test/utils.test.ts +++ b/server/backend/test/utils.test.ts @@ -16,7 +16,7 @@ import { addQueryParamsToURLIfAny } from '../utils'; describe('Test backend utils', () => { - it('checks isAnonymousPage', () => { + it('checks addQueryParamsToURLIfAny', () => { const url = '/_plugins/_security/abc'; const requestObject = { foo: 'bar', por: 'que' }; From d756b6ccdb137a039107b4ecf9a43c31248163a9 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 11 Apr 2024 12:54:12 -0400 Subject: [PATCH 23/27] Removes unnecessary modifications required to solve this bug Signed-off-by: Darshit Chanpura --- server/auth/types/authentication_type.ts | 10 +++---- server/auth/types/basic/basic_auth.ts | 29 ++++++++++++++------ server/auth/types/basic/routes.ts | 6 +--- server/auth/types/openid/routes.ts | 3 +- server/auth/types/saml/routes.ts | 3 +- server/backend/opensearch_security_client.ts | 11 +------- server/backend/opensearch_security_plugin.ts | 12 +------- server/backend/test/utils.test.ts | 29 -------------------- server/backend/utils.ts | 27 ------------------ server/readonly/readonly_service.ts | 8 +----- test/jest_integration/basic_auth.test.ts | 17 +++++++++--- 11 files changed, 44 insertions(+), 111 deletions(-) delete mode 100644 server/backend/test/utils.test.ts delete mode 100644 server/backend/utils.ts diff --git a/server/auth/types/authentication_type.ts b/server/auth/types/authentication_type.ts index 4bc013b21..8bde376ac 100755 --- a/server/auth/types/authentication_type.ts +++ b/server/auth/types/authentication_type.ts @@ -31,7 +31,7 @@ import { SecuritySessionCookie } from '../../session/security_cookie'; import { SecurityClient } from '../../backend/opensearch_security_client'; import { resolveTenant, isValidTenant } from '../../multitenancy/tenant_resolver'; import { UnauthenticatedError } from '../../errors'; -import { AuthType, GLOBAL_TENANT_SYMBOL } from '../../../common'; +import { GLOBAL_TENANT_SYMBOL } from '../../../common'; export interface IAuthenticationType { type: string; @@ -119,7 +119,7 @@ export abstract class AuthenticationType implements IAuthenticationType { try { const additionalAuthHeader = await this.getAdditionalAuthHeader(request); Object.assign(authHeaders, additionalAuthHeader); - authInfo = await this.securityClient.authinfo(request, '', additionalAuthHeader); + authInfo = await this.securityClient.authinfo(request, additionalAuthHeader); cookie = this.getCookie(request, authInfo); // set tenant from cookie if exist @@ -210,8 +210,7 @@ export abstract class AuthenticationType implements IAuthenticationType { } } if (!authInfo) { - const authRequestType = cookie.isAnonymousAuth ? AuthType.ANONYMOUS : cookie.authType; - authInfo = await this.securityClient.authinfo(request, authRequestType, authHeaders); + authInfo = await this.securityClient.authinfo(request, authHeaders); } authState.authInfo = authInfo; @@ -244,10 +243,9 @@ export abstract class AuthenticationType implements IAuthenticationType { authHeader: any, authInfo: any ): Promise { - const authType = cookie.isAnonymousAuth ? AuthType.ANONYMOUS : cookie.authType; if (!authInfo) { try { - authInfo = await this.securityClient.authinfo(request, authType, authHeader); + authInfo = await this.securityClient.authinfo(request, authHeader); } catch (error: any) { throw new UnauthenticatedError(error); } diff --git a/server/auth/types/basic/basic_auth.ts b/server/auth/types/basic/basic_auth.ts index 2838047f5..113bbb9a7 100644 --- a/server/auth/types/basic/basic_auth.ts +++ b/server/auth/types/basic/basic_auth.ts @@ -30,7 +30,12 @@ import { BasicAuthRoutes } from './routes'; import { AuthenticationType } from '../authentication_type'; import { LOGIN_PAGE_URI } from '../../../../common'; import { composeNextUrlQueryParam } from '../../../utils/next_url'; -import { AUTH_HEADER_NAME, AuthType, OPENDISTRO_SECURITY_ANONYMOUS } from '../../../../common'; +import { + ANONYMOUS_AUTH_LOGIN, + AUTH_HEADER_NAME, + AuthType, + OPENDISTRO_SECURITY_ANONYMOUS, +} from '../../../../common'; export class BasicAuthentication extends AuthenticationType { public readonly type: string = AuthType.BASIC; @@ -111,13 +116,21 @@ export class BasicAuthentication extends AuthenticationType { request, this.coreSetup.http.basePath.serverBasePath ); - - const redirectLocation = `${this.coreSetup.http.basePath.serverBasePath}${LOGIN_PAGE_URI}?${nextUrlParam}`; - return response.redirected({ - headers: { - location: `${redirectLocation}`, - }, - }); + if (this.config.auth.anonymous_auth_enabled) { + const redirectLocation = `${this.coreSetup.http.basePath.serverBasePath}${ANONYMOUS_AUTH_LOGIN}?${nextUrlParam}`; + return response.redirected({ + headers: { + location: `${redirectLocation}`, + }, + }); + } else { + const redirectLocation = `${this.coreSetup.http.basePath.serverBasePath}${LOGIN_PAGE_URI}?${nextUrlParam}`; + return response.redirected({ + headers: { + location: `${redirectLocation}`, + }, + }); + } } else { return response.unauthorized({ body: `Authentication required`, diff --git a/server/auth/types/basic/routes.ts b/server/auth/types/basic/routes.ts index 23e3d71c6..bae3e338c 100755 --- a/server/auth/types/basic/routes.ts +++ b/server/auth/types/basic/routes.ts @@ -186,11 +186,7 @@ export class BasicAuthRoutes { } context.security_plugin.logger.info('The Redirect Path is ' + redirectUrl); try { - user = await this.securityClient.authenticateWithHeaders( - request, - AuthType.ANONYMOUS, - {} - ); + user = await this.securityClient.authenticateWithHeaders(request, {}); } catch (error) { context.security_plugin.logger.error( `Failed authentication: ${error}. Redirecting to Login Page` diff --git a/server/auth/types/openid/routes.ts b/server/auth/types/openid/routes.ts index 9b87e81ea..c23e26b1f 100644 --- a/server/auth/types/openid/routes.ts +++ b/server/auth/types/openid/routes.ts @@ -187,8 +187,7 @@ export class OpenIdAuthRoutes { const user = await this.securityClient.authenticateWithHeader( request, this.openIdAuthConfig.authHeaderName as string, - `Bearer ${tokenResponse.idToken}`, - AuthType.OPEN_ID + `Bearer ${tokenResponse.idToken}` ); // set to cookie diff --git a/server/auth/types/saml/routes.ts b/server/auth/types/saml/routes.ts index 7e717cf0a..3017106d4 100644 --- a/server/auth/types/saml/routes.ts +++ b/server/auth/types/saml/routes.ts @@ -219,8 +219,7 @@ export class SamlAuthRoutes { const user = await this.securityClient.authenticateWithHeader( request, 'authorization', - credentials.authorization, - AuthType.SAML + credentials.authorization ); let expiryTime = Date.now() + this.config.session.ttl; diff --git a/server/backend/opensearch_security_client.ts b/server/backend/opensearch_security_client.ts index 1aa50004b..6134056db 100755 --- a/server/backend/opensearch_security_client.ts +++ b/server/backend/opensearch_security_client.ts @@ -53,7 +53,6 @@ export class SecurityClient { request: OpenSearchDashboardsRequest, headerName: string, headerValue: string, - authRequestType: string, whitelistedHeadersAndValues: any = {}, additionalAuthHeaders: any = {} ): Promise { @@ -72,7 +71,6 @@ export class SecurityClient { const esResponse = await this.esClient .asScoped(request) .callAsCurrentUser('opensearch_security.authinfo', { - [AUTH_TYPE_PARAM]: authRequestType, headers, }); return { @@ -90,14 +88,12 @@ export class SecurityClient { public async authenticateWithHeaders( request: OpenSearchDashboardsRequest, - authRequestType: string, additionalAuthHeaders: any = {} ): Promise { try { const esResponse = await this.esClient .asScoped(request) .callAsCurrentUser('opensearch_security.authinfo', { - [AUTH_TYPE_PARAM]: authRequestType, headers: additionalAuthHeaders, }); return { @@ -112,16 +108,11 @@ export class SecurityClient { } } - public async authinfo( - request: OpenSearchDashboardsRequest, - authRequestType: string = '', - headers: any = {} - ) { + public async authinfo(request: OpenSearchDashboardsRequest, headers: any = {}) { try { return await this.esClient .asScoped(request) .callAsCurrentUser('opensearch_security.authinfo', { - [AUTH_TYPE_PARAM]: authRequestType, headers, }); } catch (error: any) { diff --git a/server/backend/opensearch_security_plugin.ts b/server/backend/opensearch_security_plugin.ts index 9894a7efc..33b72cc0d 100644 --- a/server/backend/opensearch_security_plugin.ts +++ b/server/backend/opensearch_security_plugin.ts @@ -13,9 +13,6 @@ * permissions and limitations under the License. */ -import { AUTH_TYPE_PARAM } from '../../common'; -import { addQueryParamsToURLIfAny } from './utils'; - // eslint-disable-next-line import/no-default-export export default function (Client: any, config: any, components: any) { const ca = components.clientAction.factory; @@ -29,14 +26,7 @@ export default function (Client: any, config: any, components: any) { */ Client.prototype.opensearch_security.prototype.authinfo = ca({ url: { - fmt: `/_plugins/_security/authinfo`, - opt: { - [AUTH_TYPE_PARAM]: { - type: 'string', - required: false, - }, - }, - template: (params) => addQueryParamsToURLIfAny(params, '/_plugins/_security/authinfo'), + fmt: '/_plugins/_security/authinfo', }, }); diff --git a/server/backend/test/utils.test.ts b/server/backend/test/utils.test.ts deleted file mode 100644 index 47ad18e74..000000000 --- a/server/backend/test/utils.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -import { addQueryParamsToURLIfAny } from '../utils'; - -describe('Test backend utils', () => { - it('checks addQueryParamsToURLIfAny', () => { - const url = '/_plugins/_security/abc'; - - const requestObject = { foo: 'bar', por: 'que' }; - - const expected = url + '?foo=bar&por=que'; - const actual = addQueryParamsToURLIfAny(requestObject, url); - - expect(actual).toEqual(expected); - }); -}); diff --git a/server/backend/utils.ts b/server/backend/utils.ts deleted file mode 100644 index 8b0516bf0..000000000 --- a/server/backend/utils.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -export function addQueryParamsToURLIfAny(params: any = {}, url: string) { - // fetches all String components of query params and joins them by `&` - const queryParams = Object.entries(params) - .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`) - .join('&'); - - if (queryParams) { - url += `?${queryParams}`; - } - - return url; -} diff --git a/server/readonly/readonly_service.ts b/server/readonly/readonly_service.ts index 901594812..6e690b5f7 100644 --- a/server/readonly/readonly_service.ts +++ b/server/readonly/readonly_service.ts @@ -24,7 +24,6 @@ import { isPrivateTenant, LOGIN_PAGE_URI, CUSTOM_ERROR_PAGE_URI, - AuthType, } from '../../common'; import { SecurityClient } from '../backend/opensearch_security_client'; import { IAuthenticationType, OpenSearchAuthInfo } from '../auth/types/authentication_type'; @@ -105,12 +104,7 @@ export class ReadonlyService extends BaseReadonlyService { return false; } - const authType = cookie - ? cookie.isAnonymousAuth - ? AuthType.ANONYMOUS - : cookie.authType - : ''; - const authInfo = await this.securityClient.authinfo(request, authType, headers); + const authInfo = await this.securityClient.authinfo(request, headers); if (!authInfo.user_requested_tenant && cookie) { authInfo.user_requested_tenant = cookie.tenant; diff --git a/test/jest_integration/basic_auth.test.ts b/test/jest_integration/basic_auth.test.ts index 421b76a83..cdb4a39d0 100644 --- a/test/jest_integration/basic_auth.test.ts +++ b/test/jest_integration/basic_auth.test.ts @@ -227,11 +227,16 @@ describe('start OpenSearch Dashboards server', () => { .unset(AUTHORIZATION_HEADER_NAME); expect(response.status).toEqual(302); - expect(response.header.location).toEqual('/app/login?nextUrl=%2Fapp%2Fhome'); + expect(response.header.location).toEqual('/auth/anonymous?nextUrl=%2Fapp%2Fhome'); const response2 = await osdTestServer.request.get(root, response.header.location); - expect(response2.status).toEqual(200); + expect(response2.status).toEqual(302); + expect(response2.header.location).toEqual('/app/login?nextUrl=%2Fapp%2Fhome'); + + const response3 = await osdTestServer.request.get(root, response2.header.location); + + expect(response3.status).toEqual(200); }); it('redirect for home follows login for anonymous auth disabled', async () => { @@ -260,10 +265,14 @@ describe('start OpenSearch Dashboards server', () => { .unset(AUTHORIZATION_HEADER_NAME); expect(response.status).toEqual(302); - expect(response.header.location).toEqual(expectedPath); const response2 = await osdTestServer.request.get(root, response.header.location); - expect(response2.status).toEqual(200); + expect(response2.status).toEqual(302); + expect(response2.header.location).toEqual(expectedPath); + + const response3 = await osdTestServer.request.get(root, response2.header.location); + + expect(response3.status).toEqual(200); }); }); From f03d3abb7f50e64ea64e0f2e7430cd70c964768b Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 11 Apr 2024 13:10:13 -0400 Subject: [PATCH 24/27] Fixes import Signed-off-by: Darshit Chanpura --- server/auth/types/basic/basic_auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/auth/types/basic/basic_auth.ts b/server/auth/types/basic/basic_auth.ts index 113bbb9a7..a9cfedb6c 100644 --- a/server/auth/types/basic/basic_auth.ts +++ b/server/auth/types/basic/basic_auth.ts @@ -28,9 +28,9 @@ import { SecurityPluginConfigType } from '../../..'; import { SecuritySessionCookie } from '../../../session/security_cookie'; import { BasicAuthRoutes } from './routes'; import { AuthenticationType } from '../authentication_type'; -import { LOGIN_PAGE_URI } from '../../../../common'; import { composeNextUrlQueryParam } from '../../../utils/next_url'; import { + LOGIN_PAGE_URI, ANONYMOUS_AUTH_LOGIN, AUTH_HEADER_NAME, AuthType, From d58301fb9d39eda51e44501e9a1032309afaec87 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 11 Apr 2024 13:15:12 -0400 Subject: [PATCH 25/27] Removes unused param Signed-off-by: Darshit Chanpura --- server/backend/opensearch_security_client.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/server/backend/opensearch_security_client.ts b/server/backend/opensearch_security_client.ts index 6134056db..6f2d3439f 100755 --- a/server/backend/opensearch_security_client.ts +++ b/server/backend/opensearch_security_client.ts @@ -32,7 +32,6 @@ export class SecurityClient { headers: { authorization: `Basic ${authHeader}`, }, - [AUTH_TYPE_PARAM]: AuthType.BASIC, }); return { username: credentials.username, From 2819b108ef6b2c0e008f0302e4b8e4957eb32421 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 11 Apr 2024 13:17:21 -0400 Subject: [PATCH 26/27] Removes unused method param Signed-off-by: Darshit Chanpura --- server/auth/types/saml/routes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/auth/types/saml/routes.ts b/server/auth/types/saml/routes.ts index 3017106d4..52d050e87 100644 --- a/server/auth/types/saml/routes.ts +++ b/server/auth/types/saml/routes.ts @@ -381,7 +381,7 @@ export class SamlAuthRoutes { }, async (context, request, response) => { try { - const authInfo = await this.securityClient.authinfo(request, AuthType.SAML); + const authInfo = await this.securityClient.authinfo(request); await clearSplitCookies( request, this.getExtraAuthStorageOptions(context.security_plugin.logger) From 1ad734345b7e5ddb0b6e96accc706a954f1d24e5 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 11 Apr 2024 16:00:28 -0400 Subject: [PATCH 27/27] Removes incorrect method param Signed-off-by: Darshit Chanpura --- server/auth/types/saml/routes.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/auth/types/saml/routes.ts b/server/auth/types/saml/routes.ts index 52d050e87..d14d0711b 100644 --- a/server/auth/types/saml/routes.ts +++ b/server/auth/types/saml/routes.ts @@ -140,8 +140,7 @@ export class SamlAuthRoutes { const user = await this.securityClient.authenticateWithHeader( request, 'authorization', - credentials.authorization, - AuthType.SAML + credentials.authorization ); let expiryTime = Date.now() + this.config.session.ttl;