diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 468826191..9520c3e2e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @cliu123 @cwperks @DarshitChanpura @davidlago @derek-ho @peternied @RyanL1997 @scrawfor99 +* @cliu123 @cwperks @DarshitChanpura @derek-ho @peternied @RyanL1997 @scrawfor99 diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 4e21da685..c0eedc0a8 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -8,7 +8,6 @@ This document contains a list of maintainers in this repo. See [opensearch-proje | ---------------- | ----------------------------------------------------- | ----------- | | Chang Liu | [cliu123](https://github.com/cliu123) | Amazon | | Darshit Chanpura | [DarshitChanpura](https://github.com/DarshitChanpura) | Amazon | -| Dave Lago | [davidlago](https://github.com/davidlago) | Amazon | | Peter Nied | [peternied](https://github.com/peternied) | Amazon | | Craig Perkins | [cwperks](https://github.com/cwperks) | Amazon | | Ryan Liang | [RyanL1997](https://github.com/RyanL1997) | Amazon | @@ -22,3 +21,4 @@ This document contains a list of maintainers in this repo. See [opensearch-proje | Yan Zeng | [zengyan-amazon](https://github.com/zengyan-amazon) | Amazon | | Bandini Bhopi | [bandinib-amzn](https://github.com/bandinib-amzn) | Amazon | | Tianle Huang | [tianleh](https://github.com/tianleh) | Amazon | +| Dave Lago | [davidlago](https://github.com/davidlago) | Contributor | diff --git a/common/index.ts b/common/index.ts index b5e6a475d..1a0eb3ff5 100644 --- a/common/index.ts +++ b/common/index.ts @@ -34,6 +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_TYPE_PARAM = 'auth_type'; export const OPENID_AUTH_LOGOUT = '/auth/openid/logout'; export const SAML_AUTH_LOGOUT = '/auth/saml/logout'; diff --git a/public/apps/configuration/panels/tenant-list/configure_tab1.tsx b/public/apps/configuration/panels/tenant-list/configure_tab1.tsx index dd8686664..dafd153d6 100644 --- a/public/apps/configuration/panels/tenant-list/configure_tab1.tsx +++ b/public/apps/configuration/panels/tenant-list/configure_tab1.tsx @@ -328,7 +328,7 @@ export function ConfigureTab1(props: AppDependencies) { -

Multi-tenancy

+

Dashboards multi-tenancy

@@ -360,7 +360,7 @@ export function ConfigureTab1(props: AppDependencies) { -

Tenants

+

Dashboards tenants

{' '} - Global tenant is shared amaong all Dashboards users and cannot be disabled.{' '} + Global tenant is shared among all Dashboards users and cannot be disabled.{' '} } className="described-form-group2" @@ -407,7 +407,7 @@ export function ConfigureTab1(props: AppDependencies) { -

Default tenant

+

Dashboards default tenant

- Tenants + Dashboards tenants {' '} ({Query.execute(query || '', tenantData).length}) diff --git a/public/apps/configuration/panels/tenant-list/tenant-list.tsx b/public/apps/configuration/panels/tenant-list/tenant-list.tsx index 68e9d163d..6a4f53382 100644 --- a/public/apps/configuration/panels/tenant-list/tenant-list.tsx +++ b/public/apps/configuration/panels/tenant-list/tenant-list.tsx @@ -135,7 +135,7 @@ export function TenantList(props: TenantListProps) { <> -

Multi-tenancy

+

Dashboards multi-tenancy

diff --git a/public/apps/configuration/panels/tenant-list/test/__snapshots__/tenant-list.test.tsx.snap b/public/apps/configuration/panels/tenant-list/test/__snapshots__/tenant-list.test.tsx.snap index 64c1e1b64..c817212aa 100644 --- a/public/apps/configuration/panels/tenant-list/test/__snapshots__/tenant-list.test.tsx.snap +++ b/public/apps/configuration/panels/tenant-list/test/__snapshots__/tenant-list.test.tsx.snap @@ -10,7 +10,7 @@ exports[`Tenant list Action menu click Duplicate click 1`] = ` size="s" >

- Tenants + Dashboards tenants @@ -198,7 +198,7 @@ exports[`Tenant list Action menu click Edit click 1`] = ` size="s" >

- Tenants + Dashboards tenants diff --git a/public/apps/login/login-page.tsx b/public/apps/login/login-page.tsx index a22a36dc7..1e5f43dd8 100644 --- a/public/apps/login/login-page.tsx +++ b/public/apps/login/login-page.tsx @@ -231,14 +231,14 @@ export function LoginPage(props: LoginPageDeps) { ); - if (authOpts.length > 1) { - 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(); 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, @@ -166,12 +190,42 @@ 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, auth: { - type: [AuthType.BASIC, 'openid', AuthType.SAML], + type: [AuthType.BASIC, AuthType.OPEN_ID, AuthType.SAML], + logout_url: API_AUTH_LOGOUT, + }, + }; + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + }); + + it('renders with config value for multiauth with anonymous auth enabled', () => { + const config: ClientConfigType = { + ui: configUI, + auth: { + type: [AuthType.BASIC, AuthType.OPEN_ID, AuthType.SAML], logout_url: API_AUTH_LOGOUT, + anonymous_auth_enabled: true, }, }; const component = shallow( diff --git a/server/auth/types/basic/basic_auth.ts b/server/auth/types/basic/basic_auth.ts index f21f86827..a9cfedb6c 100644 --- a/server/auth/types/basic/basic_auth.ts +++ b/server/auth/types/basic/basic_auth.ts @@ -28,9 +28,14 @@ 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 { composeNextUrlQueryParam } from '../../../utils/next_url'; -import { AUTH_HEADER_NAME, AuthType, OPENDISTRO_SECURITY_ANONYMOUS } from '../../../../common'; +import { + LOGIN_PAGE_URI, + ANONYMOUS_AUTH_LOGIN, + AUTH_HEADER_NAME, + AuthType, + OPENDISTRO_SECURITY_ANONYMOUS, +} from '../../../../common'; export class BasicAuthentication extends AuthenticationType { public readonly type: string = AuthType.BASIC; diff --git a/server/auth/types/multiple/multi_auth.ts b/server/auth/types/multiple/multi_auth.ts index b190d9d03..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'; @@ -166,14 +166,6 @@ 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}`, diff --git a/server/auth/types/saml/routes.ts b/server/auth/types/saml/routes.ts index 87605d65e..d14d0711b 100644 --- a/server/auth/types/saml/routes.ts +++ b/server/auth/types/saml/routes.ts @@ -131,11 +131,12 @@ export class SamlAuthRoutes { } try { - const credentials = await this.securityClient.authToken( + const credentials = await this.securityClient.authToken({ requestId, - request.body.SAMLResponse, - undefined - ); + samlResponse: request.body.SAMLResponse, + acsEndpoint: undefined, + authRequestType: AuthType.SAML, + }); const user = await this.securityClient.authenticateWithHeader( request, 'authorization', @@ -208,11 +209,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, - acsEndpoint - ); + const credentials = await this.securityClient.authToken({ + requestId: undefined, + samlResponse: request.body.SAMLResponse, + acsEndpoint, + authRequestType: AuthType.SAML, + }); 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 71a65d205..6f2d3439f 100755 --- a/server/backend/opensearch_security_client.ts +++ b/server/backend/opensearch_security_client.ts @@ -16,6 +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_TYPE_PARAM, AuthType } from '../../common'; export class SecurityClient { constructor(private readonly esClient: ILegacyClusterClient) {} @@ -182,7 +183,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_TYPE_PARAM]: AuthType.SAML, + }); } catch (error: any) { // the error looks like // wwwAuthenticateDirective: @@ -217,11 +220,12 @@ export class SecurityClient { throw new Error(`Invalid SAML configuration.`); } - public async authToken( - requestId: string | undefined, - samlResponse: any, - acsEndpoint: any | undefined = undefined - ) { + public async authToken({ + requestId, + samlResponse, + acsEndpoint = undefined, + authRequestType, + }: AuthTokenParams) { const body = { RequestId: requestId, SAMLResponse: samlResponse, @@ -230,6 +234,7 @@ export class SecurityClient { try { return await this.esClient.asScoped().callAsCurrentUser('opensearch_security.authtoken', { body, + [AUTH_TYPE_PARAM]: authRequestType, }); } catch (error: any) { console.log(error); @@ -237,3 +242,10 @@ export class SecurityClient { } } } + +interface AuthTokenParams { + requestId?: string; + samlResponse: any; + acsEndpoint?: any; + authRequestType?: string; +} diff --git a/test/constant.ts b/test/constant.ts index d4ab9e3ae..5dcb387e2 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 = process.env.ADMIN_PASSWORD || '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..cdb4a39d0 100644 --- a/test/jest_integration/basic_auth.test.ts +++ b/test/jest_integration/basic_auth.test.ts @@ -132,7 +132,6 @@ describe('start OpenSearch Dashboards server', () => { }); 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); @@ -142,7 +141,7 @@ 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); }); @@ -209,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); });