diff --git a/.changeset/purple-toys-heal.md b/.changeset/purple-toys-heal.md new file mode 100644 index 0000000000..4a57dca64f --- /dev/null +++ b/.changeset/purple-toys-heal.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-vmware-cloud-provider': minor +--- + +**BREAKING**: The `profileEmailMatchingUserEntityEmail` sign-in resolver has been removed as it was using an insecure fallback for resolving user identities. See https://backstage.io/docs/auth/identity-resolver#sign-in-without-users-in-the-catalog for how to create a custom sign-in resolver if needed as a replacement. diff --git a/docs/auth/vmware-cloud/provider.md b/docs/auth/vmware-cloud/provider.md index a52ee98df2..d654331178 100644 --- a/docs/auth/vmware-cloud/provider.md +++ b/docs/auth/vmware-cloud/provider.md @@ -74,7 +74,6 @@ This provider includes several resolvers out of the box that you can use: - `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will throw a `NotFoundError`. - `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. -- `vmwareCloudSignInResolvers`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will sign in the user without associating with a catalog user. :::note Note diff --git a/plugins/auth-backend-module-vmware-cloud-provider/config.d.ts b/plugins/auth-backend-module-vmware-cloud-provider/config.d.ts index a08451cff3..8bb8320c0c 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/config.d.ts +++ b/plugins/auth-backend-module-vmware-cloud-provider/config.d.ts @@ -27,7 +27,6 @@ export interface Config { additionalScopes?: string | string[]; signIn?: { resolvers: Array< - | { resolver: 'profileEmailMatchingUserEntityEmail' } | { resolver: 'emailLocalPartMatchingUserEntityName' } | { resolver: 'emailMatchingUserEntityProfileEmail' } >; diff --git a/plugins/auth-backend-module-vmware-cloud-provider/src/index.ts b/plugins/auth-backend-module-vmware-cloud-provider/src/index.ts index 1d7bb14b63..4f42092c21 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/src/index.ts +++ b/plugins/auth-backend-module-vmware-cloud-provider/src/index.ts @@ -26,4 +26,3 @@ export { type VMwarePassportProfile, } from './authenticator'; export { authModuleVmwareCloudProvider as default } from './module'; -export { vmwareCloudSignInResolvers } from './resolvers'; diff --git a/plugins/auth-backend-module-vmware-cloud-provider/src/module.ts b/plugins/auth-backend-module-vmware-cloud-provider/src/module.ts index 5ef79654a3..4d9ca700a3 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/src/module.ts +++ b/plugins/auth-backend-module-vmware-cloud-provider/src/module.ts @@ -21,7 +21,6 @@ import { } from '@backstage/plugin-auth-node'; import { vmwareCloudAuthenticator } from './authenticator'; -import { vmwareCloudSignInResolvers } from './resolvers'; /** * VMware Cloud Provider backend module for the auth plugin @@ -40,7 +39,6 @@ export const authModuleVmwareCloudProvider = createBackendModule({ factory: createOAuthProviderFactory({ authenticator: vmwareCloudAuthenticator, signInResolverFactories: { - ...vmwareCloudSignInResolvers, ...commonSignInResolvers, }, }), diff --git a/plugins/auth-backend-module-vmware-cloud-provider/src/resolvers.test.ts b/plugins/auth-backend-module-vmware-cloud-provider/src/resolvers.test.ts deleted file mode 100644 index 250fd430c7..0000000000 --- a/plugins/auth-backend-module-vmware-cloud-provider/src/resolvers.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2023 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 { NotFoundError } from '@backstage/errors'; -import { - AuthResolverContext, - OAuthAuthenticatorResult, - PassportProfile, - SignInInfo, - SignInResolver, -} from '@backstage/plugin-auth-node'; - -import { vmwareCloudSignInResolvers } from './resolvers'; - -describe('vmwareCloudResolver', () => { - let resolverContext: jest.Mocked; - let signInInfo: SignInInfo>; - let signInResolver: SignInResolver>; - - beforeEach(() => { - resolverContext = { - issueToken: jest.fn().mockResolvedValue({ - token: 'defaultBackstageToken', - }), - findCatalogUser: jest.fn(), - signInWithCatalogUser: jest.fn().mockResolvedValue({ - token: 'backstageToken', - }), - }; - - signInInfo = { - result: {} as any, // Resolver doesn't care about the result object - profile: { - displayName: 'TestName', - email: 'user@example.com', - }, - }; - - signInResolver = - vmwareCloudSignInResolvers.profileEmailMatchingUserEntityEmail(); - }); - - it('looks up backstage identity by email', async () => { - const backstageIdentity = await signInResolver(signInInfo, resolverContext); - - expect(backstageIdentity.token).toBe('backstageToken'); - expect(resolverContext.signInWithCatalogUser).toHaveBeenCalledWith({ - filter: { - 'spec.profile.email': 'user@example.com', - }, - }); - }); - - it('returns "fake" backstage identity when no entity matches', async () => { - resolverContext.signInWithCatalogUser.mockRejectedValue( - new NotFoundError('User not found'), - ); - - const backstageIdentity = await signInResolver(signInInfo, resolverContext); - - expect(backstageIdentity.token).toBe('defaultBackstageToken'); - expect(resolverContext.issueToken).toHaveBeenCalledWith({ - claims: { - sub: 'user:default/user@example.com', - ent: ['user:default/user@example.com'], - }, - }); - }); - - it('fails when resolver context throws other error', () => { - const error = new Error('bizarre'); - resolverContext.signInWithCatalogUser.mockRejectedValue(error); - - return expect(signInResolver(signInInfo, resolverContext)).rejects.toThrow( - error, - ); - }); -}); diff --git a/plugins/auth-backend-module-vmware-cloud-provider/src/resolvers.ts b/plugins/auth-backend-module-vmware-cloud-provider/src/resolvers.ts deleted file mode 100644 index 2b3a78842b..0000000000 --- a/plugins/auth-backend-module-vmware-cloud-provider/src/resolvers.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2023 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 { stringifyEntityRef } from '@backstage/catalog-model'; -import { - createSignInResolverFactory, - OAuthAuthenticatorResult, - PassportProfile, - SignInInfo, -} from '@backstage/plugin-auth-node'; - -/** - * Available sign-in resolvers for the VMware Cloud auth provider. - * - * @public - */ -export namespace vmwareCloudSignInResolvers { - /** - * Looks up the user by matching their profile email to the entity's profile email. - * If that fails, sign in the user without associating with a catalog user. - */ - export const profileEmailMatchingUserEntityEmail = - createSignInResolverFactory({ - create() { - return async ( - info: SignInInfo>, - ctx, - ) => { - const email = info.profile.email; - - if (!email) { - throw new Error( - 'VMware login failed, user profile does not contain an email', - ); - } - - const userEntityRef = stringifyEntityRef({ - kind: 'User', - name: email, - }); - - try { - // we await here so that signInWithCatalogUser throws in the current `try` - return await ctx.signInWithCatalogUser({ - filter: { - 'spec.profile.email': email, - }, - }); - } catch (e) { - if (e.name !== 'NotFoundError') { - throw e; - } - return ctx.issueToken({ - claims: { - sub: userEntityRef, - ent: [userEntityRef], - }, - }); - } - }; - }, - }); -}