From 3c2d690ce27dbda9edf12299e0bce0765859d17c Mon Sep 17 00:00:00 2001 From: Jessica He Date: Thu, 22 Aug 2024 15:21:31 -0400 Subject: [PATCH] fix microsoft auth and ingestion for users without defined email Signed-off-by: Jessica He --- .changeset/real-pants-rule.md | 6 ++++ .../api-report.md | 4 +++ .../src/resolvers.ts | 29 ++++++++++++++++++- plugins/auth-backend/api-report.md | 1 + .../src/providers/microsoft/provider.ts | 2 ++ .../defaultTransformers.test.ts | 27 +++++++++++++++++ .../src/microsoftGraph/defaultTransformers.ts | 13 ++++++--- 7 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 .changeset/real-pants-rule.md diff --git a/.changeset/real-pants-rule.md b/.changeset/real-pants-rule.md new file mode 100644 index 0000000000..a1f430c789 --- /dev/null +++ b/.changeset/real-pants-rule.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-auth-backend-module-microsoft-provider': minor +'@backstage/plugin-catalog-backend-module-msgraph': patch +--- + +Allow users without defined email to be ingested by the `msgraph` catalog plugin and add `userIdMatchingUserEntityAnnotation` sign-in resolver for the Microsoft auth provider to support sign-in for users without defined email. diff --git a/plugins/auth-backend-module-microsoft-provider/api-report.md b/plugins/auth-backend-module-microsoft-provider/api-report.md index aa9bd8db5f..6aef395d7c 100644 --- a/plugins/auth-backend-module-microsoft-provider/api-report.md +++ b/plugins/auth-backend-module-microsoft-provider/api-report.md @@ -32,5 +32,9 @@ export namespace microsoftSignInResolvers { OAuthAuthenticatorResult, unknown >; + const userIdMatchingUserEntityAnnotation: SignInResolverFactory< + OAuthAuthenticatorResult, + unknown + >; } ``` diff --git a/plugins/auth-backend-module-microsoft-provider/src/resolvers.ts b/plugins/auth-backend-module-microsoft-provider/src/resolvers.ts index 7ef4a57175..db7aa4f7f3 100644 --- a/plugins/auth-backend-module-microsoft-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-microsoft-provider/src/resolvers.ts @@ -28,7 +28,7 @@ import { */ export namespace microsoftSignInResolvers { /** - * Looks up the user by matching their Microsoft username to the entity name. + * Looks up the user by matching their Microsoft email to the email entity annotation. */ export const emailMatchingUserEntityAnnotation = createSignInResolverFactory({ create() { @@ -50,4 +50,31 @@ export namespace microsoftSignInResolvers { }; }, }); + /** + * Looks up the user by matching their Microsoft user id to the user id entity annotation. + */ + export const userIdMatchingUserEntityAnnotation = createSignInResolverFactory( + { + create() { + return async ( + info: SignInInfo>, + ctx, + ) => { + const { result } = info; + + const id = result.fullProfile.id; + + if (!id) { + throw new Error('Microsoft profile contained no id'); + } + + return ctx.signInWithCatalogUser({ + annotations: { + 'graph.microsoft.com/user-id': id, + }, + }); + }; + }, + }, + ); } diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index 1f33886759..51f80024ae 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -533,6 +533,7 @@ export const providers: Readonly<{ resolvers: Readonly<{ emailMatchingUserEntityProfileEmail: () => SignInResolver_2; emailLocalPartMatchingUserEntityName: () => SignInResolver_2; + userIdMatchingUserEntityAnnotation: () => SignInResolver_2; emailMatchingUserEntityAnnotation: () => SignInResolver_2; }>; }>; diff --git a/plugins/auth-backend/src/providers/microsoft/provider.ts b/plugins/auth-backend/src/providers/microsoft/provider.ts index f6a3a83bcb..f0b019d1c7 100644 --- a/plugins/auth-backend/src/providers/microsoft/provider.ts +++ b/plugins/auth-backend/src/providers/microsoft/provider.ts @@ -65,5 +65,7 @@ export const microsoft = createAuthProviderIntegration({ commonSignInResolvers.emailMatchingUserEntityProfileEmail(), emailMatchingUserEntityAnnotation: microsoftSignInResolvers.emailMatchingUserEntityAnnotation(), + userIdMatchingUserEntityAnnotation: + microsoftSignInResolvers.userIdMatchingUserEntityAnnotation(), }), }); diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/defaultTransformers.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/defaultTransformers.test.ts index f123b75995..62aa758fed 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/defaultTransformers.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/defaultTransformers.test.ts @@ -90,4 +90,31 @@ describe('defaultTransformers', () => { }, }); }); + + it('tests defaultUserTransformer with no email', async () => { + const user: MicrosoftGraph.User = { + id: 'foo', + displayName: 'BAR', + userPrincipalName: 'test@upn', + }; + const userPhoto = 'test_photo'; + const result = await defaultUserTransformer(user, userPhoto); + expect(result).toEqual({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + annotations: { + 'graph.microsoft.com/user-id': 'foo', + }, + name: 'test_upn', + }, + spec: { + memberOf: [], + profile: { + displayName: 'BAR', + picture: 'test_photo', + }, + }, + }); + }); }); diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/defaultTransformers.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/defaultTransformers.ts index 7a8c4ec463..910d5a1d40 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/defaultTransformers.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/defaultTransformers.ts @@ -122,25 +122,25 @@ export async function defaultUserTransformer( user: MicrosoftGraph.User, userPhoto?: string, ): Promise { - if (!user.id || !user.displayName || !user.mail) { + if (!user.id || !user.displayName) { return undefined; } - const name = normalizeEntityName(user.mail); + const name = user.mail + ? normalizeEntityName(user.mail) + : normalizeEntityName(user.userPrincipalName!); const entity: UserEntity = { apiVersion: 'backstage.io/v1alpha1', kind: 'User', metadata: { name, annotations: { - [MICROSOFT_EMAIL_ANNOTATION]: user.mail!, [MICROSOFT_GRAPH_USER_ID_ANNOTATION]: user.id!, }, }, spec: { profile: { displayName: user.displayName!, - email: user.mail!, // TODO: Additional fields? // jobTitle: user.jobTitle || undefined, @@ -151,6 +151,11 @@ export async function defaultUserTransformer( }, }; + if (user.mail) { + entity.metadata.annotations![MICROSOFT_EMAIL_ANNOTATION] = user.mail; + entity.spec.profile!.email = user.mail; + } + if (userPhoto) { entity.spec.profile!.picture = userPhoto; }