Merge pull request #29470 from ioboi/auth-backend-module-openshift-provider
Init auth-backend-module-openshift-provider [ContribFest]
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/app-defaults': minor
|
||||
---
|
||||
|
||||
Add and configure the OpenShift authentication provider to the default APIs.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-plugin-api': minor
|
||||
---
|
||||
|
||||
Make `openshiftAuthApiRef` available in `@backstage/core-plugin-api`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-app': minor
|
||||
---
|
||||
|
||||
Add implementation of OpenShift authentication provider.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-user-settings': patch
|
||||
---
|
||||
|
||||
Add the OpenShift authenticator provider to the default `user-settings` providers page.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend-module-openshift-provider': minor
|
||||
---
|
||||
|
||||
Add new `auth-backend-module-openshift-provider`. This authentication provider enables Backstage to sign in with OpenShift.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/frontend-plugin-api': minor
|
||||
---
|
||||
|
||||
Make `openshiftApiRef` available to the new frontend system.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-app-api': minor
|
||||
---
|
||||
|
||||
Add `OpenShiftAuth` helper to create default OAuth flow for OpenShift.
|
||||
@@ -34,6 +34,7 @@ Backstage comes with many common authentication providers in the core library:
|
||||
- [Okta](okta/provider.md)
|
||||
- [OAuth 2 Custom Proxy](oauth2-proxy/provider.md)
|
||||
- [OneLogin](onelogin/provider.md)
|
||||
- [OpenShift](openshift/provider.md)
|
||||
- [VMware Cloud](vmware-cloud/provider.md)
|
||||
|
||||
These built-in providers handle the authentication flow for a particular service, including required scopes, callbacks, etc. These providers are each added to a
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
---
|
||||
id: provider
|
||||
title: OpenShift Authentication Provider
|
||||
sidebar_label: OpenShift
|
||||
description: Adding OpenShift OAuth as an authentication provider in Backstage
|
||||
---
|
||||
|
||||
The Backstage `core-plugin-api` package comes with a OpenShift authentication
|
||||
provider that can authenticate users using OpenShift OAuth.
|
||||
|
||||
## Use Case
|
||||
|
||||
This setup enables the [Kubernetes plugin](../../features/kubernetes/index.md) to access OpenShift clusters using the user's permissions,
|
||||
leveraging OAuth 2.0 _On-Behalf-Of_ flow via the [Kubernetes Client Side Provider](../../features/kubernetes/authentication.md).
|
||||
|
||||
To make this work, the corresponding `User` entities must exist in the Backstage catalog,
|
||||
and their names must match the OpenShift users.
|
||||
|
||||
Although the OpenShift authentication provider does not support OIDC natively,
|
||||
you can still configure it for use with the Kubernetes integration by treating it as an OIDC provider
|
||||
in the `KubernetesAuthProviders` configuration.
|
||||
|
||||
```ts title="packages/app/src/apis.ts"
|
||||
import {
|
||||
KubernetesAuthProviders,
|
||||
kubernetesAuthProvidersApiRef,
|
||||
} from '@backstage/plugin-kubernetes';
|
||||
import {
|
||||
googleAuthApiRef,
|
||||
microsoftAuthApiRef,
|
||||
openshiftAuthApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
export const apis: AnyApiFactory[] = [
|
||||
// ...
|
||||
createApiFactory({
|
||||
api: kubernetesAuthProvidersApiRef,
|
||||
deps: {
|
||||
microsoftAuthApi: microsoftAuthApiRef,
|
||||
googleAuthApi: googleAuthApiRef,
|
||||
openshiftAuthApi: openshiftAuthApiRef,
|
||||
},
|
||||
factory({ microsoftAuthApi, googleAuthApi, openshiftAuthApi }) {
|
||||
return new KubernetesAuthProviders({
|
||||
microsoftAuthApi,
|
||||
googleAuthApi,
|
||||
oidcProviders: {
|
||||
openshift: {
|
||||
async getIdToken(_) {
|
||||
return await openshiftAuthApi.getAccessToken('user:full');
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
//...
|
||||
];
|
||||
```
|
||||
|
||||
:::note Note
|
||||
|
||||
The OpenShift auth API does **not** implement the `OpenIdConnectApi` interface. In other words, it does **not** return an ID token.
|
||||
Instead, it returns an **access token**, which is used by the Kubernetes integration in place of an ID token.
|
||||
This is the only functional difference from the standard OIDC-based authentication flow.
|
||||
|
||||
:::
|
||||
|
||||
## Create an OAuth client in OpenShift
|
||||
|
||||
Make sure that an OAuth client exists in the OpenShift cluster.
|
||||
|
||||
To configure the OpenShift integration, create an [`OAuthClient`](https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/authentication_and_authorization/configuring-oauth-clients).
|
||||
|
||||
The redirect URI must be in the following format: `https://<fqdn>/api/auth/openshift/handler/frame`.
|
||||
|
||||
## Configuration
|
||||
|
||||
The provider configuration can then be added to your `app-config.yaml` under the
|
||||
root `auth` configuration:
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
environment: development
|
||||
providers:
|
||||
openshift:
|
||||
development:
|
||||
clientId: ${AUTH_OPENSHIFT_CLIENT_ID}
|
||||
clientSecret: ${AUTH_OPENSHIFT_CLIENT_SECRET}
|
||||
authorizationUrl: ${AUTH_OPENSHIFT_AUTHORIZATION_URL}
|
||||
tokenUrl: ${AUTH_OPENSHIFT_TOKEN_URL}
|
||||
openshiftApiServerUrl: ${OPENSHIFT_API_SERVER_URL}
|
||||
## uncomment to set lifespan of user session
|
||||
# sessionDuration: { hours: 24 } # supports `ms` library format (e.g. '24h', '2 days'), ISO duration, "human duration" as used in code
|
||||
# sessionDuration: 1d
|
||||
signIn:
|
||||
resolvers:
|
||||
- resolver: displayNameMatchingUserEntityName
|
||||
```
|
||||
|
||||
The OpenShift provider is a structure with these configuration keys:
|
||||
|
||||
- `clientId`: The client ID of your OpenShift OAuth client, e.g., `my-backstage`
|
||||
- `clientSecret`: The client secret tied to the OpenShift OAuth client.
|
||||
- `authorizationUrl`: The OpenShift OAuth client auth endpoint, format: `https://<oauth-client-route>/oauth/authorize`.
|
||||
- `tokenUrl`: The OpenShift OAuth client token endpoint, format: `https://<oauth-client-route>/oauth/token`.
|
||||
- `openshiftApiServerUrl`: The OpenShift API server endpoint, format: `https://<openshift-api>`.
|
||||
- `sessionDuration`: (optional): Lifespan of the user session.
|
||||
- `signIn`: The configuration for the sign-in process, including the **resolvers**
|
||||
that should be used to match the user from the auth provider with the user
|
||||
entity in the Backstage catalog (typically a single resolver is sufficient).
|
||||
|
||||
The provider needs to use the scope **user:full**.
|
||||
|
||||
## Backend Installation
|
||||
|
||||
To add the provider to the backend we will first need to install the package by running this command:
|
||||
|
||||
```bash title="from your Backstage root directory"
|
||||
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-openshift-provider
|
||||
```
|
||||
|
||||
Then we will need to add this line:
|
||||
|
||||
```ts title="in packages/backend/src/index.ts"
|
||||
backend.add(import('@backstage/plugin-auth-backend'));
|
||||
/* highlight-add-start */
|
||||
backend.add(import('@backstage/plugin-auth-backend-module-openshift-provider'));
|
||||
/* highlight-add-end */
|
||||
```
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
FetchMiddlewares,
|
||||
VMwareCloudAuth,
|
||||
FrontendHostDiscovery,
|
||||
OpenShiftAuth,
|
||||
} from '@backstage/core-app-api';
|
||||
|
||||
import {
|
||||
@@ -58,6 +59,7 @@ import {
|
||||
bitbucketServerAuthApiRef,
|
||||
atlassianAuthApiRef,
|
||||
vmwareCloudAuthApiRef,
|
||||
openshiftAuthApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import {
|
||||
permissionApiRef,
|
||||
@@ -275,6 +277,22 @@ export const apis = [
|
||||
});
|
||||
},
|
||||
}),
|
||||
createApiFactory({
|
||||
api: openshiftAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) => {
|
||||
return OpenShiftAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
});
|
||||
},
|
||||
}),
|
||||
createApiFactory({
|
||||
api: permissionApiRef,
|
||||
deps: {
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
oneloginAuthApiRef,
|
||||
bitbucketAuthApiRef,
|
||||
bitbucketServerAuthApiRef,
|
||||
openshiftAuthApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
export const providers = [
|
||||
@@ -74,4 +75,10 @@ export const providers = [
|
||||
message: 'Sign In using Bitbucket Server',
|
||||
apiRef: bitbucketServerAuthApiRef,
|
||||
},
|
||||
{
|
||||
id: 'openshift-auth-provider',
|
||||
title: 'OpenShift',
|
||||
message: 'Sign In using OpenShift',
|
||||
apiRef: openshiftAuthApiRef,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"@backstage/plugin-auth-backend": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-github-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-guest-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-openshift-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-node": "workspace:^",
|
||||
"@backstage/plugin-catalog-backend": "workspace:^",
|
||||
"@backstage/plugin-catalog-backend-module-backstage-openapi": "workspace:^",
|
||||
|
||||
@@ -33,6 +33,7 @@ const searchLoader = createBackendFeatureLoader({
|
||||
backend.add(import('@backstage/plugin-auth-backend'));
|
||||
backend.add(import('./authModuleGithubProvider'));
|
||||
backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));
|
||||
backend.add(import('@backstage/plugin-auth-backend-module-openshift-provider'));
|
||||
backend.add(import('@backstage/plugin-app-backend'));
|
||||
backend.add(import('@backstage/plugin-catalog-backend-module-unprocessed'));
|
||||
backend.add(
|
||||
|
||||
@@ -52,6 +52,7 @@ import { Observable } from '@backstage/types';
|
||||
import { oktaAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { oneloginAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { OpenIdConnectApi } from '@backstage/core-plugin-api';
|
||||
import { openshiftAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { PendingOAuthRequest } from '@backstage/core-plugin-api';
|
||||
import { ProfileInfo } from '@backstage/core-plugin-api';
|
||||
import { ProfileInfoApi } from '@backstage/core-plugin-api';
|
||||
@@ -651,6 +652,12 @@ export type OpenLoginPopupOptions = {
|
||||
height?: number;
|
||||
};
|
||||
|
||||
// @public
|
||||
export class OpenShiftAuth {
|
||||
// (undocumented)
|
||||
static create(options: OAuthApiCreateOptions): typeof openshiftAuthApiRef.T;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type PopupOptions = {
|
||||
size?:
|
||||
|
||||
@@ -26,4 +26,5 @@ export * from './bitbucket';
|
||||
export * from './bitbucketServer';
|
||||
export * from './atlassian';
|
||||
export * from './vmwareCloud';
|
||||
export * from './openshift';
|
||||
export type { OAuthApiCreateOptions, AuthApiCreateOptions } from './types';
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* 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 { openshiftAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { OAuth2 } from '../oauth2';
|
||||
import { OAuthApiCreateOptions } from '../types';
|
||||
|
||||
const DEFAULT_PROVIDER = {
|
||||
id: 'openshift',
|
||||
title: 'OpenShift',
|
||||
icon: () => null,
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements the OAuth flow to OpenShift
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export default class OpenShiftAuth {
|
||||
static create(options: OAuthApiCreateOptions): typeof openshiftAuthApiRef.T {
|
||||
const {
|
||||
configApi,
|
||||
discoveryApi,
|
||||
environment = 'development',
|
||||
provider = DEFAULT_PROVIDER,
|
||||
oauthRequestApi,
|
||||
defaultScopes = ['user:info'],
|
||||
} = options;
|
||||
|
||||
return OAuth2.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
provider,
|
||||
environment,
|
||||
defaultScopes,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* 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 OpenShiftAuth } from './OpenShiftAuth';
|
||||
@@ -604,6 +604,11 @@ export type OpenIdConnectApi = {
|
||||
getIdToken(options?: AuthRequestOptions): Promise<string>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const openshiftAuthApiRef: ApiRef<
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
|
||||
>;
|
||||
|
||||
// @public @deprecated
|
||||
export type OptionalParams<
|
||||
Params extends {
|
||||
|
||||
@@ -474,3 +474,20 @@ export const vmwareCloudAuthApiRef: ApiRef<
|
||||
> = createApiRef({
|
||||
id: 'core.auth.vmware-cloud',
|
||||
});
|
||||
|
||||
/**
|
||||
* Provides authentication towards OpenShift APIs and identities.
|
||||
*
|
||||
* @public
|
||||
* @remarks
|
||||
*
|
||||
* See {@link https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/authentication_and_authorization/configuring-oauth-clients}
|
||||
* on how to configure the OAuth clients and
|
||||
* {@link https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html-single/authentication_and_authorization/index#tokens-scoping-about_configuring-internal-oauth}
|
||||
* for available scopes.
|
||||
*/
|
||||
export const openshiftAuthApiRef: ApiRef<
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
|
||||
> = createApiRef({
|
||||
id: 'core.auth.openshift',
|
||||
});
|
||||
|
||||
@@ -372,6 +372,7 @@ describe('createApp', () => {
|
||||
<api:app/bitbucket-server-auth out=[core.api.factory] />
|
||||
<api:app/atlassian-auth out=[core.api.factory] />
|
||||
<api:app/vmware-cloud-auth out=[core.api.factory] />
|
||||
<api:app/openshift-auth out=[core.api.factory] />
|
||||
<api:app/permission out=[core.api.factory] />
|
||||
<api:app/scm-auth out=[core.api.factory] />
|
||||
<api:app/scm-integrations out=[core.api.factory] />
|
||||
|
||||
@@ -69,6 +69,7 @@ import { OAuthScope } from '@backstage/core-plugin-api';
|
||||
import { oktaAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { oneloginAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { OpenIdConnectApi } from '@backstage/core-plugin-api';
|
||||
import { openshiftAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { PendingOAuthRequest } from '@backstage/core-plugin-api';
|
||||
import { ProfileInfo } from '@backstage/core-plugin-api';
|
||||
import { ProfileInfoApi } from '@backstage/core-plugin-api';
|
||||
@@ -1518,6 +1519,8 @@ export { oneloginAuthApiRef };
|
||||
|
||||
export { OpenIdConnectApi };
|
||||
|
||||
export { openshiftAuthApiRef };
|
||||
|
||||
// @public
|
||||
export interface OverridableFrontendPlugin<
|
||||
TRoutes extends {
|
||||
|
||||
@@ -37,4 +37,5 @@ export {
|
||||
microsoftAuthApiRef,
|
||||
oneloginAuthApiRef,
|
||||
vmwareCloudAuthApiRef,
|
||||
openshiftAuthApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
@@ -539,6 +539,21 @@ const appPlugin: OverridableFrontendPlugin<
|
||||
params: ApiFactory<TApi, TImpl, TDeps>,
|
||||
) => ExtensionBlueprintParams<AnyApiFactory>;
|
||||
}>;
|
||||
'api:app/openshift-auth': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
name: 'openshift-auth';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>;
|
||||
inputs: {};
|
||||
params: <
|
||||
TApi,
|
||||
TImpl extends TApi,
|
||||
TDeps extends { [name in string]: unknown },
|
||||
>(
|
||||
params: ApiFactory<TApi, TImpl, TDeps>,
|
||||
) => ExtensionBlueprintParams<AnyApiFactory>;
|
||||
}>;
|
||||
'api:app/permission': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
name: 'permission';
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
createFetchApi,
|
||||
FetchMiddlewares,
|
||||
VMwareCloudAuth,
|
||||
OpenShiftAuth,
|
||||
} from '../../../packages/core-app-api/src/apis/implementations';
|
||||
|
||||
import {
|
||||
@@ -56,6 +57,7 @@ import {
|
||||
bitbucketServerAuthApiRef,
|
||||
atlassianAuthApiRef,
|
||||
vmwareCloudAuthApiRef,
|
||||
openshiftAuthApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { ApiBlueprint, dialogApiRef } from '@backstage/frontend-plugin-api';
|
||||
import {
|
||||
@@ -353,6 +355,26 @@ export const apis = [
|
||||
},
|
||||
}),
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'openshift-auth',
|
||||
params: defineParams =>
|
||||
defineParams({
|
||||
api: openshiftAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) => {
|
||||
return OpenShiftAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
});
|
||||
},
|
||||
}),
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'permission',
|
||||
params: defineParams =>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,5 @@
|
||||
# @backstage/plugin-auth-backend-module-openshift-provider
|
||||
|
||||
The openshift-provider backend module for the auth plugin.
|
||||
|
||||
_This plugin was created through the Backstage CLI_
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: backstage-plugin-auth-backend-module-openshift-provider
|
||||
title: '@backstage/plugin-auth-backend-module-openshift-provider'
|
||||
description: The OpenShift backend module for the auth plugin.
|
||||
spec:
|
||||
lifecycle: experimental
|
||||
type: backstage-backend-plugin-module
|
||||
owner: auth-maintainers
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* 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 { HumanDuration } from '@backstage/types';
|
||||
|
||||
export interface Config {
|
||||
auth?: {
|
||||
providers?: {
|
||||
/** @visibility frontend */
|
||||
openshift?: {
|
||||
[authEnv: string]: {
|
||||
clientId: string;
|
||||
/**
|
||||
* @visibility secret
|
||||
*/
|
||||
clientSecret: string;
|
||||
authorizationUrl: string;
|
||||
tokenUrl: string;
|
||||
callbackUrl?: string;
|
||||
openshiftApiServerUrl: string;
|
||||
signIn?: {
|
||||
resolvers: Array<{
|
||||
resolver: 'displayNameMatchingUserEntityName';
|
||||
dangerouslyAllowSignInWithoutUserInCatalog?: boolean;
|
||||
}>;
|
||||
};
|
||||
sessionDuration?: HumanDuration | string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* 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 { createBackend } from '@backstage/backend-defaults';
|
||||
import authPlugin from '@backstage/plugin-auth-backend';
|
||||
import authModuleOpenShiftProvider from '../src';
|
||||
|
||||
const backend = createBackend();
|
||||
|
||||
backend.add(authPlugin);
|
||||
backend.add(authModuleOpenShiftProvider);
|
||||
|
||||
backend.start();
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "@backstage/plugin-auth-backend-module-openshift-provider",
|
||||
"version": "0.0.0",
|
||||
"description": "The OpenShift backend module for the auth plugin.",
|
||||
"backstage": {
|
||||
"role": "backend-plugin-module",
|
||||
"pluginId": "auth",
|
||||
"pluginPackage": "@backstage/plugin-auth-backend"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "plugins/auth-backend-module-openshift-provider"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"config.d.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "backstage-cli package build",
|
||||
"clean": "backstage-cli package clean",
|
||||
"lint": "backstage-cli package lint",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack",
|
||||
"start": "backstage-cli package start",
|
||||
"test": "backstage-cli package test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"@backstage/catalog-model": "workspace:^",
|
||||
"@backstage/plugin-auth-node": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
"passport-oauth2": "^1.8.0",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-defaults": "workspace:^",
|
||||
"@backstage/backend-test-utils": "workspace:^",
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/plugin-auth-backend": "workspace:^",
|
||||
"express": "^4.18.2",
|
||||
"msw": "^2.7.3",
|
||||
"supertest": "^7.1.0"
|
||||
},
|
||||
"configSchema": "config.d.ts"
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
## API Report File for "@backstage/plugin-auth-backend-module-openshift-provider"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
import { OAuthAuthenticator } from '@backstage/plugin-auth-node';
|
||||
import { PassportOAuthAuthenticatorHelper } from '@backstage/plugin-auth-node';
|
||||
import { PassportProfile } from '@backstage/plugin-auth-node';
|
||||
|
||||
// @public (undocumented)
|
||||
const authModuleOpenshiftProvider: BackendFeature;
|
||||
export default authModuleOpenshiftProvider;
|
||||
|
||||
// @public (undocumented)
|
||||
export const openshiftAuthenticator: OAuthAuthenticator<
|
||||
OpenShiftAuthenticatorContext,
|
||||
PassportProfile
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface OpenShiftAuthenticatorContext {
|
||||
// (undocumented)
|
||||
helper: PassportOAuthAuthenticatorHelper;
|
||||
// (undocumented)
|
||||
openshiftApiServerUrl: string;
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* 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 { setupServer } from 'msw/node';
|
||||
import {
|
||||
decodeOAuthState,
|
||||
encodeOAuthState,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import { registerMswTestHooks } from '@backstage/backend-test-utils';
|
||||
import { http, HttpResponse } from 'msw';
|
||||
import { openshiftAuthenticator } from './authenticator';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import {
|
||||
OAuthState,
|
||||
OAuthAuthenticatorStartInput,
|
||||
OAuthAuthenticatorAuthenticateInput,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import express from 'express';
|
||||
|
||||
describe('openshiftAuthenticator', () => {
|
||||
let implementation: any;
|
||||
let oauthState: OAuthState;
|
||||
|
||||
const mswServer = setupServer();
|
||||
registerMswTestHooks(mswServer);
|
||||
|
||||
beforeEach(() => {
|
||||
mswServer.use(
|
||||
http.post('https://openshift.test/oauth/token', () => {
|
||||
return HttpResponse.json({
|
||||
access_token: 'accessToken',
|
||||
scope: 'user:full',
|
||||
expires_in: 60 * 60 * 24,
|
||||
});
|
||||
}),
|
||||
http.get(
|
||||
'https://api.openshift.test/apis/user.openshift.io/v1/users/~',
|
||||
async () => {
|
||||
return HttpResponse.json({
|
||||
kind: 'User',
|
||||
apiVersion: 'user.openshift.io/v1',
|
||||
metadata: {
|
||||
name: 'alice',
|
||||
uid: 'ca993628-8817-4a3b-9811-be4a34c60bf4',
|
||||
resourceVersion: '1',
|
||||
creationTimestamp: '2022-01-11T13:10:45Z',
|
||||
managedFields: [],
|
||||
},
|
||||
fullName: 'Alice Adams',
|
||||
identities: ['SSO:id'],
|
||||
groups: ['system:authenticated', 'system:authenticated:oauth'],
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
implementation = openshiftAuthenticator.initialize({
|
||||
callbackUrl: 'https://backstage.test/callback',
|
||||
config: new ConfigReader({
|
||||
clientId: 'clientId',
|
||||
clientSecret: 'clientSecret',
|
||||
authorizationUrl: 'https://openshift.test/oauth/authorize',
|
||||
tokenUrl: 'https://openshift.test/oauth/token',
|
||||
openshiftApiServerUrl: 'https://api.openshift.test',
|
||||
}),
|
||||
});
|
||||
|
||||
oauthState = {
|
||||
nonce: 'nonce',
|
||||
env: 'env',
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('#start', () => {
|
||||
let fakeSession: Record<string, any>;
|
||||
let startRequest: OAuthAuthenticatorStartInput;
|
||||
|
||||
beforeEach(() => {
|
||||
fakeSession = {};
|
||||
startRequest = {
|
||||
state: encodeOAuthState(oauthState),
|
||||
req: {
|
||||
method: 'GET',
|
||||
url: 'test',
|
||||
session: fakeSession,
|
||||
},
|
||||
} as unknown as OAuthAuthenticatorStartInput;
|
||||
});
|
||||
|
||||
it('initiates authorization code grant', async () => {
|
||||
const startResponse = await openshiftAuthenticator.start(
|
||||
startRequest,
|
||||
implementation,
|
||||
);
|
||||
const { searchParams } = new URL(startResponse.url);
|
||||
|
||||
expect(searchParams.get('response_type')).toBe('code');
|
||||
});
|
||||
|
||||
it('passes client ID from config', async () => {
|
||||
const startResponse = await openshiftAuthenticator.start(
|
||||
startRequest,
|
||||
implementation,
|
||||
);
|
||||
const { searchParams } = new URL(startResponse.url);
|
||||
|
||||
expect(searchParams.get('client_id')).toBe('clientId');
|
||||
});
|
||||
|
||||
it('passes callback URL from config', async () => {
|
||||
const startResponse = await openshiftAuthenticator.start(
|
||||
startRequest,
|
||||
implementation,
|
||||
);
|
||||
const { searchParams } = new URL(startResponse.url);
|
||||
|
||||
expect(searchParams.get('redirect_uri')).toBe(
|
||||
'https://backstage.test/callback',
|
||||
);
|
||||
});
|
||||
|
||||
it('encodes OAuth state in query param', async () => {
|
||||
const startResponse = await openshiftAuthenticator.start(
|
||||
startRequest,
|
||||
implementation,
|
||||
);
|
||||
const { searchParams } = new URL(startResponse.url);
|
||||
const stateParam = searchParams.get('state');
|
||||
const decodedState = decodeOAuthState(stateParam!);
|
||||
|
||||
expect(decodedState).toMatchObject(oauthState);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#authenticate', () => {
|
||||
let handlerRequest: OAuthAuthenticatorAuthenticateInput;
|
||||
|
||||
beforeEach(() => {
|
||||
handlerRequest = {
|
||||
req: {
|
||||
method: 'GET',
|
||||
query: {
|
||||
code: 'authorization_code',
|
||||
state: encodeOAuthState(oauthState),
|
||||
},
|
||||
session: {
|
||||
'oauth2:openshift': {
|
||||
state: encodeOAuthState(oauthState),
|
||||
},
|
||||
},
|
||||
} as unknown as express.Request,
|
||||
};
|
||||
});
|
||||
|
||||
it('exchanges authorization code for access token', async () => {
|
||||
const authenticatorResult = await openshiftAuthenticator.authenticate(
|
||||
handlerRequest,
|
||||
implementation,
|
||||
);
|
||||
const accessToken = authenticatorResult.session.accessToken;
|
||||
|
||||
expect(accessToken).toEqual('accessToken');
|
||||
});
|
||||
|
||||
it('returns granted scope', async () => {
|
||||
const authenticatorResult = await openshiftAuthenticator.authenticate(
|
||||
handlerRequest,
|
||||
implementation,
|
||||
);
|
||||
const responseScope = authenticatorResult.session.scope;
|
||||
|
||||
expect(responseScope).toEqual('user:full');
|
||||
});
|
||||
|
||||
it('returns a default session.tokentype field', async () => {
|
||||
const authenticatorResult = await openshiftAuthenticator.authenticate(
|
||||
handlerRequest,
|
||||
implementation,
|
||||
);
|
||||
const tokenType = authenticatorResult.session.tokenType;
|
||||
|
||||
expect(tokenType).toEqual('bearer');
|
||||
});
|
||||
|
||||
it('returns displayName', async () => {
|
||||
const authenticatorResult = await openshiftAuthenticator.authenticate(
|
||||
handlerRequest,
|
||||
implementation,
|
||||
);
|
||||
|
||||
expect(authenticatorResult).toMatchObject({
|
||||
fullProfile: {
|
||||
displayName: 'alice',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should store access token as refresh token', async () => {
|
||||
const authenticatorResult = await openshiftAuthenticator.authenticate(
|
||||
handlerRequest,
|
||||
implementation,
|
||||
);
|
||||
|
||||
expect(authenticatorResult.session.refreshToken).toBe(
|
||||
authenticatorResult.session.accessToken,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* 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 {
|
||||
createOAuthAuthenticator,
|
||||
PassportOAuthAuthenticatorHelper,
|
||||
PassportOAuthDoneCallback,
|
||||
PassportProfile,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import { createHash } from 'node:crypto';
|
||||
import OAuth2Strategy from 'passport-oauth2';
|
||||
import { z } from 'zod';
|
||||
|
||||
/** @public */
|
||||
export interface OpenShiftAuthenticatorContext {
|
||||
openshiftApiServerUrl: string;
|
||||
helper: PassportOAuthAuthenticatorHelper;
|
||||
}
|
||||
|
||||
/** @private
|
||||
* Schema for user.openshift.io/v1,
|
||||
* see https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/user_and_group_apis/user-user-openshift-io-v1#user-user-openshift-io-v1
|
||||
*/
|
||||
const OpenShiftUser = z.object({
|
||||
metadata: z.object({
|
||||
name: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export const openshiftAuthenticator = createOAuthAuthenticator<
|
||||
OpenShiftAuthenticatorContext,
|
||||
PassportProfile
|
||||
>({
|
||||
defaultProfileTransform:
|
||||
PassportOAuthAuthenticatorHelper.defaultProfileTransform,
|
||||
scopes: {
|
||||
required: ['user:full'],
|
||||
},
|
||||
initialize({ callbackUrl, config }) {
|
||||
const clientId = config.getString('clientId');
|
||||
const clientSecret = config.getString('clientSecret');
|
||||
const authorizationUrl = config.getString('authorizationUrl');
|
||||
const tokenUrl = config.getString('tokenUrl');
|
||||
const openshiftApiServerUrl = config.getString('openshiftApiServerUrl');
|
||||
|
||||
// userUrl: `${openshiftApiServerUrl}/apis/user.openshift.io/v1/users/~`,
|
||||
const strategy = new OAuth2Strategy(
|
||||
{
|
||||
clientID: clientId,
|
||||
clientSecret: clientSecret,
|
||||
callbackURL: callbackUrl,
|
||||
authorizationURL: authorizationUrl,
|
||||
tokenURL: tokenUrl,
|
||||
passReqToCallback: false,
|
||||
},
|
||||
(
|
||||
accessToken: any,
|
||||
refreshToken: string,
|
||||
params: any,
|
||||
fullProfile: PassportProfile,
|
||||
done: PassportOAuthDoneCallback,
|
||||
) => {
|
||||
done(undefined, { fullProfile, params, accessToken }, { refreshToken });
|
||||
},
|
||||
);
|
||||
|
||||
strategy.userProfile = function userProfile(
|
||||
accessToken: string,
|
||||
done: (err?: unknown, profile?: any) => void,
|
||||
): void {
|
||||
this._oauth2.useAuthorizationHeaderforGET(true);
|
||||
|
||||
this._oauth2.get(
|
||||
`${openshiftApiServerUrl}/apis/user.openshift.io/v1/users/~`,
|
||||
accessToken,
|
||||
(error, data, _) => {
|
||||
if (error !== null && error.statusCode !== 200) {
|
||||
done(new Error(`HTTP error! Status: ${error.statusCode}`));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
done(new Error('No data provided!'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof data !== 'string') {
|
||||
done(new Error('Data of type Buffer is not supported!'));
|
||||
return;
|
||||
}
|
||||
|
||||
const user = OpenShiftUser.parse(JSON.parse(data));
|
||||
done(null, { displayName: user.metadata.name });
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
openshiftApiServerUrl,
|
||||
helper: PassportOAuthAuthenticatorHelper.from(strategy),
|
||||
};
|
||||
},
|
||||
async start(input, { helper }) {
|
||||
return helper.start(input, {
|
||||
accessType: 'offline',
|
||||
prompt: 'consent',
|
||||
});
|
||||
},
|
||||
async authenticate(input, { helper }) {
|
||||
// Same workaround as the GitHub provider; see https://github.com/backstage/backstage/issues/25383
|
||||
const { fullProfile, session } = await helper.authenticate(input);
|
||||
session.refreshToken = session.accessToken;
|
||||
session.refreshTokenExpiresInSeconds = session.expiresInSeconds;
|
||||
return { fullProfile, session };
|
||||
},
|
||||
async refresh(input, { helper }) {
|
||||
// Because the session is refreshed on login, this override is crucial,
|
||||
// see https://github.com/backstage/backstage/issues/25383
|
||||
const accessToken = input.refreshToken;
|
||||
|
||||
const fullProfile = await helper.fetchProfile(accessToken).catch(error => {
|
||||
if (error.oauthError?.statusCode === 401) {
|
||||
throw new Error('Invalid access token');
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
|
||||
return {
|
||||
fullProfile,
|
||||
session: {
|
||||
accessToken,
|
||||
tokenType: 'bearer',
|
||||
scope: input.scope,
|
||||
refreshToken: input.refreshToken,
|
||||
},
|
||||
};
|
||||
},
|
||||
async logout(input, { openshiftApiServerUrl, helper }) {
|
||||
// Due to the implementation of createOAuthRouteHandlers, only the refresh token is set.
|
||||
// In this provider, the refresh token actually IS the access token.
|
||||
const accessToken = input.refreshToken;
|
||||
if (!accessToken) {
|
||||
throw new Error('access token/refresh token needs to be set for logout');
|
||||
}
|
||||
|
||||
// Check if access token is still valid.
|
||||
try {
|
||||
await helper.fetchProfile(accessToken);
|
||||
} catch {
|
||||
// Invalid token, no need to delete OAuthAccessToken.
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate token name, see:
|
||||
// https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/oauth_apis/oauthaccesstoken-oauth-openshift-io-v1#apis-oauth-openshift-io-v1-oauthaccesstokens
|
||||
const tokenName = createHash('sha256')
|
||||
.update(accessToken.slice('sha256~'.length))
|
||||
.digest()
|
||||
.toString('base64url');
|
||||
|
||||
const response = await fetch(
|
||||
`${openshiftApiServerUrl}/apis/oauth.openshift.io/v1/oauthaccesstokens/sha256~${tokenName}`,
|
||||
{ method: 'DELETE', headers: { Authorization: `Bearer ${accessToken}` } },
|
||||
);
|
||||
|
||||
if (response.status === 401) {
|
||||
throw new Error('unauthorized');
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* The openshift-provider backend module for the auth plugin.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
export {
|
||||
openshiftAuthenticator,
|
||||
type OpenShiftAuthenticatorContext,
|
||||
} from './authenticator';
|
||||
export { authModuleOpenshiftProvider as default } from './module';
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* 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 { createBackendModule } from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
authProvidersExtensionPoint,
|
||||
createOAuthProviderFactory,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import { openshiftAuthenticator } from './authenticator';
|
||||
import { openshiftSignInResolvers } from './resolvers';
|
||||
|
||||
/** @public */
|
||||
export const authModuleOpenshiftProvider = createBackendModule({
|
||||
pluginId: 'auth',
|
||||
moduleId: 'openshift-provider',
|
||||
register(reg) {
|
||||
reg.registerInit({
|
||||
deps: { providers: authProvidersExtensionPoint },
|
||||
async init({ providers }) {
|
||||
providers.registerProvider({
|
||||
providerId: 'openshift',
|
||||
factory: createOAuthProviderFactory({
|
||||
authenticator: openshiftAuthenticator,
|
||||
signInResolverFactories: {
|
||||
...openshiftSignInResolvers,
|
||||
},
|
||||
}),
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* 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 {
|
||||
createSignInResolverFactory,
|
||||
OAuthAuthenticatorResult,
|
||||
PassportProfile,
|
||||
SignInInfo,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
|
||||
import {
|
||||
DEFAULT_NAMESPACE,
|
||||
stringifyEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
import { z } from 'zod';
|
||||
|
||||
export namespace openshiftSignInResolvers {
|
||||
export const displayNameMatchingUserEntityName = createSignInResolverFactory({
|
||||
optionsSchema: z
|
||||
.object({
|
||||
dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
create(options = {}) {
|
||||
return async (
|
||||
info: SignInInfo<OAuthAuthenticatorResult<PassportProfile>>,
|
||||
ctx,
|
||||
) => {
|
||||
const { displayName } = info.profile;
|
||||
|
||||
if (!displayName) {
|
||||
throw new Error(
|
||||
`OpenShift user profile does not contain a displayName`,
|
||||
);
|
||||
}
|
||||
|
||||
const userRef = stringifyEntityRef({
|
||||
kind: 'User',
|
||||
name: displayName,
|
||||
namespace: DEFAULT_NAMESPACE,
|
||||
});
|
||||
|
||||
return await ctx.signInWithCatalogUser(
|
||||
{ entityRef: userRef },
|
||||
{
|
||||
dangerousEntityRefFallback:
|
||||
options?.dangerouslyAllowSignInWithoutUserInCatalog
|
||||
? { entityRef: { name: displayName } }
|
||||
: undefined,
|
||||
},
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
bitbucketServerAuthApiRef,
|
||||
atlassianAuthApiRef,
|
||||
oneloginAuthApiRef,
|
||||
openshiftAuthApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { userSettingsTranslationRef } from '../../translation';
|
||||
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
||||
@@ -128,6 +129,14 @@ export const DefaultProviderSettings = (props: {
|
||||
icon={Star}
|
||||
/>
|
||||
)}
|
||||
{configuredProviders.includes('openshift') && (
|
||||
<ProviderSettingsItem
|
||||
title="OpenShift"
|
||||
description="Provides authentication towards OpenShift APIs and identities"
|
||||
apiRef={openshiftAuthApiRef}
|
||||
icon={Star}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4122,6 +4122,27 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-auth-backend-module-openshift-provider@workspace:^, @backstage/plugin-auth-backend-module-openshift-provider@workspace:plugins/auth-backend-module-openshift-provider":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-auth-backend-module-openshift-provider@workspace:plugins/auth-backend-module-openshift-provider"
|
||||
dependencies:
|
||||
"@backstage/backend-defaults": "workspace:^"
|
||||
"@backstage/backend-plugin-api": "workspace:^"
|
||||
"@backstage/backend-test-utils": "workspace:^"
|
||||
"@backstage/catalog-model": "workspace:^"
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/config": "workspace:^"
|
||||
"@backstage/plugin-auth-backend": "workspace:^"
|
||||
"@backstage/plugin-auth-node": "workspace:^"
|
||||
"@backstage/types": "workspace:^"
|
||||
express: "npm:^4.18.2"
|
||||
msw: "npm:^2.7.3"
|
||||
passport-oauth2: "npm:^1.8.0"
|
||||
supertest: "npm:^7.1.0"
|
||||
zod: "npm:^3.24.2"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-auth-backend-module-pinniped-provider@workspace:plugins/auth-backend-module-pinniped-provider":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-auth-backend-module-pinniped-provider@workspace:plugins/auth-backend-module-pinniped-provider"
|
||||
@@ -11122,9 +11143,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mswjs/interceptors@npm:^0.39.1":
|
||||
version: 0.39.2
|
||||
resolution: "@mswjs/interceptors@npm:0.39.2"
|
||||
"@mswjs/interceptors@npm:^0.37.0":
|
||||
version: 0.37.1
|
||||
resolution: "@mswjs/interceptors@npm:0.37.1"
|
||||
dependencies:
|
||||
"@open-draft/deferred-promise": "npm:^2.2.0"
|
||||
"@open-draft/logger": "npm:^0.3.0"
|
||||
@@ -11132,7 +11153,7 @@ __metadata:
|
||||
is-node-process: "npm:^1.2.0"
|
||||
outvariant: "npm:^1.4.3"
|
||||
strict-event-emitter: "npm:^0.5.1"
|
||||
checksum: 10/faaa95d636363a197f125c32066457fa74d5063d8ccae4c9c0e0510179060d92b1faf8640df45a0623e0bf42a30d610c83364a58e0eb0ca412c87b2e835936c1
|
||||
checksum: 10/332d8aa50beb4834ccbda6a800ca00b1204adc0eba23e1c1f7bb9f4e564a92707e563f7a2424d4a8607404ec91424e5d8c34a87c250b191ca7b24dff12eba2c5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -26441,10 +26462,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"component-emitter@npm:^1.3.1":
|
||||
version: 1.3.1
|
||||
resolution: "component-emitter@npm:1.3.1"
|
||||
checksum: 10/94550aa462c7bd5a61c1bc480e28554aa306066930152d1b1844a0dd3845d4e5db7e261ddec62ae184913b3e59b55a2ad84093b9d3596a8f17c341514d6c483d
|
||||
"component-emitter@npm:^1.3.0":
|
||||
version: 1.3.0
|
||||
resolution: "component-emitter@npm:1.3.0"
|
||||
checksum: 10/dfc1ec2e7aa2486346c068f8d764e3eefe2e1ca0b24f57506cd93b2ae3d67829a7ebd7cc16e2bf51368fac2f45f78fcff231718e40b1975647e4a86be65e1d05
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -27668,15 +27689,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"debug@npm:4, debug@npm:^4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.3.7, debug@npm:^4.4.0":
|
||||
version: 4.4.1
|
||||
resolution: "debug@npm:4.4.1"
|
||||
"debug@npm:4, debug@npm:^4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.4.0":
|
||||
version: 4.4.0
|
||||
resolution: "debug@npm:4.4.0"
|
||||
dependencies:
|
||||
ms: "npm:^2.1.3"
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
checksum: 10/8e2709b2144f03c7950f8804d01ccb3786373df01e406a0f66928e47001cf2d336cbed9ee137261d4f90d68d8679468c755e3548ed83ddacdc82b194d2468afe
|
||||
checksum: 10/1847944c2e3c2c732514b93d11886575625686056cd765336212dc15de2d2b29612b6cd80e1afba767bb8e1803b778caf9973e98169ef1a24a7a7009e1820367
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -30098,6 +30119,7 @@ __metadata:
|
||||
"@backstage/plugin-auth-backend": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-github-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-guest-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-openshift-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-node": "workspace:^"
|
||||
"@backstage/plugin-catalog-backend": "workspace:^"
|
||||
"@backstage/plugin-catalog-backend-module-backstage-openapi": "workspace:^"
|
||||
@@ -31155,7 +31177,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"formidable@npm:^3.5.4":
|
||||
"formidable@npm:^3.5.1":
|
||||
version: 3.5.4
|
||||
resolution: "formidable@npm:3.5.4"
|
||||
dependencies:
|
||||
@@ -38701,15 +38723,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"msw@npm:^2.0.0, msw@npm:^2.0.8":
|
||||
version: 2.10.4
|
||||
resolution: "msw@npm:2.10.4"
|
||||
"msw@npm:^2.0.0, msw@npm:^2.0.8, msw@npm:^2.7.3":
|
||||
version: 2.7.3
|
||||
resolution: "msw@npm:2.7.3"
|
||||
dependencies:
|
||||
"@bundled-es-modules/cookie": "npm:^2.0.1"
|
||||
"@bundled-es-modules/statuses": "npm:^1.0.1"
|
||||
"@bundled-es-modules/tough-cookie": "npm:^0.1.6"
|
||||
"@inquirer/confirm": "npm:^5.0.0"
|
||||
"@mswjs/interceptors": "npm:^0.39.1"
|
||||
"@mswjs/interceptors": "npm:^0.37.0"
|
||||
"@open-draft/deferred-promise": "npm:^2.2.0"
|
||||
"@open-draft/until": "npm:^2.1.0"
|
||||
"@types/cookie": "npm:^0.6.0"
|
||||
@@ -38730,7 +38752,7 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
msw: cli/index.js
|
||||
checksum: 10/e2f25dda1aba66c7444c29c41d3157cb15c0332055ab7ebfb74ef4b506e7b90098cf37c577768edb5b2b2dbf0d6ed6a7a3ca8ee6da3d72df5a25823d82f33316
|
||||
checksum: 10/f193329a68fc22e477a6f8504aa44a92bd12847f2eeac1dfbd8ec1cc43ff293112ec067de1c7fe312ba02beecb313fb00aeeebf5817432b57af2d796b2dff2fa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -40680,7 +40702,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"passport-oauth2@npm:1.8.0, passport-oauth2@npm:1.x.x, passport-oauth2@npm:^1.1.2, passport-oauth2@npm:^1.4.0, passport-oauth2@npm:^1.6.0, passport-oauth2@npm:^1.6.1, passport-oauth2@npm:^1.7.0":
|
||||
"passport-oauth2@npm:1.8.0, passport-oauth2@npm:1.x.x, passport-oauth2@npm:^1.1.2, passport-oauth2@npm:^1.4.0, passport-oauth2@npm:^1.6.0, passport-oauth2@npm:^1.6.1, passport-oauth2@npm:^1.7.0, passport-oauth2@npm:^1.8.0":
|
||||
version: 1.8.0
|
||||
resolution: "passport-oauth2@npm:1.8.0"
|
||||
dependencies:
|
||||
@@ -42336,7 +42358,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"qs@npm:^6.10.1, qs@npm:^6.10.3, qs@npm:^6.11.2, qs@npm:^6.12.2, qs@npm:^6.12.3, qs@npm:^6.14.0, qs@npm:^6.7.0, qs@npm:^6.9.4":
|
||||
"qs@npm:^6.10.1, qs@npm:^6.10.3, qs@npm:^6.11.0, qs@npm:^6.11.2, qs@npm:^6.12.2, qs@npm:^6.12.3, qs@npm:^6.14.0, qs@npm:^6.7.0, qs@npm:^6.9.4":
|
||||
version: 6.14.0
|
||||
resolution: "qs@npm:6.14.0"
|
||||
dependencies:
|
||||
@@ -46586,30 +46608,30 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"superagent@npm:^10.2.3":
|
||||
version: 10.2.3
|
||||
resolution: "superagent@npm:10.2.3"
|
||||
"superagent@npm:^9.0.1":
|
||||
version: 9.0.2
|
||||
resolution: "superagent@npm:9.0.2"
|
||||
dependencies:
|
||||
component-emitter: "npm:^1.3.1"
|
||||
component-emitter: "npm:^1.3.0"
|
||||
cookiejar: "npm:^2.1.4"
|
||||
debug: "npm:^4.3.7"
|
||||
debug: "npm:^4.3.4"
|
||||
fast-safe-stringify: "npm:^2.1.1"
|
||||
form-data: "npm:^4.0.4"
|
||||
formidable: "npm:^3.5.4"
|
||||
form-data: "npm:^4.0.0"
|
||||
formidable: "npm:^3.5.1"
|
||||
methods: "npm:^1.1.2"
|
||||
mime: "npm:2.6.0"
|
||||
qs: "npm:^6.11.2"
|
||||
checksum: 10/377bf938e68927dd772169c5285be27872bf6e84fac01c52bcd9396bc5b348c9ded8f8be54649510ec09a67bc5096055847b37cb01b3bca0eb06ff1856170e35
|
||||
qs: "npm:^6.11.0"
|
||||
checksum: 10/d3c0c9051ceec84d5b431eaa410ad81bcd53255cea57af1fc66d683a24c34f3ba4761b411072a9bf489a70e3d5b586a78a0e6f2eac6a561067e7d196ddab0907
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"supertest@npm:^7.0.0":
|
||||
version: 7.1.4
|
||||
resolution: "supertest@npm:7.1.4"
|
||||
"supertest@npm:^7.0.0, supertest@npm:^7.1.0":
|
||||
version: 7.1.0
|
||||
resolution: "supertest@npm:7.1.0"
|
||||
dependencies:
|
||||
methods: "npm:^1.1.2"
|
||||
superagent: "npm:^10.2.3"
|
||||
checksum: 10/ecb5d41f2b62b257dbdcabac245c32b8e8fb264fe2636dd85c2c883569d23dc14adc0a471abb84187cbdb49bc36ad870ad355b4a0b85973f510fd57fc229e6cc
|
||||
superagent: "npm:^9.0.1"
|
||||
checksum: 10/20069f739a44821dfa4f7f397b9086ef31a358366331138f97945eedb2e231796e7c55b032125d3bd12f9839f089fbb809893dbc0f98edc57e12333b9f42b726
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -50109,10 +50131,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zod@npm:^3.22.4, zod@npm:^3.23.8":
|
||||
version: 3.25.76
|
||||
resolution: "zod@npm:3.25.76"
|
||||
checksum: 10/f0c963ec40cd96858451d1690404d603d36507c1fc9682f2dae59ab38b578687d542708a7fdbf645f77926f78c9ed558f57c3d3aa226c285f798df0c4da16995
|
||||
"zod@npm:^3.22.4, zod@npm:^3.23.8, zod@npm:^3.24.2":
|
||||
version: 3.25.67
|
||||
resolution: "zod@npm:3.25.67"
|
||||
checksum: 10/0e35432dcca7f053e63f5dd491a87c78abe0d981817547252c3b6d05f0f58788695d1a69724759c6501dff3fd62929be24c9f314a3625179bee889150f7a61fa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user