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"