|
| 1 | +import type { OAuth2ProxyResult } from '@backstage/plugin-auth-backend-module-oauth2-proxy-provider'; |
| 2 | +import type { OidcAuthResult } from '@backstage/plugin-auth-backend-module-oidc-provider'; |
| 3 | +import { |
| 4 | + AuthResolverContext, |
| 5 | + createSignInResolverFactory, |
| 6 | + OAuthAuthenticatorResult, |
| 7 | + SignInInfo, |
| 8 | +} from '@backstage/plugin-auth-node'; |
| 9 | + |
| 10 | +import { decodeJwt } from 'jose'; |
| 11 | +import { z } from 'zod'; |
| 12 | + |
| 13 | +import { createOidcSubClaimResolver, OidcProviderInfo } from './resolverUtils'; |
| 14 | + |
| 15 | +const KEYCLOAK_INFO: OidcProviderInfo = { |
| 16 | + userIdKey: 'keycloak.org/id', |
| 17 | + providerName: 'Keycloak', |
| 18 | +}; |
| 19 | + |
| 20 | +const PING_IDENTITY_INFO: OidcProviderInfo = { |
| 21 | + userIdKey: 'pingidentity.org/id', |
| 22 | + providerName: 'Ping Identity', |
| 23 | +}; |
| 24 | + |
| 25 | +const LDAP_UUID_ANNOTATION = 'backstage.io/ldap-uuid'; |
| 26 | + |
| 27 | +/** |
| 28 | + * Additional RHDH specific sign-in resolvers. |
| 29 | + * |
| 30 | + * @public |
| 31 | + */ |
| 32 | +export namespace rhdhSignInResolvers { |
| 33 | + /** |
| 34 | + * An OIDC resolver that looks up the user using their Keycloak user ID. |
| 35 | + */ |
| 36 | + export const oidcSubClaimMatchingKeycloakUserId = |
| 37 | + createOidcSubClaimResolver(KEYCLOAK_INFO); |
| 38 | + |
| 39 | + /** |
| 40 | + * An OIDC resolver that looks up the user using their Ping Identity user ID. |
| 41 | + */ |
| 42 | + export const oidcSubClaimMatchingPingIdentityUserId = |
| 43 | + createOidcSubClaimResolver(PING_IDENTITY_INFO); |
| 44 | + |
| 45 | + /** |
| 46 | + * An oauth2proxy resolver that looks up the user using the OAUTH_USER_HEADER environment variable, |
| 47 | + * 'x-forwarded-preferred-username' or 'x-forwarded-user'. |
| 48 | + */ |
| 49 | + export const oauth2ProxyUserHeaderMatchingUserEntityName = |
| 50 | + createSignInResolverFactory({ |
| 51 | + optionsSchema: z |
| 52 | + .object({ |
| 53 | + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), |
| 54 | + }) |
| 55 | + .optional(), |
| 56 | + create(options) { |
| 57 | + return async ( |
| 58 | + info: SignInInfo<OAuth2ProxyResult>, |
| 59 | + ctx: AuthResolverContext, |
| 60 | + ) => { |
| 61 | + const name = process.env.OAUTH_USER_HEADER |
| 62 | + ? info.result.getHeader(process.env.OAUTH_USER_HEADER) |
| 63 | + : info.result.getHeader('x-forwarded-preferred-username') || |
| 64 | + info.result.getHeader('x-forwarded-user'); |
| 65 | + if (!name) { |
| 66 | + throw new Error('Request did not contain a user'); |
| 67 | + } |
| 68 | + return ctx.signInWithCatalogUser( |
| 69 | + { |
| 70 | + entityRef: { name }, |
| 71 | + }, |
| 72 | + name, |
| 73 | + options?.dangerouslyAllowSignInWithoutUserInCatalog, |
| 74 | + ); |
| 75 | + }; |
| 76 | + }, |
| 77 | + }); |
| 78 | + |
| 79 | + export const oidcLdapUuidMatchingAnnotation = createSignInResolverFactory({ |
| 80 | + optionsSchema: z |
| 81 | + .object({ |
| 82 | + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), |
| 83 | + }) |
| 84 | + .optional(), |
| 85 | + create(options) { |
| 86 | + return async ( |
| 87 | + info: SignInInfo<OAuthAuthenticatorResult<OidcAuthResult>>, |
| 88 | + ctx: AuthResolverContext, |
| 89 | + ) => { |
| 90 | + const uuid = info.result.fullProfile.userinfo.ldap_uuid as string; |
| 91 | + if (!uuid) { |
| 92 | + throw new Error( |
| 93 | + `The user profile from LDAP is missing the UUID, likely due to a misconfiguration in the provider. Please contact your system administrator for assistance.`, |
| 94 | + ); |
| 95 | + } |
| 96 | + |
| 97 | + const idToken = info.result.fullProfile.tokenset.id_token; |
| 98 | + if (!idToken) { |
| 99 | + throw new Error( |
| 100 | + `The user ID token from LDAP is missing. Please contact your system administrator for assistance.`, |
| 101 | + ); |
| 102 | + } |
| 103 | + |
| 104 | + const uuidFromIdToken = decodeJwt(idToken)?.ldap_uuid; |
| 105 | + if (uuid !== uuidFromIdToken) { |
| 106 | + throw new Error( |
| 107 | + `There was a problem verifying your identity with LDAP due to mismatching UUID. Please contact your system administrator for assistance.`, |
| 108 | + ); |
| 109 | + } |
| 110 | + |
| 111 | + return ctx.signInWithCatalogUser( |
| 112 | + { |
| 113 | + annotations: { [LDAP_UUID_ANNOTATION]: uuid }, |
| 114 | + }, |
| 115 | + uuid, |
| 116 | + options?.dangerouslyAllowSignInWithoutUserInCatalog, |
| 117 | + ); |
| 118 | + }; |
| 119 | + }, |
| 120 | + }); |
| 121 | +} |
0 commit comments