From 1ae5a74c3ae8eb01c917d51d9631cccd08745c3c Mon Sep 17 00:00:00 2001 From: "J Shamsul Bahri (jibone))" Date: Fri, 4 Sep 2020 13:56:54 +0800 Subject: [PATCH 01/17] WIP: Got the SAML login working TODO: Cleanup, remove hardcoded url. --- app-config.yaml | 136 +++++++++--------- packages/app/src/App.tsx | 5 +- packages/app/src/apis.ts | 10 ++ packages/app/src/identityProviders.ts | 7 + .../core-api/src/apis/definitions/auth.ts | 23 +++ .../src/apis/implementations/auth/index.ts | 1 + .../implementations/auth/saml/SamlAuth.ts | 126 ++++++++++++++++ .../apis/implementations/auth/saml/index.ts | 16 +++ .../apis/implementations/auth/saml/types.ts | 22 +++ packages/core-api/src/app/AppIdentity.ts | 4 + .../lib/AuthConnector/SamlAuthConnector.ts | 105 ++++++++++++++ .../core-api/src/lib/AuthConnector/index.ts | 1 + .../SamlAuthSessionManager.ts | 64 +++++++++ .../SamlAuthSessionStore.ts | 96 +++++++++++++ .../src/lib/AuthSessionManager/index.ts | 2 + .../src/lib/AuthSessionManager/types.ts | 7 + .../core/src/layout/Sidebar/UserSettings.tsx | 8 +- .../src/layout/SignInPage/commonProvider.tsx | 5 + .../core/src/layout/SignInPage/providers.tsx | 2 + .../src/layout/SignInPage/samlProvider.tsx | 92 ++++++++++++ packages/core/src/layout/SignInPage/types.ts | 9 +- .../auth-backend/src/providers/factories.ts | 1 + .../src/providers/saml/provider.ts | 29 +++- .../src/ingestion/HigherOrderOperations.ts | 6 +- yarn.lock | 5 +- 25 files changed, 698 insertions(+), 84 deletions(-) create mode 100644 packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts create mode 100644 packages/core-api/src/apis/implementations/auth/saml/index.ts create mode 100644 packages/core-api/src/apis/implementations/auth/saml/types.ts create mode 100644 packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts create mode 100644 packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionManager.ts create mode 100644 packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionStore.ts create mode 100644 packages/core/src/layout/SignInPage/samlProvider.tsx diff --git a/app-config.yaml b/app-config.yaml index f578fb3fca..e65df93969 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -28,72 +28,74 @@ sentry: auth: providers: - google: - development: - appOrigin: "http://localhost:3000/" - secure: false - clientId: - $secret: - env: AUTH_GOOGLE_CLIENT_ID - clientSecret: - $secret: - env: AUTH_GOOGLE_CLIENT_SECRET - github: - development: - appOrigin: "http://localhost:3000/" - secure: false - clientId: - $secret: - env: AUTH_GITHUB_CLIENT_ID - clientSecret: - $secret: - env: AUTH_GITHUB_CLIENT_SECRET - enterpriseInstanceUrl: - $secret: - env: AUTH_GITHUB_ENTERPRISE_INSTANCE_URL - gitlab: - development: - appOrigin: "http://localhost:3000/" - secure: false - clientId: - $secret: - env: AUTH_GITLAB_CLIENT_ID - clientSecret: - $secret: - env: AUTH_GITLAB_CLIENT_SECRET - audience: - $secret: - env: GITLAB_BASE_URL - # saml: + # google: # development: - # entryPoint: "http://localhost:7001/" - # issuer: "passport-saml" - okta: + # appOrigin: 'http://localhost:3000/' + # secure: false + # clientId: + # $secret: + # env: AUTH_GOOGLE_CLIENT_ID + # clientSecret: + # $secret: + # env: AUTH_GOOGLE_CLIENT_SECRET + # github: + # development: + # appOrigin: 'http://localhost:3000/' + # secure: false + # clientId: + # $secret: + # env: AUTH_GITHUB_CLIENT_ID + # clientSecret: + # $secret: + # env: AUTH_GITHUB_CLIENT_SECRET + # enterpriseInstanceUrl: + # $secret: + # env: AUTH_GITHUB_ENTERPRISE_INSTANCE_URL + # gitlab: + # development: + # appOrigin: 'http://localhost:3000/' + # secure: false + # clientId: + # $secret: + # env: AUTH_GITLAB_CLIENT_ID + # clientSecret: + # $secret: + # env: AUTH_GITLAB_CLIENT_SECRET + # audience: + # $secret: + # env: GITLAB_BASE_URL + saml: development: - appOrigin: "http://localhost:3000/" - secure: false - clientId: - $secret: - env: AUTH_OKTA_CLIENT_ID - clientSecret: - $secret: - env: AUTH_OKTA_CLIENT_SECRET - audience: - $secret: - env: AUTH_OKTA_AUDIENCE - oauth2: - development: - appOrigin: "http://localhost:3000/" - secure: false - clientId: - $secret: - env: AUTH_OAUTH2_CLIENT_ID - clientSecret: - $secret: - env: AUTH_OAUTH2_CLIENT_SECRET - authorizationURL: - $secret: - env: AUTH_OAUTH2_AUTH_URL - tokenURL: - $secret: - env: AUTH_OAUTH2_TOKEN_URL + entryPoint: 'https://sso.jumpcloud.com/saml2/backstage-test' + issuer: 'passport-saml' + # entryPoint: 'http://localhost:7001/' + # issuer: 'passport-saml' + # okta + # development: + # appOrigin: 'http://localhost:3000/' + # secure: false + # clientId: + # $secret: + # env: AUTH_OKTA_CLIENT_ID + # clientSecret: + # $secret: + # env: AUTH_OKTA_CLIENT_SECRET + # audience: + # $secret: + # env: AUTH_OKTA_AUDIENCE + # oauth2: + # development: + # appOrigin: 'http://localhost:3000/' + # secure: false + # clientId: + # $secret: + # env: AUTH_OAUTH2_CLIENT_ID + # clientSecret: + # $secret: + # env: AUTH_OAUTH2_CLIENT_SECRET + # authorizationURL: + # $secret: + # env: AUTH_OAUTH2_AUTH_URL + # tokenURL: + # $secret: + # env: AUTH_OAUTH2_TOKEN_URL diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 9a9fdfe5c4..29e371b469 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -33,7 +33,10 @@ const app = createApp({ components: { SignInPage: props => { return ( - + ); }, }, diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts index cac3d6e171..0c097450bc 100644 --- a/packages/app/src/apis.ts +++ b/packages/app/src/apis.ts @@ -38,6 +38,8 @@ import { gitlabAuthApiRef, storageApiRef, WebStorage, + SamlAuth, + samlAuthApiRef, } from '@backstage/core'; import { @@ -139,6 +141,14 @@ export const apis = (config: ConfigApi) => { }), ); + builder.add( + samlAuthApiRef, + SamlAuth.create({ + apiOrigin: backendUrl, + basePath: '/auth/', + }), + ); + builder.add( techRadarApiRef, new TechRadar({ diff --git a/packages/app/src/identityProviders.ts b/packages/app/src/identityProviders.ts index e80b1d6fea..e229bb7671 100644 --- a/packages/app/src/identityProviders.ts +++ b/packages/app/src/identityProviders.ts @@ -19,6 +19,7 @@ import { gitlabAuthApiRef, oktaAuthApiRef, githubAuthApiRef, + samlAuthApiRef, } from '@backstage/core'; export const providers = [ @@ -46,4 +47,10 @@ export const providers = [ message: 'Sign In using Okta', apiRef: oktaAuthApiRef, }, + { + id: 'saml-auth-provider', + title: 'SAML', + message: 'Sign In using SAML', + apiRef: samlAuthApiRef, + }, ]; diff --git a/packages/core-api/src/apis/definitions/auth.ts b/packages/core-api/src/apis/definitions/auth.ts index ec7aba81c4..6dfe9595b0 100644 --- a/packages/core-api/src/apis/definitions/auth.ts +++ b/packages/core-api/src/apis/definitions/auth.ts @@ -97,6 +97,21 @@ export type OAuthApi = { logout(): Promise; }; +/** + * This API provides access to SAML 2 credentials. Verify user access with identity provider. + */ +export type SamlApi = { + // Not sure what Promise call back should have. + getBackstageIdentity( + options?: AuthRequestOptions, + ): Promise; + + getProfile(options?: AuthRequestOptions): Promise; + + // Not sure if this is needed. + logout(): Promise; +}; + /** * This API provides access to OpenID Connect credentials. It lets you request ID tokens, * which can be passed to backend services to prove the user's identity. @@ -273,3 +288,11 @@ export const oauth2ApiRef = createApiRef< id: 'core.auth.oauth2', description: 'Example of how to use oauth2 custom provider', }); + +/** + * Provides authentication for saml based identity providers + */ +export const samlAuthApiRef = createApiRef({ + id: 'core.auth.saml', + description: 'provides authentication towards saml based provider', +}); diff --git a/packages/core-api/src/apis/implementations/auth/index.ts b/packages/core-api/src/apis/implementations/auth/index.ts index 786e9fa771..0906e34149 100644 --- a/packages/core-api/src/apis/implementations/auth/index.ts +++ b/packages/core-api/src/apis/implementations/auth/index.ts @@ -19,3 +19,4 @@ export * from './gitlab'; export * from './google'; export * from './oauth2'; export * from './okta'; +export * from './saml'; diff --git a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts new file mode 100644 index 0000000000..75aefda7f5 --- /dev/null +++ b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts @@ -0,0 +1,126 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 SamlIcon from '@material-ui/icons/AcUnit'; +import { SamlAuthConnector } from '../../../../lib/AuthConnector'; +import { SessionManager } from '../../../../lib/AuthSessionManager/types'; +import { Observable } from '../../../../types'; +import { + ProfileInfo, + BackstageIdentity, + SessionState, + AuthRequestOptions, + SamlApi, +} from '../../../definitions/auth'; +import { AuthProvider } from '../../../definitions'; +import { SamlSession } from './types'; +import { + SamlAuthSessionManager, + SamlAuthSessionStore, +} from '../../../../lib/AuthSessionManager'; + +type CreateOptions = { + apiOrigin: string; + basePath: string; + + // oauthRequestApi?: OAuthRequestApi; + + environment?: string; + provider?: AuthProvider & { id: string }; +}; + +export type SamlAuthResponse = { + userId: string; + profile: ProfileInfo; + backstageIdentity: BackstageIdentity; +}; + +const DEFAULT_PROVIDER = { + id: 'saml', + title: 'SAML', + icon: SamlIcon, +}; + +class SamlAuth implements SamlApi { + static create({ + apiOrigin, + basePath, + environment = 'development', + provider = DEFAULT_PROVIDER, + }: CreateOptions) { + // eslint-disable-next-line no-console + console.log('this is from SamlAuth'); + + const connector = new SamlAuthConnector({ + apiOrigin, + basePath, + environment, + provider, + }); + + const sessionManager = new SamlAuthSessionManager({ + connector, + }); + + const authSessionStore = new SamlAuthSessionStore({ + manager: sessionManager, + storageKey: 'samlSession', + }); + + // return new SamlAuth(authSessionStore); + return new SamlAuth(authSessionStore); + } + + sessionState$(): Observable { + return this.sessionManager.sessionState$(); + } + // constructor(private readonly sessionManager: any) {} + // eslint-disable-next-line @typescript-eslint/no-useless-constructor + constructor(private readonly sessionManager: SessionManager) { + // eslint-disable-next-line no-console + console.log('this is the constructor'); + } + + async getBackstageIdentity( + options: AuthRequestOptions, + ): Promise { + // eslint-disable-next-line no-console + console.log('===> Saml getBackstageIdentity()'); + const session = await this.sessionManager.getSession(options); + // eslint-disable-next-line no-console + console.log('this this thish lkkdfkkfkfji'); + // eslint-disable-next-line no-console + console.log(session); + return session?.backstageIdentity; + } + + async getProfile(options: AuthRequestOptions = {}) { + // eslint-disable-next-line no-console + console.log('==> samlauth getprofile()'); + const session = await this.sessionManager.getSession(options); + // eslint-disable-next-line no-console + console.log('+++ this is the session from getProfile()'); + // eslint-disable-next-line no-console + console.log(session); + return session?.profile; + } + + async logout() { + await this.sessionManager.removeSession(); + } +} + +export default SamlAuth; diff --git a/packages/core-api/src/apis/implementations/auth/saml/index.ts b/packages/core-api/src/apis/implementations/auth/saml/index.ts new file mode 100644 index 0000000000..c2436ab435 --- /dev/null +++ b/packages/core-api/src/apis/implementations/auth/saml/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 { default as SamlAuth } from './SamlAuth'; diff --git a/packages/core-api/src/apis/implementations/auth/saml/types.ts b/packages/core-api/src/apis/implementations/auth/saml/types.ts new file mode 100644 index 0000000000..296f70b0ea --- /dev/null +++ b/packages/core-api/src/apis/implementations/auth/saml/types.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 { ProfileInfo, BackstageIdentity } from '../../../definitions'; + +export type SamlSession = { + userId: string; + profile: ProfileInfo; + backstageIdentity: BackstageIdentity; +}; diff --git a/packages/core-api/src/app/AppIdentity.ts b/packages/core-api/src/app/AppIdentity.ts index 69ee5d28ac..1a6cdac3e6 100644 --- a/packages/core-api/src/app/AppIdentity.ts +++ b/packages/core-api/src/app/AppIdentity.ts @@ -67,6 +67,10 @@ export class AppIdentity implements IdentityApi { // This is indirectly called by the sign-in page to continue into the app. setSignInResult(result: SignInResult) { + // eslint-disable-next-line no-console + console.log('this is from setSignInResult'); + // eslint-disable-next-line no-console + console.log(result); if (this.hasIdentity) { return; } diff --git a/packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts b/packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts new file mode 100644 index 0000000000..9dc0fca0a0 --- /dev/null +++ b/packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts @@ -0,0 +1,105 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 { + AuthProvider, + ProfileInfo, + BackstageIdentity, +} from '../../apis/definitions'; +import { AuthConnector } from './types'; +import { showLoginPopup } from '../loginPopup'; + +const DEFAULT_BASE_PATH = '/api/auth'; + +type Options = { + apiOrigin?: string; + basePath?: string; + environment?: string; + provider: AuthProvider & { id: string }; +}; + +export type SamlResponse = { + userId: string; + profile: ProfileInfo; + backstageIdentity: BackstageIdentity; +}; + +export class SamlAuthConnector implements AuthConnector { + private readonly apiOrigin: string; + private readonly basePath: string; + private readonly environment: string | undefined; + private readonly provider: AuthProvider & { id: string }; + + constructor(options: Options) { + const { + apiOrigin = window.location.origin, + basePath = DEFAULT_BASE_PATH, + environment, + provider, + } = options; + + this.apiOrigin = apiOrigin; + this.basePath = basePath; + this.environment = environment; + this.provider = provider; + } + + async createSession(): Promise { + // eslint-disable-next-line no-console + console.log('==> from SamlAuthConnector createSession'); + + const payload = await showLoginPopup({ + url: 'http://localhost:7000/auth/saml/start', // FIXME: this should be from app.config or somewhere + name: 'SAML Login', // FIXME: change this to provider name? and not hardcode the name + origin: this.apiOrigin, + width: 450, + height: 730, + }); + + // eslint-disable-next-line no-console + console.log(payload); + + return { + ...payload, + id: payload.profile.email, + }; + } + + // TODO: do we need this for SAML? + async refreshSession(): Promise { + // eslint-disable-next-line no-console + console.log('==> this is refresh session'); + } + + async removeSession(): Promise { + // eslint-disable-next-line no-console + console.log('this removes the session'); + const res = await fetch('http://localhost:7000/auth/saml/logout', { + method: 'POST', + headers: { + 'x-requested-with': 'XMLHttpRequest', + }, + credentials: 'include', + }).catch(error => { + throw new Error(`Logout request failed, ${error}`); + }); + + if (!res.ok) { + const error: any = new Error(`Logout request failed, ${res.statusText}`); + error.status = res.status; + throw error; + } + } +} diff --git a/packages/core-api/src/lib/AuthConnector/index.ts b/packages/core-api/src/lib/AuthConnector/index.ts index db5c582328..84f5067443 100644 --- a/packages/core-api/src/lib/AuthConnector/index.ts +++ b/packages/core-api/src/lib/AuthConnector/index.ts @@ -15,4 +15,5 @@ */ export { DefaultAuthConnector } from './DefaultAuthConnector'; +export { SamlAuthConnector } from './SamlAuthConnector'; export * from './types'; diff --git a/packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionManager.ts b/packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionManager.ts new file mode 100644 index 0000000000..9439684cf4 --- /dev/null +++ b/packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionManager.ts @@ -0,0 +1,64 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 { SessionManager, GetSessionOptions } from './types'; +import { SamlAuthConnector, SamlResponse } from '../AuthConnector/SamlAuthConnector'; +import { SessionStateTracker } from './SessionStateTracker'; + + +type Options = { + connector: SamlAuthConnector; +}; + +export class SamlAuthSessionManager implements SessionManager { + private readonly connector: SamlAuthConnector; + private readonly stateTracker = new SessionStateTracker(); + + private currentSession: any | undefined; // FIXME: proper typing here + + constructor(options: Options) { + const { connector } = options; + + this.connector = connector; + // this.helper = new SessionScopeHelper + } + + async getSession(options: GetSessionOptions): Promise { + // eslint-disable-next-line no-console + console.log('==> this is from SamlAuthSessionManager getSession()'); + if (this.currentSession) { + return this.currentSession; + } + + if (options.optional) { + return undefined; + } + + this.currentSession = await this.connector.createSession(); + this.stateTracker.setIsSignedIn(true); + return this.currentSession; + } + + async removeSession() { + this.currentSession = undefined; + await this.connector.removeSession(); + this.stateTracker.setIsSignedIn(false); + } + + sessionState$() { + return this.stateTracker.sessionState$(); + } +} diff --git a/packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionStore.ts b/packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionStore.ts new file mode 100644 index 0000000000..53fba09b54 --- /dev/null +++ b/packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionStore.ts @@ -0,0 +1,96 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 { SamlAuthSessionManager, GetSessionOptions } from './types'; + +type Options = { + manager: SamlAuthSessionManager; + storageKey: string; +}; + +export class SamlAuthSessionStore implements SamlAuthSessionManager { + private readonly manager: SamlAuthSessionManager; + private readonly storageKey: string; + + constructor(options: Options) { + const { manager, storageKey } = options; + + this.manager = manager; + this.storageKey = storageKey; + } + + async getSession(options: GetSessionOptions): Promise { + // eslint-disable-next-line no-console + console.log('==>> this is from SamlAuthSessionStore getSession()'); + const session = this.loadSession(); + + if (session) { + return session!; + } + + const newSession = await this.manager.getSession(options); + this.saveSession(newSession); + return newSession; + } + + async removeSession() { + localStorage.removeItem(this.storageKey); + await this.manager.removeSession(); + } + + sessionState$() { + return this.manager.sessionState$(); + } + + private saveSession(session: T | undefined) { + if (session === undefined) { + localStorage.removeItem(this.storageKey); + } else { + localStorage.setItem( + this.storageKey, + JSON.stringify(session, (_key, value) => { + if (value instanceof Set) { + return { + __type: 'Set', + __value: Array.from(value), + }; + } + return value; + }), + ); + } + } + + private loadSession(): T | undefined { + try { + const sessionJson = localStorage.getItem(this.storageKey); + if (sessionJson) { + const session = JSON.parse(sessionJson, (_key, value) => { + if (value?.__type === 'Set') { + return new Set(value.__value); + } + return value; + }); + return session; + } + + return undefined; + } catch (error) { + localStorage.removeItem(this.storageKey); + return undefined; + } + } +} diff --git a/packages/core-api/src/lib/AuthSessionManager/index.ts b/packages/core-api/src/lib/AuthSessionManager/index.ts index 5f4dde8662..a452ac0055 100644 --- a/packages/core-api/src/lib/AuthSessionManager/index.ts +++ b/packages/core-api/src/lib/AuthSessionManager/index.ts @@ -16,5 +16,7 @@ export { RefreshingAuthSessionManager } from './RefreshingAuthSessionManager'; export { StaticAuthSessionManager } from './StaticAuthSessionManager'; +export { SamlAuthSessionManager } from './SamlAuthSessionManager'; export { AuthSessionStore } from './AuthSessionStore'; +export { SamlAuthSessionStore } from './SamlAuthSessionStore'; export * from './types'; diff --git a/packages/core-api/src/lib/AuthSessionManager/types.ts b/packages/core-api/src/lib/AuthSessionManager/types.ts index 804c7121e1..1be439a840 100644 --- a/packages/core-api/src/lib/AuthSessionManager/types.ts +++ b/packages/core-api/src/lib/AuthSessionManager/types.ts @@ -36,6 +36,13 @@ export type SessionManager = { sessionState$(): Observable; }; +// FIXME do we need this? +export type SamlAuthSessionManager = { + getSession(options: GetSessionOptions): Promise; + removeSession(): Promise; + sessionState$(): Observable; +}; + /** * A function called to determine the scopes of a session. */ diff --git a/packages/core/src/layout/Sidebar/UserSettings.tsx b/packages/core/src/layout/Sidebar/UserSettings.tsx index e69d63186d..b08a3b82d8 100644 --- a/packages/core/src/layout/Sidebar/UserSettings.tsx +++ b/packages/core/src/layout/Sidebar/UserSettings.tsx @@ -49,6 +49,12 @@ export function SidebarUserSettings() { if (!sidebarOpen && open) setOpen(false); }, [open, sidebarOpen]); + // FIXME: Change this and remove the session storage stuff + const handleLogout = () => { + identityApi.logout(); + window.sessionStorage.clear(); + }; + return ( <> @@ -91,7 +97,7 @@ export function SidebarUserSettings() { identityApi.logout()} + onClick={handleLogout} /> diff --git a/packages/core/src/layout/SignInPage/commonProvider.tsx b/packages/core/src/layout/SignInPage/commonProvider.tsx index f9611342a2..88a53ec358 100644 --- a/packages/core/src/layout/SignInPage/commonProvider.tsx +++ b/packages/core/src/layout/SignInPage/commonProvider.tsx @@ -36,6 +36,11 @@ const Component: ProviderComponent = ({ config, onResult }) => { instantPopup: true, }); + // eslint-disable-next-line no-console + console.log('====++++++++++++++++++++++++++> handleLogin for commonProvider'); + // eslint-disable-next-line no-console + console.log(identity); + const profile = await authApi.getProfile(); onResult({ userId: identity!.id, diff --git a/packages/core/src/layout/SignInPage/providers.tsx b/packages/core/src/layout/SignInPage/providers.tsx index ff60eba228..436078e49c 100644 --- a/packages/core/src/layout/SignInPage/providers.tsx +++ b/packages/core/src/layout/SignInPage/providers.tsx @@ -26,6 +26,7 @@ import { SignInConfig, IdentityProviders, SignInProvider } from './types'; import { commonProvider } from './commonProvider'; import { guestProvider } from './guestProvider'; import { customProvider } from './customProvider'; +import { samlProvider } from './samlProvider'; const PROVIDER_STORAGE_KEY = '@backstage/core:SignInPage:provider'; @@ -41,6 +42,7 @@ const signInProviders: { [key: string]: SignInProvider } = { guest: guestProvider, custom: customProvider, common: commonProvider, + saml: samlProvider, }; function validateIDs(id: string, providers: SignInProviderType): void { diff --git a/packages/core/src/layout/SignInPage/samlProvider.tsx b/packages/core/src/layout/SignInPage/samlProvider.tsx new file mode 100644 index 0000000000..e9673bca8c --- /dev/null +++ b/packages/core/src/layout/SignInPage/samlProvider.tsx @@ -0,0 +1,92 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 React from 'react'; +import { Grid, Typography, Button } from '@material-ui/core'; +import { ProviderComponent, ProviderLoader, SignInProvider } from './types'; +import { useApi, errorApiRef /* samlAuthApiRef */ } from '@backstage/core-api'; +import { InfoCard } from '../InfoCard/InfoCard'; + +const Component: ProviderComponent = ({ onResult }) => { + // const samlApi = useApi(samlAuthApiRef); + const errorApi = useApi(errorApiRef); + + const handleSSORedirect = () => { + // FIXME: this shoudl be from identity API or something. + window.open('http://localhost:7000/auth/saml/start'); + }; + + const receiveMessage = (event: any) => { + // FIXME: Should use the backstage identity API and not create this session storage. + window.sessionStorage.setItem( + 'helixSession', + JSON.stringify(event.data.response), + ); + + onResult({ + userId: event.data.response.backstageIdentity, + profile: { + email: event.data.response.profile.email, + displayName: event.data.response.profile.displayName, + }, + }); + }; + + const handleLogin = async () => { + try { + // FIXME: this should be handle through backstage API or something. + handleSSORedirect(); + window.addEventListener('message', receiveMessage, false); + } catch (error) { + errorApi.post(error); + } + }; + + return ( + + + Sign In + + } + > + Sign In using SAML lolololol + + + ); +}; + +const loader: ProviderLoader = async () => { + // FIXME: should get the profile from identiy API not from storage session. + const sessionRaw = window.sessionStorage.getItem('helixSession'); + + if (!sessionRaw) { + return undefined; + } + + const session = JSON.parse(sessionRaw); + + return { + userId: session.backstageIdentity, + profile: { + email: session.profile.email, + displayName: session.profile.displayName, + }, + }; +}; + +export const samlProvider: SignInProvider = { Component, loader }; diff --git a/packages/core/src/layout/SignInPage/types.ts b/packages/core/src/layout/SignInPage/types.ts index 9c501e8095..74463f93b4 100644 --- a/packages/core/src/layout/SignInPage/types.ts +++ b/packages/core/src/layout/SignInPage/types.ts @@ -24,18 +24,19 @@ import { ProfileInfoApi, BackstageIdentityApi, SessionStateApi, + SamlApi, } from '@backstage/core-api'; export type SignInConfig = { id: string; title: string; message: string; - apiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionStateApi - >; + apiRef: + | ApiRef + | ApiRef; }; -export type IdentityProviders = ('guest' | 'custom' | SignInConfig)[]; +export type IdentityProviders = ('guest' | 'custom' | 'saml' | SignInConfig)[]; export type ProviderComponent = ComponentType< SignInPageProps & { config: SignInConfig } diff --git a/plugins/auth-backend/src/providers/factories.ts b/plugins/auth-backend/src/providers/factories.ts index 701eef81af..3311bfc836 100644 --- a/plugins/auth-backend/src/providers/factories.ts +++ b/plugins/auth-backend/src/providers/factories.ts @@ -62,6 +62,7 @@ export const createAuthProviderRouter = ( for (const env of envs) { const envConfig = providerConfig.getConfig(env); + console.log(envConfig); const provider = factory(globalConfig, env, envConfig, logger, issuer); if (provider) { envProviders[env] = provider; diff --git a/plugins/auth-backend/src/providers/saml/provider.ts b/plugins/auth-backend/src/providers/saml/provider.ts index 1ccee1a9cb..fe6bb87b6d 100644 --- a/plugins/auth-backend/src/providers/saml/provider.ts +++ b/plugins/auth-backend/src/providers/saml/provider.ts @@ -55,8 +55,21 @@ export class SamlAuthProvider implements AuthProviderRouteHandlers { // for non-oauth auth flows. // TODO: This flow doesn't issue an identity token that can be used to validate // the identity of the user in other backends, which we need in some form. + console.log('===>'); + console.log('===>'); + console.log('===>'); + console.log('===>'); + console.log('===>'); + console.log('===>'); + console.log('===>'); + console.log('===>'); + console.log('===>'); + console.log('===>'); + console.log('===>'); + console.log('===> SamlAuthProvider constructor'); + console.log(profile); done(undefined, { - userId: profile.ID!, + userId: profile.nameID!, profile: { email: profile.email!, displayName: profile.displayName as string, @@ -76,9 +89,9 @@ export class SamlAuthProvider implements AuthProviderRouteHandlers { ): Promise { try { const { - response: { userId, profile }, + response: { userId, profile }, } = await executeFrameHandlerStrategy(req, this.strategy); - + const id = userId; const idToken = await this.tokenIssuer.issueToken({ claims: { sub: id }, @@ -107,8 +120,14 @@ export class SamlAuthProvider implements AuthProviderRouteHandlers { res.send('noop'); } - identifyEnv(): string | undefined { - return undefined; + identifyEnv(req: express.Request): string | undefined { + const reqEnv = req.query.env?.toString(); + if (reqEnv) { + return reqEnv; + } + + // FIXME : do we want to always to return 'development' ? + return 'development'; } } diff --git a/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts b/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts index 0224dc7f1d..2b446c9cd6 100644 --- a/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts +++ b/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts @@ -116,14 +116,14 @@ export class HigherOrderOperations implements HigherOrderOperation { * Entities are read from their respective sources, are parsed and validated * according to the entity policy, and get inserted or updated in the catalog. * Entities that have disappeared from their location are left orphaned, - * without changes. + * without changes.i */ async refreshAllLocations(): Promise { const startTimestamp = new Date().valueOf(); - this.logger.info('Beginning locations refresh'); + // this.logger.info('Beginning locations refresh'); const locations = await this.locationsCatalog.locations(); - this.logger.info(`Visiting ${locations.length} locations`); + // this.logger.info(`Visiting ${locations.length} locations`); for (const { data: location } of locations) { this.logger.debug( diff --git a/yarn.lock b/yarn.lock index 0354b1844f..f873b2cdaf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9848,9 +9848,8 @@ file-loader@^4.2.0: loader-utils "^1.2.3" schema-utils "^2.5.0" -"file-saver@github:eligrey/FileSaver.js#1.3.8": +file-saver@eligrey/FileSaver.js#1.3.8: version "1.3.8" - uid e865e37af9f9947ddcced76b549e27dc45c1cb2e resolved "https://codeload.github.com/eligrey/FileSaver.js/tar.gz/e865e37af9f9947ddcced76b549e27dc45c1cb2e" file-system-cache@^1.0.5: @@ -12977,7 +12976,7 @@ jspdf@1.5.3: integrity sha512-J9X76xnncMw+wIqb15HeWfPMqPwYxSpPY8yWPJ7rAZN/ZDzFkjCSZObryCyUe8zbrVRNiuCnIeQteCzMn7GnWw== dependencies: canvg "1.5.3" - file-saver "github:eligrey/FileSaver.js#1.3.8" + file-saver eligrey/FileSaver.js#1.3.8 html2canvas "1.0.0-alpha.12" omggif "1.0.7" promise-polyfill "8.1.0" From 667b42cfb7dbaffbb9488512b4a79847f567d773 Mon Sep 17 00:00:00 2001 From: "J Shamsul Bahri (jibone))" Date: Fri, 4 Sep 2020 16:18:32 +0800 Subject: [PATCH 02/17] Remove console.log debug stuff. --- packages/app/src/App.tsx | 2 +- .../core-api/src/apis/definitions/auth.ts | 3 + .../implementations/auth/saml/SamlAuth.ts | 32 ++----- packages/core-api/src/app/AppIdentity.ts | 4 - .../lib/AuthConnector/SamlAuthConnector.ts | 21 ++--- .../SamlAuthSessionManager.ts | 11 +-- .../SamlAuthSessionStore.ts | 2 - .../src/layout/SignInPage/commonProvider.tsx | 5 - .../core/src/layout/SignInPage/providers.tsx | 2 - .../src/layout/SignInPage/samlProvider.tsx | 92 ------------------- packages/core/src/layout/SignInPage/types.ts | 8 +- .../src/providers/saml/provider.ts | 11 --- 12 files changed, 27 insertions(+), 166 deletions(-) delete mode 100644 packages/core/src/layout/SignInPage/samlProvider.tsx diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 29e371b469..6ff4da0fe3 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -35,7 +35,7 @@ const app = createApp({ return ( ); }, diff --git a/packages/core-api/src/apis/definitions/auth.ts b/packages/core-api/src/apis/definitions/auth.ts index 6dfe9595b0..8f0a4b6595 100644 --- a/packages/core-api/src/apis/definitions/auth.ts +++ b/packages/core-api/src/apis/definitions/auth.ts @@ -108,6 +108,9 @@ export type SamlApi = { getProfile(options?: AuthRequestOptions): Promise; + // FIXME: is this needed? + getAccessToken(options?: AuthRequestOptions): Promise; + // Not sure if this is needed. logout(): Promise; }; diff --git a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts index 75aefda7f5..cb93dcea97 100644 --- a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts +++ b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts @@ -35,9 +35,6 @@ import { type CreateOptions = { apiOrigin: string; basePath: string; - - // oauthRequestApi?: OAuthRequestApi; - environment?: string; provider?: AuthProvider & { id: string }; }; @@ -61,9 +58,6 @@ class SamlAuth implements SamlApi { environment = 'development', provider = DEFAULT_PROVIDER, }: CreateOptions) { - // eslint-disable-next-line no-console - console.log('this is from SamlAuth'); - const connector = new SamlAuthConnector({ apiOrigin, basePath, @@ -87,37 +81,27 @@ class SamlAuth implements SamlApi { sessionState$(): Observable { return this.sessionManager.sessionState$(); } - // constructor(private readonly sessionManager: any) {} - // eslint-disable-next-line @typescript-eslint/no-useless-constructor - constructor(private readonly sessionManager: SessionManager) { - // eslint-disable-next-line no-console - console.log('this is the constructor'); - } + + constructor(private readonly sessionManager: SessionManager) {} async getBackstageIdentity( options: AuthRequestOptions, ): Promise { - // eslint-disable-next-line no-console - console.log('===> Saml getBackstageIdentity()'); const session = await this.sessionManager.getSession(options); - // eslint-disable-next-line no-console - console.log('this this thish lkkdfkkfkfji'); - // eslint-disable-next-line no-console - console.log(session); return session?.backstageIdentity; } async getProfile(options: AuthRequestOptions = {}) { - // eslint-disable-next-line no-console - console.log('==> samlauth getprofile()'); const session = await this.sessionManager.getSession(options); - // eslint-disable-next-line no-console - console.log('+++ this is the session from getProfile()'); - // eslint-disable-next-line no-console - console.log(session); return session?.profile; } + // FIXME: Is this needed?... + async getAccessToken(options: AuthRequestOptions) { + const session = await this.sessionManager.getSession(options); + return session?.userId ?? ''; + } + async logout() { await this.sessionManager.removeSession(); } diff --git a/packages/core-api/src/app/AppIdentity.ts b/packages/core-api/src/app/AppIdentity.ts index 1a6cdac3e6..69ee5d28ac 100644 --- a/packages/core-api/src/app/AppIdentity.ts +++ b/packages/core-api/src/app/AppIdentity.ts @@ -67,10 +67,6 @@ export class AppIdentity implements IdentityApi { // This is indirectly called by the sign-in page to continue into the app. setSignInResult(result: SignInResult) { - // eslint-disable-next-line no-console - console.log('this is from setSignInResult'); - // eslint-disable-next-line no-console - console.log(result); if (this.hasIdentity) { return; } diff --git a/packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts b/packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts index 9dc0fca0a0..4a6babe224 100644 --- a/packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts +++ b/packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts @@ -36,7 +36,8 @@ export type SamlResponse = { backstageIdentity: BackstageIdentity; }; -export class SamlAuthConnector implements AuthConnector { +export class SamlAuthConnector + implements AuthConnector { private readonly apiOrigin: string; private readonly basePath: string; private readonly environment: string | undefined; @@ -57,35 +58,25 @@ export class SamlAuthConnector implements AuthConnector { - // eslint-disable-next-line no-console - console.log('==> from SamlAuthConnector createSession'); - const payload = await showLoginPopup({ - url: 'http://localhost:7000/auth/saml/start', // FIXME: this should be from app.config or somewhere + url: 'http://localhost:7000/auth/saml/start', // FIXME: remove hardcoded here. should do something like buildUrl name: 'SAML Login', // FIXME: change this to provider name? and not hardcode the name origin: this.apiOrigin, width: 450, height: 730, }); - // eslint-disable-next-line no-console - console.log(payload); - return { ...payload, id: payload.profile.email, }; } - // TODO: do we need this for SAML? - async refreshSession(): Promise { - // eslint-disable-next-line no-console - console.log('==> this is refresh session'); - } + // FIXME: do we need this for SAML? + async refreshSession(): Promise {} async removeSession(): Promise { - // eslint-disable-next-line no-console - console.log('this removes the session'); + // FIXME: remove hardcoded url here... should do something like buildUrl const res = await fetch('http://localhost:7000/auth/saml/logout', { method: 'POST', headers: { diff --git a/packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionManager.ts b/packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionManager.ts index 9439684cf4..8a21bc05b9 100644 --- a/packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionManager.ts +++ b/packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionManager.ts @@ -15,10 +15,12 @@ */ import { SessionManager, GetSessionOptions } from './types'; -import { SamlAuthConnector, SamlResponse } from '../AuthConnector/SamlAuthConnector'; +import { + SamlAuthConnector, + SamlResponse, +} from '../AuthConnector/SamlAuthConnector'; import { SessionStateTracker } from './SessionStateTracker'; - type Options = { connector: SamlAuthConnector; }; @@ -27,18 +29,15 @@ export class SamlAuthSessionManager implements SessionManager { private readonly connector: SamlAuthConnector; private readonly stateTracker = new SessionStateTracker(); - private currentSession: any | undefined; // FIXME: proper typing here + private currentSession: any | undefined; // FIXME: proper typing here? constructor(options: Options) { const { connector } = options; this.connector = connector; - // this.helper = new SessionScopeHelper } async getSession(options: GetSessionOptions): Promise { - // eslint-disable-next-line no-console - console.log('==> this is from SamlAuthSessionManager getSession()'); if (this.currentSession) { return this.currentSession; } diff --git a/packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionStore.ts b/packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionStore.ts index 53fba09b54..3afb07c4df 100644 --- a/packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionStore.ts +++ b/packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionStore.ts @@ -33,8 +33,6 @@ export class SamlAuthSessionStore implements SamlAuthSessionManager { } async getSession(options: GetSessionOptions): Promise { - // eslint-disable-next-line no-console - console.log('==>> this is from SamlAuthSessionStore getSession()'); const session = this.loadSession(); if (session) { diff --git a/packages/core/src/layout/SignInPage/commonProvider.tsx b/packages/core/src/layout/SignInPage/commonProvider.tsx index 88a53ec358..f9611342a2 100644 --- a/packages/core/src/layout/SignInPage/commonProvider.tsx +++ b/packages/core/src/layout/SignInPage/commonProvider.tsx @@ -36,11 +36,6 @@ const Component: ProviderComponent = ({ config, onResult }) => { instantPopup: true, }); - // eslint-disable-next-line no-console - console.log('====++++++++++++++++++++++++++> handleLogin for commonProvider'); - // eslint-disable-next-line no-console - console.log(identity); - const profile = await authApi.getProfile(); onResult({ userId: identity!.id, diff --git a/packages/core/src/layout/SignInPage/providers.tsx b/packages/core/src/layout/SignInPage/providers.tsx index 436078e49c..ff60eba228 100644 --- a/packages/core/src/layout/SignInPage/providers.tsx +++ b/packages/core/src/layout/SignInPage/providers.tsx @@ -26,7 +26,6 @@ import { SignInConfig, IdentityProviders, SignInProvider } from './types'; import { commonProvider } from './commonProvider'; import { guestProvider } from './guestProvider'; import { customProvider } from './customProvider'; -import { samlProvider } from './samlProvider'; const PROVIDER_STORAGE_KEY = '@backstage/core:SignInPage:provider'; @@ -42,7 +41,6 @@ const signInProviders: { [key: string]: SignInProvider } = { guest: guestProvider, custom: customProvider, common: commonProvider, - saml: samlProvider, }; function validateIDs(id: string, providers: SignInProviderType): void { diff --git a/packages/core/src/layout/SignInPage/samlProvider.tsx b/packages/core/src/layout/SignInPage/samlProvider.tsx deleted file mode 100644 index e9673bca8c..0000000000 --- a/packages/core/src/layout/SignInPage/samlProvider.tsx +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License 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 React from 'react'; -import { Grid, Typography, Button } from '@material-ui/core'; -import { ProviderComponent, ProviderLoader, SignInProvider } from './types'; -import { useApi, errorApiRef /* samlAuthApiRef */ } from '@backstage/core-api'; -import { InfoCard } from '../InfoCard/InfoCard'; - -const Component: ProviderComponent = ({ onResult }) => { - // const samlApi = useApi(samlAuthApiRef); - const errorApi = useApi(errorApiRef); - - const handleSSORedirect = () => { - // FIXME: this shoudl be from identity API or something. - window.open('http://localhost:7000/auth/saml/start'); - }; - - const receiveMessage = (event: any) => { - // FIXME: Should use the backstage identity API and not create this session storage. - window.sessionStorage.setItem( - 'helixSession', - JSON.stringify(event.data.response), - ); - - onResult({ - userId: event.data.response.backstageIdentity, - profile: { - email: event.data.response.profile.email, - displayName: event.data.response.profile.displayName, - }, - }); - }; - - const handleLogin = async () => { - try { - // FIXME: this should be handle through backstage API or something. - handleSSORedirect(); - window.addEventListener('message', receiveMessage, false); - } catch (error) { - errorApi.post(error); - } - }; - - return ( - - - Sign In - - } - > - Sign In using SAML lolololol - - - ); -}; - -const loader: ProviderLoader = async () => { - // FIXME: should get the profile from identiy API not from storage session. - const sessionRaw = window.sessionStorage.getItem('helixSession'); - - if (!sessionRaw) { - return undefined; - } - - const session = JSON.parse(sessionRaw); - - return { - userId: session.backstageIdentity, - profile: { - email: session.profile.email, - displayName: session.profile.displayName, - }, - }; -}; - -export const samlProvider: SignInProvider = { Component, loader }; diff --git a/packages/core/src/layout/SignInPage/types.ts b/packages/core/src/layout/SignInPage/types.ts index 74463f93b4..513fe9f674 100644 --- a/packages/core/src/layout/SignInPage/types.ts +++ b/packages/core/src/layout/SignInPage/types.ts @@ -36,7 +36,7 @@ export type SignInConfig = { | ApiRef; }; -export type IdentityProviders = ('guest' | 'custom' | 'saml' | SignInConfig)[]; +export type IdentityProviders = ('guest' | 'custom' | SignInConfig)[]; export type ProviderComponent = ComponentType< SignInPageProps & { config: SignInConfig } @@ -44,9 +44,9 @@ export type ProviderComponent = ComponentType< export type ProviderLoader = ( apis: ApiHolder, - apiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionStateApi - >, + apiRef: + | ApiRef + | ApiRef, ) => Promise; export type SignInProvider = { diff --git a/plugins/auth-backend/src/providers/saml/provider.ts b/plugins/auth-backend/src/providers/saml/provider.ts index fe6bb87b6d..f790228107 100644 --- a/plugins/auth-backend/src/providers/saml/provider.ts +++ b/plugins/auth-backend/src/providers/saml/provider.ts @@ -55,17 +55,6 @@ export class SamlAuthProvider implements AuthProviderRouteHandlers { // for non-oauth auth flows. // TODO: This flow doesn't issue an identity token that can be used to validate // the identity of the user in other backends, which we need in some form. - console.log('===>'); - console.log('===>'); - console.log('===>'); - console.log('===>'); - console.log('===>'); - console.log('===>'); - console.log('===>'); - console.log('===>'); - console.log('===>'); - console.log('===>'); - console.log('===>'); console.log('===> SamlAuthProvider constructor'); console.log(profile); done(undefined, { From 66b816cdf00153e215515efd140174dc4de32164 Mon Sep 17 00:00:00 2001 From: "J Shamsul Bahri (jibone))" Date: Fri, 4 Sep 2020 18:11:02 +0800 Subject: [PATCH 03/17] clean up config --- app-config.yaml | 134 ++++++++++++++++++++++++------------------------ 1 file changed, 66 insertions(+), 68 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index e65df93969..d6c745a6c2 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -28,74 +28,72 @@ sentry: auth: providers: - # google: - # development: - # appOrigin: 'http://localhost:3000/' - # secure: false - # clientId: - # $secret: - # env: AUTH_GOOGLE_CLIENT_ID - # clientSecret: - # $secret: - # env: AUTH_GOOGLE_CLIENT_SECRET - # github: - # development: - # appOrigin: 'http://localhost:3000/' - # secure: false - # clientId: - # $secret: - # env: AUTH_GITHUB_CLIENT_ID - # clientSecret: - # $secret: - # env: AUTH_GITHUB_CLIENT_SECRET - # enterpriseInstanceUrl: - # $secret: - # env: AUTH_GITHUB_ENTERPRISE_INSTANCE_URL - # gitlab: - # development: - # appOrigin: 'http://localhost:3000/' - # secure: false - # clientId: - # $secret: - # env: AUTH_GITLAB_CLIENT_ID - # clientSecret: - # $secret: - # env: AUTH_GITLAB_CLIENT_SECRET - # audience: - # $secret: - # env: GITLAB_BASE_URL + google: + development: + appOrigin: 'http://localhost:3000/' + secure: false + clientId: + $secret: + env: AUTH_GOOGLE_CLIENT_ID + clientSecret: + $secret: + env: AUTH_GOOGLE_CLIENT_SECRET + github: + development: + appOrigin: 'http://localhost:3000/' + secure: false + clientId: + $secret: + env: AUTH_GITHUB_CLIENT_ID + clientSecret: + $secret: + env: AUTH_GITHUB_CLIENT_SECRET + enterpriseInstanceUrl: + $secret: + env: AUTH_GITHUB_ENTERPRISE_INSTANCE_URL + gitlab: + development: + appOrigin: 'http://localhost:3000/' + secure: false + clientId: + $secret: + env: AUTH_GITLAB_CLIENT_ID + clientSecret: + $secret: + env: AUTH_GITLAB_CLIENT_SECRET + audience: + $secret: + env: GITLAB_BASE_URL saml: development: - entryPoint: 'https://sso.jumpcloud.com/saml2/backstage-test' + entryPoint: 'http://localhost:7001/' issuer: 'passport-saml' - # entryPoint: 'http://localhost:7001/' - # issuer: 'passport-saml' - # okta - # development: - # appOrigin: 'http://localhost:3000/' - # secure: false - # clientId: - # $secret: - # env: AUTH_OKTA_CLIENT_ID - # clientSecret: - # $secret: - # env: AUTH_OKTA_CLIENT_SECRET - # audience: - # $secret: - # env: AUTH_OKTA_AUDIENCE - # oauth2: - # development: - # appOrigin: 'http://localhost:3000/' - # secure: false - # clientId: - # $secret: - # env: AUTH_OAUTH2_CLIENT_ID - # clientSecret: - # $secret: - # env: AUTH_OAUTH2_CLIENT_SECRET - # authorizationURL: - # $secret: - # env: AUTH_OAUTH2_AUTH_URL - # tokenURL: - # $secret: - # env: AUTH_OAUTH2_TOKEN_URL + okta: + development: + appOrigin: 'http://localhost:3000/' + secure: false + clientId: + $secret: + env: AUTH_OKTA_CLIENT_ID + clientSecret: + $secret: + env: AUTH_OKTA_CLIENT_SECRET + audience: + $secret: + env: AUTH_OKTA_AUDIENCE + oauth2: + development: + appOrigin: 'http://localhost:3000/' + secure: false + clientId: + $secret: + env: AUTH_OAUTH2_CLIENT_ID + clientSecret: + $secret: + env: AUTH_OAUTH2_CLIENT_SECRET + authorizationURL: + $secret: + env: AUTH_OAUTH2_AUTH_URL + tokenURL: + $secret: + env: AUTH_OAUTH2_TOKEN_URL From 1cd640dc16c1169e85813fb8e041d271105de100 Mon Sep 17 00:00:00 2001 From: "J Shamsul Bahri (jibone))" Date: Fri, 4 Sep 2020 18:33:13 +0800 Subject: [PATCH 04/17] fix merge conflicts --- app-config.yaml | 34 ++----------------- packages/app/src/App.tsx | 3 -- packages/app/src/identityProviders.ts | 3 -- .../src/apis/implementations/auth/index.ts | 3 -- .../auth-backend/src/providers/factories.ts | 22 ------------ 5 files changed, 2 insertions(+), 63 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 7eb6023f61..2620f6b9b7 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -92,11 +92,6 @@ auth: providers: google: development: -<<<<<<< HEAD - appOrigin: 'http://localhost:3000/' - secure: false -======= ->>>>>>> master clientId: $secret: env: AUTH_GOOGLE_CLIENT_ID @@ -105,11 +100,6 @@ auth: env: AUTH_GOOGLE_CLIENT_SECRET github: development: -<<<<<<< HEAD - appOrigin: 'http://localhost:3000/' - secure: false -======= ->>>>>>> master clientId: $secret: env: AUTH_GITHUB_CLIENT_ID @@ -121,11 +111,6 @@ auth: env: AUTH_GITHUB_ENTERPRISE_INSTANCE_URL gitlab: development: -<<<<<<< HEAD - appOrigin: 'http://localhost:3000/' - secure: false -======= ->>>>>>> master clientId: $secret: env: AUTH_GITLAB_CLIENT_ID @@ -136,20 +121,10 @@ auth: $secret: env: GITLAB_BASE_URL saml: -<<<<<<< HEAD - development: - entryPoint: 'http://localhost:7001/' - issuer: 'passport-saml' + entryPoint: 'http://localhost:7001/' + issuer: 'passport-saml' okta: development: - appOrigin: 'http://localhost:3000/' - secure: false -======= - entryPoint: "http://localhost:7001/" - issuer: "passport-saml" - okta: - development: ->>>>>>> master clientId: $secret: env: AUTH_OKTA_CLIENT_ID @@ -161,11 +136,6 @@ auth: env: AUTH_OKTA_AUDIENCE oauth2: development: -<<<<<<< HEAD - appOrigin: 'http://localhost:3000/' - secure: false -======= ->>>>>>> master clientId: $secret: env: AUTH_OAUTH2_CLIENT_ID diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 53f7d93d3c..cb38726716 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -40,11 +40,8 @@ const app = createApp({ >>>>>> master /> ); }, diff --git a/packages/app/src/identityProviders.ts b/packages/app/src/identityProviders.ts index 767229eed4..388bc8d8b6 100644 --- a/packages/app/src/identityProviders.ts +++ b/packages/app/src/identityProviders.ts @@ -19,11 +19,8 @@ import { gitlabAuthApiRef, oktaAuthApiRef, githubAuthApiRef, -<<<<<<< HEAD samlAuthApiRef, -======= microsoftAuthApiRef, ->>>>>>> master } from '@backstage/core'; export const providers = [ diff --git a/packages/core-api/src/apis/implementations/auth/index.ts b/packages/core-api/src/apis/implementations/auth/index.ts index 9c6b8ff565..39223e8e15 100644 --- a/packages/core-api/src/apis/implementations/auth/index.ts +++ b/packages/core-api/src/apis/implementations/auth/index.ts @@ -19,9 +19,6 @@ export * from './gitlab'; export * from './google'; export * from './oauth2'; export * from './okta'; -<<<<<<< HEAD export * from './saml'; -======= export * from './auth0'; export * from './microsoft'; ->>>>>>> master diff --git a/plugins/auth-backend/src/providers/factories.ts b/plugins/auth-backend/src/providers/factories.ts index 948cc3eb8f..6c83dffda3 100644 --- a/plugins/auth-backend/src/providers/factories.ts +++ b/plugins/auth-backend/src/providers/factories.ts @@ -53,29 +53,7 @@ export const createAuthProviderRouter = ( const router = Router(); -<<<<<<< HEAD - for (const env of envs) { - const envConfig = providerConfig.getConfig(env); - console.log(envConfig); - const provider = factory(globalConfig, env, envConfig, logger, issuer); - if (provider) { - envProviders[env] = provider; - envIdentifier = provider.identifyEnv; - } - } - - if (typeof envIdentifier === 'undefined') { - throw Error(`No envIdentifier provided for '${providerId}'`); - } - - const handler = new EnvironmentHandler( - providerId, - envProviders, - envIdentifier, - ); -======= const handler = factory({ globalConfig, config, logger, tokenIssuer }); ->>>>>>> master router.get('/start', handler.start.bind(handler)); router.get('/handler/frame', handler.frameHandler.bind(handler)); From 41edd22db2dccdf99a8e4c2e25a03f4a29229256 Mon Sep 17 00:00:00 2001 From: "J Shamsul Bahri (jibone))" Date: Fri, 4 Sep 2020 18:44:30 +0800 Subject: [PATCH 05/17] Remove commented out log code. --- .../catalog-backend/src/ingestion/HigherOrderOperations.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts b/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts index 2b446c9cd6..0224dc7f1d 100644 --- a/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts +++ b/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts @@ -116,14 +116,14 @@ export class HigherOrderOperations implements HigherOrderOperation { * Entities are read from their respective sources, are parsed and validated * according to the entity policy, and get inserted or updated in the catalog. * Entities that have disappeared from their location are left orphaned, - * without changes.i + * without changes. */ async refreshAllLocations(): Promise { const startTimestamp = new Date().valueOf(); - // this.logger.info('Beginning locations refresh'); + this.logger.info('Beginning locations refresh'); const locations = await this.locationsCatalog.locations(); - // this.logger.info(`Visiting ${locations.length} locations`); + this.logger.info(`Visiting ${locations.length} locations`); for (const { data: location } of locations) { this.logger.debug( From bb8bc7dd42d1d07bd47a40e58351ad932e558b71 Mon Sep 17 00:00:00 2001 From: "J Shamsul Bahri (jibone))" Date: Mon, 21 Sep 2020 15:00:56 +0800 Subject: [PATCH 06/17] merge master and start using discoveryApi --- app-config.yaml | 5 +-- .../core-api/src/apis/definitions/auth.ts | 2 +- .../implementations/auth/saml/SamlAuth.ts | 12 +++---- .../lib/AuthConnector/SamlAuthConnector.ts | 32 ++++++++----------- packages/core/src/api-wrappers/defaultApis.ts | 9 ++++++ 5 files changed, 31 insertions(+), 29 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 5b704f9b8e..562a2b0e2e 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -131,8 +131,9 @@ auth: $secret: env: GITLAB_BASE_URL saml: - entryPoint: 'http://localhost:7001/' - issuer: 'passport-saml' + development: + entryPoint: 'http://localhost:7001/' + issuer: 'passport-saml' okta: development: clientId: diff --git a/packages/core-api/src/apis/definitions/auth.ts b/packages/core-api/src/apis/definitions/auth.ts index ec99bae6b2..933a78b693 100644 --- a/packages/core-api/src/apis/definitions/auth.ts +++ b/packages/core-api/src/apis/definitions/auth.ts @@ -332,5 +332,5 @@ export const oauth2ApiRef = createApiRef< */ export const samlAuthApiRef = createApiRef({ id: 'core.auth.saml', - description: 'provides authentication towards saml based provider', + description: 'Example of how to use SAML custom provider', }); diff --git a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts index cb93dcea97..aeede356ef 100644 --- a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts +++ b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts @@ -25,7 +25,7 @@ import { AuthRequestOptions, SamlApi, } from '../../../definitions/auth'; -import { AuthProvider } from '../../../definitions'; +import { AuthProvider, DiscoveryApi } from '../../../definitions'; import { SamlSession } from './types'; import { SamlAuthSessionManager, @@ -33,8 +33,7 @@ import { } from '../../../../lib/AuthSessionManager'; type CreateOptions = { - apiOrigin: string; - basePath: string; + discoveryApi: DiscoveryApi; environment?: string; provider?: AuthProvider & { id: string }; }; @@ -53,14 +52,12 @@ const DEFAULT_PROVIDER = { class SamlAuth implements SamlApi { static create({ - apiOrigin, - basePath, + discoveryApi, environment = 'development', provider = DEFAULT_PROVIDER, }: CreateOptions) { const connector = new SamlAuthConnector({ - apiOrigin, - basePath, + discoveryApi, environment, provider, }); @@ -74,7 +71,6 @@ class SamlAuth implements SamlApi { storageKey: 'samlSession', }); - // return new SamlAuth(authSessionStore); return new SamlAuth(authSessionStore); } diff --git a/packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts b/packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts index 4a6babe224..47d8f25820 100644 --- a/packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts +++ b/packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts @@ -17,15 +17,13 @@ import { AuthProvider, ProfileInfo, BackstageIdentity, + DiscoveryApi, } from '../../apis/definitions'; import { AuthConnector } from './types'; import { showLoginPopup } from '../loginPopup'; -const DEFAULT_BASE_PATH = '/api/auth'; - type Options = { - apiOrigin?: string; - basePath?: string; + discoveryApi: DiscoveryApi; environment?: string; provider: AuthProvider & { id: string }; }; @@ -38,30 +36,24 @@ export type SamlResponse = { export class SamlAuthConnector implements AuthConnector { - private readonly apiOrigin: string; - private readonly basePath: string; + private readonly discoveryApi: DiscoveryApi; private readonly environment: string | undefined; private readonly provider: AuthProvider & { id: string }; constructor(options: Options) { - const { - apiOrigin = window.location.origin, - basePath = DEFAULT_BASE_PATH, - environment, - provider, - } = options; + const { discoveryApi, environment, provider } = options; - this.apiOrigin = apiOrigin; - this.basePath = basePath; + this.discoveryApi = discoveryApi; this.environment = environment; this.provider = provider; } async createSession(): Promise { + const popupUrl = await this.buildUrl('/start'); const payload = await showLoginPopup({ - url: 'http://localhost:7000/auth/saml/start', // FIXME: remove hardcoded here. should do something like buildUrl + url: popupUrl, name: 'SAML Login', // FIXME: change this to provider name? and not hardcode the name - origin: this.apiOrigin, + origin: new URL(popupUrl).origin, width: 450, height: 730, }); @@ -76,8 +68,7 @@ export class SamlAuthConnector async refreshSession(): Promise {} async removeSession(): Promise { - // FIXME: remove hardcoded url here... should do something like buildUrl - const res = await fetch('http://localhost:7000/auth/saml/logout', { + const res = await fetch(await this.buildUrl('/logout'), { method: 'POST', headers: { 'x-requested-with': 'XMLHttpRequest', @@ -93,4 +84,9 @@ export class SamlAuthConnector throw error; } } + + private async buildUrl(path: string): Promise { + const baseUrl = await this.discoveryApi.getBaseUrl('auth'); + return `${baseUrl}/${this.provider.id}${path}?env=${this.environment}`; + } } diff --git a/packages/core/src/api-wrappers/defaultApis.ts b/packages/core/src/api-wrappers/defaultApis.ts index a3f0cb0251..dedadf0456 100644 --- a/packages/core/src/api-wrappers/defaultApis.ts +++ b/packages/core/src/api-wrappers/defaultApis.ts @@ -44,6 +44,8 @@ import { createApiFactory, configApiRef, UrlPatternDiscovery, + samlAuthApiRef, + SamlAuth, } from '@backstage/core-api'; export const defaultApis = [ @@ -132,4 +134,11 @@ export const defaultApis = [ factory: ({ discoveryApi, oauthRequestApi }) => OAuth2.create({ discoveryApi, oauthRequestApi }), }), + createApiFactory({ + api: samlAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + }, + factory: ({ discoveryApi }) => SamlAuth.create({ discoveryApi }), + }), ]; From 5288ce0deeddfce29857a39a01c71864fc84b296 Mon Sep 17 00:00:00 2001 From: "J Shamsul Bahri (jibone))" Date: Mon, 21 Sep 2020 17:43:05 +0800 Subject: [PATCH 07/17] Remove getAccessToken func --- app-config.yaml | 5 ++--- packages/core-api/src/apis/definitions/auth.ts | 4 ---- .../src/apis/implementations/auth/saml/SamlAuth.ts | 6 ------ .../core-api/src/lib/AuthConnector/SamlAuthConnector.ts | 9 ++------- 4 files changed, 4 insertions(+), 20 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 6fda32f175..60ecb813f7 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -160,9 +160,8 @@ auth: $secret: env: GITLAB_BASE_URL saml: - development: - entryPoint: 'http://localhost:7001/' - issuer: 'passport-saml' + entryPoint: 'http://localhost:7001/' + issuer: 'passport-saml' okta: development: clientId: diff --git a/packages/core-api/src/apis/definitions/auth.ts b/packages/core-api/src/apis/definitions/auth.ts index 933a78b693..b536d74801 100644 --- a/packages/core-api/src/apis/definitions/auth.ts +++ b/packages/core-api/src/apis/definitions/auth.ts @@ -101,16 +101,12 @@ export type OAuthApi = { * This API provides access to SAML 2 credentials. Verify user access with identity provider. */ export type SamlApi = { - // Not sure what Promise call back should have. getBackstageIdentity( options?: AuthRequestOptions, ): Promise; getProfile(options?: AuthRequestOptions): Promise; - // FIXME: is this needed? - getAccessToken(options?: AuthRequestOptions): Promise; - // Not sure if this is needed. logout(): Promise; }; diff --git a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts index aeede356ef..c06a6274fd 100644 --- a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts +++ b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts @@ -92,12 +92,6 @@ class SamlAuth implements SamlApi { return session?.profile; } - // FIXME: Is this needed?... - async getAccessToken(options: AuthRequestOptions) { - const session = await this.sessionManager.getSession(options); - return session?.userId ?? ''; - } - async logout() { await this.sessionManager.removeSession(); } diff --git a/packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts b/packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts index 47d8f25820..30de81536d 100644 --- a/packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts +++ b/packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts @@ -19,7 +19,6 @@ import { BackstageIdentity, DiscoveryApi, } from '../../apis/definitions'; -import { AuthConnector } from './types'; import { showLoginPopup } from '../loginPopup'; type Options = { @@ -34,8 +33,7 @@ export type SamlResponse = { backstageIdentity: BackstageIdentity; }; -export class SamlAuthConnector - implements AuthConnector { +export class SamlAuthConnector { private readonly discoveryApi: DiscoveryApi; private readonly environment: string | undefined; private readonly provider: AuthProvider & { id: string }; @@ -52,7 +50,7 @@ export class SamlAuthConnector const popupUrl = await this.buildUrl('/start'); const payload = await showLoginPopup({ url: popupUrl, - name: 'SAML Login', // FIXME: change this to provider name? and not hardcode the name + name: `${this.provider.title} Login`, origin: new URL(popupUrl).origin, width: 450, height: 730, @@ -64,9 +62,6 @@ export class SamlAuthConnector }; } - // FIXME: do we need this for SAML? - async refreshSession(): Promise {} - async removeSession(): Promise { const res = await fetch(await this.buildUrl('/logout'), { method: 'POST', From 70dc0627aa770680c7abb57dc01f2a58491e6744 Mon Sep 17 00:00:00 2001 From: "J Shamsul Bahri (jibone))" Date: Mon, 21 Sep 2020 17:53:18 +0800 Subject: [PATCH 08/17] remove the console.log --- plugins/auth-backend/src/providers/saml/provider.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/auth-backend/src/providers/saml/provider.ts b/plugins/auth-backend/src/providers/saml/provider.ts index aac42d6bd9..2163d50f37 100644 --- a/plugins/auth-backend/src/providers/saml/provider.ts +++ b/plugins/auth-backend/src/providers/saml/provider.ts @@ -53,8 +53,6 @@ export class SamlAuthProvider implements AuthProviderRouteHandlers { // for non-oauth auth flows. // TODO: This flow doesn't issue an identity token that can be used to validate // the identity of the user in other backends, which we need in some form. - console.log('===> SamlAuthProvider constructor'); - console.log(profile); done(undefined, { userId: profile.nameID!, profile: { @@ -76,9 +74,9 @@ export class SamlAuthProvider implements AuthProviderRouteHandlers { ): Promise { try { const { - response: { userId, profile }, + response: { userId, profile }, } = await executeFrameHandlerStrategy(req, this.strategy); - + const id = userId; const idToken = await this.tokenIssuer.issueToken({ claims: { sub: id }, From e2d719f502eaf166f6473ef14a5d9d02c9e0495c Mon Sep 17 00:00:00 2001 From: "J Shamsul Bahri (jibone))" Date: Mon, 21 Sep 2020 18:48:13 +0800 Subject: [PATCH 09/17] switch to use generic sessionmanager and storage --- .../implementations/auth/saml/SamlAuth.ts | 8 +- .../AuthSessionManager/AuthSessionStore.ts | 2 +- .../SamlAuthSessionManager.ts | 63 ------------- .../SamlAuthSessionStore.ts | 94 ------------------- .../StaticAuthSessionManager.ts | 2 +- .../src/lib/AuthSessionManager/common.ts | 7 +- .../src/lib/AuthSessionManager/index.ts | 2 - 7 files changed, 11 insertions(+), 167 deletions(-) delete mode 100644 packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionManager.ts delete mode 100644 packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionStore.ts diff --git a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts index c06a6274fd..6e0bd0af10 100644 --- a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts +++ b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts @@ -28,8 +28,8 @@ import { import { AuthProvider, DiscoveryApi } from '../../../definitions'; import { SamlSession } from './types'; import { - SamlAuthSessionManager, - SamlAuthSessionStore, + AuthSessionStore, + StaticAuthSessionManager, } from '../../../../lib/AuthSessionManager'; type CreateOptions = { @@ -62,11 +62,11 @@ class SamlAuth implements SamlApi { provider, }); - const sessionManager = new SamlAuthSessionManager({ + const sessionManager = new StaticAuthSessionManager({ connector, }); - const authSessionStore = new SamlAuthSessionStore({ + const authSessionStore = new AuthSessionStore({ manager: sessionManager, storageKey: 'samlSession', }); diff --git a/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.ts b/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.ts index f2b8558f74..e82557b1ce 100644 --- a/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.ts +++ b/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.ts @@ -28,7 +28,7 @@ type Options = { /** Storage key to use to store sessions */ storageKey: string; /** Used to get the scope of the session */ - sessionScopes: SessionScopesFunc; + sessionScopes?: SessionScopesFunc; /** Used to check if the session needs to be refreshed, defaults to never refresh */ sessionShouldRefresh?: SessionShouldRefreshFunc; }; diff --git a/packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionManager.ts b/packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionManager.ts deleted file mode 100644 index 8a21bc05b9..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionManager.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License 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 { SessionManager, GetSessionOptions } from './types'; -import { - SamlAuthConnector, - SamlResponse, -} from '../AuthConnector/SamlAuthConnector'; -import { SessionStateTracker } from './SessionStateTracker'; - -type Options = { - connector: SamlAuthConnector; -}; - -export class SamlAuthSessionManager implements SessionManager { - private readonly connector: SamlAuthConnector; - private readonly stateTracker = new SessionStateTracker(); - - private currentSession: any | undefined; // FIXME: proper typing here? - - constructor(options: Options) { - const { connector } = options; - - this.connector = connector; - } - - async getSession(options: GetSessionOptions): Promise { - if (this.currentSession) { - return this.currentSession; - } - - if (options.optional) { - return undefined; - } - - this.currentSession = await this.connector.createSession(); - this.stateTracker.setIsSignedIn(true); - return this.currentSession; - } - - async removeSession() { - this.currentSession = undefined; - await this.connector.removeSession(); - this.stateTracker.setIsSignedIn(false); - } - - sessionState$() { - return this.stateTracker.sessionState$(); - } -} diff --git a/packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionStore.ts b/packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionStore.ts deleted file mode 100644 index 3afb07c4df..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/SamlAuthSessionStore.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License 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 { SamlAuthSessionManager, GetSessionOptions } from './types'; - -type Options = { - manager: SamlAuthSessionManager; - storageKey: string; -}; - -export class SamlAuthSessionStore implements SamlAuthSessionManager { - private readonly manager: SamlAuthSessionManager; - private readonly storageKey: string; - - constructor(options: Options) { - const { manager, storageKey } = options; - - this.manager = manager; - this.storageKey = storageKey; - } - - async getSession(options: GetSessionOptions): Promise { - const session = this.loadSession(); - - if (session) { - return session!; - } - - const newSession = await this.manager.getSession(options); - this.saveSession(newSession); - return newSession; - } - - async removeSession() { - localStorage.removeItem(this.storageKey); - await this.manager.removeSession(); - } - - sessionState$() { - return this.manager.sessionState$(); - } - - private saveSession(session: T | undefined) { - if (session === undefined) { - localStorage.removeItem(this.storageKey); - } else { - localStorage.setItem( - this.storageKey, - JSON.stringify(session, (_key, value) => { - if (value instanceof Set) { - return { - __type: 'Set', - __value: Array.from(value), - }; - } - return value; - }), - ); - } - } - - private loadSession(): T | undefined { - try { - const sessionJson = localStorage.getItem(this.storageKey); - if (sessionJson) { - const session = JSON.parse(sessionJson, (_key, value) => { - if (value?.__type === 'Set') { - return new Set(value.__value); - } - return value; - }); - return session; - } - - return undefined; - } catch (error) { - localStorage.removeItem(this.storageKey); - return undefined; - } - } -} diff --git a/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.ts b/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.ts index e59c11421c..e02b600828 100644 --- a/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.ts +++ b/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.ts @@ -23,7 +23,7 @@ type Options = { /** The connector used for acting on the auth session */ connector: AuthConnector; /** Used to get the scope of the session */ - sessionScopes: (session: T) => Set; + sessionScopes?: (session: T) => Set; /** The default scopes that should always be present in a session, defaults to none. */ defaultScopes?: Set; }; diff --git a/packages/core-api/src/lib/AuthSessionManager/common.ts b/packages/core-api/src/lib/AuthSessionManager/common.ts index 83b81bc81a..ff2897535d 100644 --- a/packages/core-api/src/lib/AuthSessionManager/common.ts +++ b/packages/core-api/src/lib/AuthSessionManager/common.ts @@ -29,7 +29,7 @@ export function hasScopes( } type ScopeHelperOptions = { - sessionScopes: SessionScopesFunc; + sessionScopes: SessionScopesFunc | undefined; defaultScopes?: Set; }; @@ -46,13 +46,16 @@ export class SessionScopeHelper { if (!scopes) { return true; } + if (this.options.sessionScopes === undefined) { + return true; + } const sessionScopes = this.options.sessionScopes(session); return hasScopes(sessionScopes, scopes); } getExtendedScope(session: T | undefined, scopes?: Set) { const newScope = new Set(this.options.defaultScopes); - if (session) { + if (session && this.options.sessionScopes !== undefined) { const sessionScopes = this.options.sessionScopes(session); for (const scope of sessionScopes) { newScope.add(scope); diff --git a/packages/core-api/src/lib/AuthSessionManager/index.ts b/packages/core-api/src/lib/AuthSessionManager/index.ts index a452ac0055..5f4dde8662 100644 --- a/packages/core-api/src/lib/AuthSessionManager/index.ts +++ b/packages/core-api/src/lib/AuthSessionManager/index.ts @@ -16,7 +16,5 @@ export { RefreshingAuthSessionManager } from './RefreshingAuthSessionManager'; export { StaticAuthSessionManager } from './StaticAuthSessionManager'; -export { SamlAuthSessionManager } from './SamlAuthSessionManager'; export { AuthSessionStore } from './AuthSessionStore'; -export { SamlAuthSessionStore } from './SamlAuthSessionStore'; export * from './types'; From eccef941eabfabcd26d10108cc9b983c495d519c Mon Sep 17 00:00:00 2001 From: "J Shamsul Bahri (jibone))" Date: Mon, 21 Sep 2020 18:51:24 +0800 Subject: [PATCH 10/17] remove type that is not used --- packages/core-api/src/lib/AuthSessionManager/types.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/core-api/src/lib/AuthSessionManager/types.ts b/packages/core-api/src/lib/AuthSessionManager/types.ts index 1be439a840..804c7121e1 100644 --- a/packages/core-api/src/lib/AuthSessionManager/types.ts +++ b/packages/core-api/src/lib/AuthSessionManager/types.ts @@ -36,13 +36,6 @@ export type SessionManager = { sessionState$(): Observable; }; -// FIXME do we need this? -export type SamlAuthSessionManager = { - getSession(options: GetSessionOptions): Promise; - removeSession(): Promise; - sessionState$(): Observable; -}; - /** * A function called to determine the scopes of a session. */ From 113ac17c2e5410844892a09e8805b8dd147e0cf2 Mon Sep 17 00:00:00 2001 From: "J Shamsul Bahri (jibone))" Date: Tue, 22 Sep 2020 12:23:09 +0800 Subject: [PATCH 11/17] fix tsc error --- packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts b/packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts index 30de81536d..e8ab412756 100644 --- a/packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts +++ b/packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts @@ -62,6 +62,8 @@ export class SamlAuthConnector { }; } + async refreshSession(): Promise {} + async removeSession(): Promise { const res = await fetch(await this.buildUrl('/logout'), { method: 'POST', From 40a6a4ee23c0bd0e864b49027fe7621fffb71e01 Mon Sep 17 00:00:00 2001 From: "J Shamsul Bahri (jibone))" Date: Tue, 22 Sep 2020 14:47:16 +0800 Subject: [PATCH 12/17] add sidebar option --- .../src/apis/implementations/auth/saml/SamlAuth.ts | 5 +++++ .../core/src/layout/Sidebar/DefaultProviderSettings.tsx | 8 ++++++++ .../src/layout/Sidebar/Settings/OIDCProviderSettings.tsx | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts index 6e0bd0af10..2c8c98ecf6 100644 --- a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts +++ b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts @@ -87,6 +87,11 @@ class SamlAuth implements SamlApi { return session?.backstageIdentity; } + async getIdToken(options: AuthRequestOptions = {}) { + const session = await this.sessionManager.getSession(options); + return session?.backstageIdentity ?? ''; + } + async getProfile(options: AuthRequestOptions = {}) { const session = await this.sessionManager.getSession(options); return session?.profile; diff --git a/packages/core/src/layout/Sidebar/DefaultProviderSettings.tsx b/packages/core/src/layout/Sidebar/DefaultProviderSettings.tsx index 293cefb19a..246ce722a1 100644 --- a/packages/core/src/layout/Sidebar/DefaultProviderSettings.tsx +++ b/packages/core/src/layout/Sidebar/DefaultProviderSettings.tsx @@ -21,6 +21,7 @@ import { oauth2ApiRef, oktaAuthApiRef, microsoftAuthApiRef, + samlAuthApiRef, useApi, } from '@backstage/core-api'; import Star from '@material-ui/icons/Star'; @@ -69,6 +70,13 @@ export const DefaultProviderSettings = () => { icon={Star} /> )} + {providers.includes('saml') && ( + + )} {providers.includes('oauth2') && ( ; + apiRef: ApiRef | ApiRef; }; export const OIDCProviderSettings: FC = ({ From 60fdb3eda1ff6604fb4510a0476823faf41051bf Mon Sep 17 00:00:00 2001 From: "J Shamsul Bahri (jibone))" Date: Tue, 22 Sep 2020 16:02:00 +0800 Subject: [PATCH 13/17] remove comment --- plugins/auth-backend/src/providers/saml/provider.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/auth-backend/src/providers/saml/provider.ts b/plugins/auth-backend/src/providers/saml/provider.ts index 2163d50f37..82e0acbdcc 100644 --- a/plugins/auth-backend/src/providers/saml/provider.ts +++ b/plugins/auth-backend/src/providers/saml/provider.ts @@ -111,7 +111,6 @@ export class SamlAuthProvider implements AuthProviderRouteHandlers { return reqEnv; } - // FIXME : do we want to always to return 'development' ? return 'development'; } } From 7258144cf9c083e582a36f01fa67630e7a46cce5 Mon Sep 17 00:00:00 2001 From: "J Shamsul Bahri (jibone))" Date: Wed, 23 Sep 2020 23:25:46 +0800 Subject: [PATCH 14/17] refactor to use sessionApi --- packages/core-api/src/apis/definitions/auth.ts | 18 +++--------------- .../apis/implementations/auth/saml/SamlAuth.ts | 17 +++++++++++------ 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/packages/core-api/src/apis/definitions/auth.ts b/packages/core-api/src/apis/definitions/auth.ts index 076a7479d6..109f15b26e 100644 --- a/packages/core-api/src/apis/definitions/auth.ts +++ b/packages/core-api/src/apis/definitions/auth.ts @@ -92,20 +92,6 @@ export type OAuthApi = { ): Promise; }; -/** - * This API provides access to SAML 2 credentials. Verify user access with identity provider. - */ -export type SamlApi = { - getBackstageIdentity( - options?: AuthRequestOptions, - ): Promise; - - getProfile(options?: AuthRequestOptions): Promise; - - // Not sure if this is needed. - logout(): Promise; -}; - /** * This API provides access to OpenID Connect credentials. It lets you request ID tokens, * which can be passed to backend services to prove the user's identity. @@ -328,7 +314,9 @@ export const oauth2ApiRef = createApiRef< /** * Provides authentication for saml based identity providers */ -export const samlAuthApiRef = createApiRef({ +export const samlAuthApiRef = createApiRef< + ProfileInfoApi & BackstageIdentityApi & SessionApi +>({ id: 'core.auth.saml', description: 'Example of how to use SAML custom provider', }); diff --git a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts index 2c8c98ecf6..433d3befe8 100644 --- a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts +++ b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts @@ -23,7 +23,9 @@ import { BackstageIdentity, SessionState, AuthRequestOptions, - SamlApi, + ProfileInfoApi, + BackstageIdentityApi, + SessionApi, } from '../../../definitions/auth'; import { AuthProvider, DiscoveryApi } from '../../../definitions'; import { SamlSession } from './types'; @@ -50,7 +52,7 @@ const DEFAULT_PROVIDER = { icon: SamlIcon, }; -class SamlAuth implements SamlApi { +class SamlAuth implements ProfileInfoApi, BackstageIdentityApi, SessionApi { static create({ discoveryApi, environment = 'development', @@ -80,6 +82,13 @@ class SamlAuth implements SamlApi { constructor(private readonly sessionManager: SessionManager) {} + async signIn() { + await this.getBackstageIdentity({}); + } + async signOut() { + await this.sessionManager.removeSession(); + } + async getBackstageIdentity( options: AuthRequestOptions, ): Promise { @@ -96,10 +105,6 @@ class SamlAuth implements SamlApi { const session = await this.sessionManager.getSession(options); return session?.profile; } - - async logout() { - await this.sessionManager.removeSession(); - } } export default SamlAuth; From 32def99071323d4dd681af37cbe965349f84845b Mon Sep 17 00:00:00 2001 From: "J Shamsul Bahri (jibone))" Date: Thu, 24 Sep 2020 10:45:11 +0800 Subject: [PATCH 15/17] Remove getTokenId and userId --- .../core-api/src/apis/implementations/auth/saml/SamlAuth.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts index 433d3befe8..dbd9368842 100644 --- a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts +++ b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts @@ -41,7 +41,6 @@ type CreateOptions = { }; export type SamlAuthResponse = { - userId: string; profile: ProfileInfo; backstageIdentity: BackstageIdentity; }; @@ -96,11 +95,6 @@ class SamlAuth implements ProfileInfoApi, BackstageIdentityApi, SessionApi { return session?.backstageIdentity; } - async getIdToken(options: AuthRequestOptions = {}) { - const session = await this.sessionManager.getSession(options); - return session?.backstageIdentity ?? ''; - } - async getProfile(options: AuthRequestOptions = {}) { const session = await this.sessionManager.getSession(options); return session?.profile; From e3b504798af83337c3b1c88d6f2fc26f0f3be540 Mon Sep 17 00:00:00 2001 From: "J Shamsul Bahri (jibone))" Date: Thu, 24 Sep 2020 11:28:43 +0800 Subject: [PATCH 16/17] revert identifyEnv implementation --- plugins/auth-backend/src/providers/saml/provider.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/plugins/auth-backend/src/providers/saml/provider.ts b/plugins/auth-backend/src/providers/saml/provider.ts index 82e0acbdcc..a90f3e3053 100644 --- a/plugins/auth-backend/src/providers/saml/provider.ts +++ b/plugins/auth-backend/src/providers/saml/provider.ts @@ -105,13 +105,8 @@ export class SamlAuthProvider implements AuthProviderRouteHandlers { res.send('noop'); } - identifyEnv(req: express.Request): string | undefined { - const reqEnv = req.query.env?.toString(); - if (reqEnv) { - return reqEnv; - } - - return 'development'; + identifyEnv(): string | undefined { + return undefined; } } From aa970ab49b136509f197df921ad245be80a6ca50 Mon Sep 17 00:00:00 2001 From: "J Shamsul Bahri (jibone))" Date: Fri, 25 Sep 2020 17:02:17 +0800 Subject: [PATCH 17/17] rename SamlAuthConnector to DirectAuthConnector --- .../core-api/src/apis/implementations/auth/saml/SamlAuth.ts | 4 ++-- .../{SamlAuthConnector.ts => DirectAuthConnector.ts} | 6 +++--- packages/core-api/src/lib/AuthConnector/index.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) rename packages/core-api/src/lib/AuthConnector/{SamlAuthConnector.ts => DirectAuthConnector.ts} (94%) diff --git a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts index dbd9368842..973b402756 100644 --- a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts +++ b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts @@ -15,7 +15,7 @@ */ import SamlIcon from '@material-ui/icons/AcUnit'; -import { SamlAuthConnector } from '../../../../lib/AuthConnector'; +import { DirectAuthConnector } from '../../../../lib/AuthConnector'; import { SessionManager } from '../../../../lib/AuthSessionManager/types'; import { Observable } from '../../../../types'; import { @@ -57,7 +57,7 @@ class SamlAuth implements ProfileInfoApi, BackstageIdentityApi, SessionApi { environment = 'development', provider = DEFAULT_PROVIDER, }: CreateOptions) { - const connector = new SamlAuthConnector({ + const connector = new DirectAuthConnector({ discoveryApi, environment, provider, diff --git a/packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts b/packages/core-api/src/lib/AuthConnector/DirectAuthConnector.ts similarity index 94% rename from packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts rename to packages/core-api/src/lib/AuthConnector/DirectAuthConnector.ts index e8ab412756..517bf82ae7 100644 --- a/packages/core-api/src/lib/AuthConnector/SamlAuthConnector.ts +++ b/packages/core-api/src/lib/AuthConnector/DirectAuthConnector.ts @@ -27,13 +27,13 @@ type Options = { provider: AuthProvider & { id: string }; }; -export type SamlResponse = { +export type DirectAuthResponse = { userId: string; profile: ProfileInfo; backstageIdentity: BackstageIdentity; }; -export class SamlAuthConnector { +export class DirectAuthConnector { private readonly discoveryApi: DiscoveryApi; private readonly environment: string | undefined; private readonly provider: AuthProvider & { id: string }; @@ -46,7 +46,7 @@ export class SamlAuthConnector { this.provider = provider; } - async createSession(): Promise { + async createSession(): Promise { const popupUrl = await this.buildUrl('/start'); const payload = await showLoginPopup({ url: popupUrl, diff --git a/packages/core-api/src/lib/AuthConnector/index.ts b/packages/core-api/src/lib/AuthConnector/index.ts index 84f5067443..388619e2c1 100644 --- a/packages/core-api/src/lib/AuthConnector/index.ts +++ b/packages/core-api/src/lib/AuthConnector/index.ts @@ -15,5 +15,5 @@ */ export { DefaultAuthConnector } from './DefaultAuthConnector'; -export { SamlAuthConnector } from './SamlAuthConnector'; +export { DirectAuthConnector } from './DirectAuthConnector'; export * from './types';