diff --git a/packages/backend/migration/1745247339195-SSO-wantEmailAddressNormalized.js b/packages/backend/migration/1745247339195-SSO-wantEmailAddressNormalized.js new file mode 100644 index 000000000..9d65b3127 --- /dev/null +++ b/packages/backend/migration/1745247339195-SSO-wantEmailAddressNormalized.js @@ -0,0 +1,11 @@ +export class SSOWantEmailAddressNormalized1745247339195 { + name = 'SSOWantEmailAddressNormalized1745247339195' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "sso_service_provider" ADD "wantEmailAddressNormalized" boolean NOT NULL DEFAULT true`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "sso_service_provider" DROP COLUMN "wantEmailAddressNormalized"`); + } +} diff --git a/packages/backend/src/models/SingleSignOnServiceProvider.ts b/packages/backend/src/models/SingleSignOnServiceProvider.ts index 03a095754..85ce1cfdc 100644 --- a/packages/backend/src/models/SingleSignOnServiceProvider.ts +++ b/packages/backend/src/models/SingleSignOnServiceProvider.ts @@ -79,4 +79,9 @@ export class MiSingleSignOnServiceProvider { default: true, }) public wantAssertionsSigned: boolean; + + @Column('boolean', { + default: true, + }) + public wantEmailAddressNormalized: boolean; } diff --git a/packages/backend/src/server/api/endpoints/admin/sso/create.ts b/packages/backend/src/server/api/endpoints/admin/sso/create.ts index 79cfafe9c..f6e28d7a2 100644 --- a/packages/backend/src/server/api/endpoints/admin/sso/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/sso/create.ts @@ -84,6 +84,10 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + wantEmailAddressNormalized: { + type: 'boolean', + optional: false, nullable: false, + }, }, }, } as const; @@ -101,6 +105,7 @@ export const paramDef = { cipherAlgorithm: { type: 'string', nullable: true }, wantAuthnRequestsSigned: { type: 'boolean', nullable: false, default: false }, wantAssertionsSigned: { type: 'boolean', nullable: false, default: true }, + wantEmailAddressNormalized: { type: 'boolean', nullable: false, default: true }, useCertificate: { type: 'boolean', nullable: false, default: true }, secret: { type: 'string', nullable: true }, }, @@ -157,6 +162,7 @@ export default class extends Endpoint { // eslint- cipherAlgorithm: ps.cipherAlgorithm ? ps.cipherAlgorithm : null, wantAuthnRequestsSigned: ps.wantAuthnRequestsSigned, wantAssertionsSigned: ps.wantAssertionsSigned, + wantEmailAddressNormalized: ps.wantEmailAddressNormalized, }).then(r => this.singleSignOnServiceProviderRepository.findOneByOrFail({ id: r.identifiers[0].id })); this.moderationLogService.log(me, 'createSSOServiceProvider', { @@ -178,6 +184,7 @@ export default class extends Endpoint { // eslint- cipherAlgorithm: ssoServiceProvider.cipherAlgorithm, wantAuthnRequestsSigned: ssoServiceProvider.wantAuthnRequestsSigned, wantAssertionsSigned: ssoServiceProvider.wantAssertionsSigned, + wantEmailAddressNormalized: ssoServiceProvider.wantEmailAddressNormalized, }; }); } diff --git a/packages/backend/src/server/api/endpoints/admin/sso/list.ts b/packages/backend/src/server/api/endpoints/admin/sso/list.ts index 9adc9bc54..6c2031488 100644 --- a/packages/backend/src/server/api/endpoints/admin/sso/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/sso/list.ts @@ -77,6 +77,10 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + wantEmailAddressNormalized: { + type: 'boolean', + optional: false, nullable: false, + }, }, }, }, @@ -116,6 +120,7 @@ export default class extends Endpoint { // eslint- cipherAlgorithm: service.cipherAlgorithm, wantAuthnRequestsSigned: service.wantAuthnRequestsSigned, wantAssertionsSigned: service.wantAssertionsSigned, + wantEmailAddressNormalized: service.wantEmailAddressNormalized, })); }); } diff --git a/packages/backend/src/server/api/endpoints/admin/sso/update.ts b/packages/backend/src/server/api/endpoints/admin/sso/update.ts index da8597cb4..870bdac58 100644 --- a/packages/backend/src/server/api/endpoints/admin/sso/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/sso/update.ts @@ -37,6 +37,7 @@ export const paramDef = { cipherAlgorithm: { type: 'string', nullable: true }, wantAuthnRequestsSigned: { type: 'boolean', nullable: false }, wantAssertionsSigned: { type: 'boolean', nullable: false }, + wantEmailAddressNormalized: { type: 'boolean', nullable: false }, regenerateCertificate: { type: 'boolean', nullable: true }, secret: { type: 'string', nullable: true }, }, @@ -92,6 +93,7 @@ export default class extends Endpoint { // eslint- cipherAlgorithm: ps.cipherAlgorithm !== '' ? ps.cipherAlgorithm : null, wantAuthnRequestsSigned: ps.wantAuthnRequestsSigned, wantAssertionsSigned: ps.wantAssertionsSigned, + wantEmailAddressNormalized: ps.wantEmailAddressNormalized, }); const updatedService = await this.singleSignOnServiceProviderRepository.findOneByOrFail({ id: service.id }); diff --git a/packages/backend/src/server/sso/JWTIdentifyProviderService.ts b/packages/backend/src/server/sso/JWTIdentifyProviderService.ts index 4e202688f..7d9b7fc7a 100644 --- a/packages/backend/src/server/sso/JWTIdentifyProviderService.ts +++ b/packages/backend/src/server/sso/JWTIdentifyProviderService.ts @@ -180,7 +180,9 @@ export class JWTIdentifyProviderService { preferred_username: user.username, profile: `${this.config.url}/@${user.username}`, picture: user.avatarUrl ?? undefined, - email: profile.emailVerified ? normalizeEmailAddress(profile.email) : `${user.username}@${this.config.hostname}`, + email: profile.emailVerified + ? (ssoServiceProvider.wantEmailAddressNormalized ? normalizeEmailAddress(profile.email) : profile.email) + : `${user.username}@users.${this.config.hostname}`, email_verified: profile.emailVerified, mfa_enabled: profile.twoFactorEnabled, updated_at: Math.floor((user.updatedAt?.getTime() ?? user.createdAt.getTime()) / 1000), diff --git a/packages/backend/src/server/sso/SAMLIdentifyProviderService.ts b/packages/backend/src/server/sso/SAMLIdentifyProviderService.ts index b0399ad6e..b8f236190 100644 --- a/packages/backend/src/server/sso/SAMLIdentifyProviderService.ts +++ b/packages/backend/src/server/sso/SAMLIdentifyProviderService.ts @@ -444,7 +444,9 @@ export class SAMLIdentifyProviderService { 'saml:Subject': { 'saml:NameID': { '@Format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', - '#text': profile.emailVerified ? normalizeEmailAddress(profile.email) : `${user.username}@${this.config.hostname}`, + '#text': profile.emailVerified + ? (ssoServiceProvider.wantEmailAddressNormalized ? normalizeEmailAddress(profile.email) : profile.email) + : `${user.username}@users.${this.config.hostname}`, }, 'saml:SubjectConfirmation': { '@Method': 'urn:oasis:names:tc:SAML:2.0:cm:bearer', @@ -569,7 +571,9 @@ export class SAMLIdentifyProviderService { '@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', 'saml:AttributeValue': { '@xsi:type': 'xs:string', - '#text': profile.emailVerified ? normalizeEmailAddress(profile.email) : `${user.username}@${this.config.hostname}`, + '#text': profile.emailVerified + ? (ssoServiceProvider.wantEmailAddressNormalized ? normalizeEmailAddress(profile.email) : profile.email) + : `${user.username}@users.${this.config.hostname}`, }, }, { diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue index 55cc0dd12..cc096613e 100644 --- a/packages/frontend/src/pages/admin/security.vue +++ b/packages/frontend/src/pages/admin/security.vue @@ -206,6 +206,9 @@ SPDX-License-Identifier: AGPL-3.0-only + + + @@ -422,6 +425,7 @@ function ssoServiceAddNew() { cipherAlgorithm: '', wantAuthnRequestsSigned: false, wantAssertionsSigned: true, + wantEmailAddressNormalized: true, regenerateCertificate: false, }); } @@ -453,6 +457,7 @@ async function ssoServiceSave(service) { cipherAlgorithm: service.cipherAlgorithm, wantAuthnRequestsSigned: service.wantAuthnRequestsSigned, wantAssertionsSigned: service.wantAssertionsSigned, + wantEmailAddressNormalized: service.wantEmailAddressNormalized, }; if (service.createdAt !== undefined) { diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index f21abcaed..c5619cd72 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -10972,6 +10972,8 @@ export type operations = { /** @default true */ wantAssertionsSigned?: boolean; /** @default true */ + wantEmailAddressNormalized?: boolean; + /** @default true */ useCertificate: boolean; secret?: string | null; }; @@ -10998,6 +11000,7 @@ export type operations = { cipherAlgorithm?: string | null; wantAuthnRequestsSigned: boolean; wantAssertionsSigned: boolean; + wantEmailAddressNormalized: boolean; }; }; }; @@ -11123,6 +11126,7 @@ export type operations = { cipherAlgorithm?: string | null; wantAuthnRequestsSigned: boolean; wantAssertionsSigned: boolean; + wantEmailAddressNormalized: boolean; })[]; }; }; @@ -11179,6 +11183,7 @@ export type operations = { cipherAlgorithm?: string | null; wantAuthnRequestsSigned?: boolean; wantAssertionsSigned?: boolean; + wantEmailAddressNormalized?: boolean; regenerateCertificate?: boolean | null; secret?: string | null; };