feat(SSO): メールアドレスのnormalizeを設定可能にする (MisskeyIO#971)

This commit is contained in:
あわわわとーにゅ 2025-04-22 00:14:13 +09:00 committed by GitHub
parent 17e14bb87e
commit c94e5d7e22
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 49 additions and 3 deletions

View file

@ -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"`);
}
}

View file

@ -79,4 +79,9 @@ export class MiSingleSignOnServiceProvider {
default: true, default: true,
}) })
public wantAssertionsSigned: boolean; public wantAssertionsSigned: boolean;
@Column('boolean', {
default: true,
})
public wantEmailAddressNormalized: boolean;
} }

View file

@ -84,6 +84,10 @@ export const meta = {
type: 'boolean', type: 'boolean',
optional: false, nullable: false, optional: false, nullable: false,
}, },
wantEmailAddressNormalized: {
type: 'boolean',
optional: false, nullable: false,
},
}, },
}, },
} as const; } as const;
@ -101,6 +105,7 @@ export const paramDef = {
cipherAlgorithm: { type: 'string', nullable: true }, cipherAlgorithm: { type: 'string', nullable: true },
wantAuthnRequestsSigned: { type: 'boolean', nullable: false, default: false }, wantAuthnRequestsSigned: { type: 'boolean', nullable: false, default: false },
wantAssertionsSigned: { type: 'boolean', nullable: false, default: true }, wantAssertionsSigned: { type: 'boolean', nullable: false, default: true },
wantEmailAddressNormalized: { type: 'boolean', nullable: false, default: true },
useCertificate: { type: 'boolean', nullable: false, default: true }, useCertificate: { type: 'boolean', nullable: false, default: true },
secret: { type: 'string', nullable: true }, secret: { type: 'string', nullable: true },
}, },
@ -157,6 +162,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
cipherAlgorithm: ps.cipherAlgorithm ? ps.cipherAlgorithm : null, cipherAlgorithm: ps.cipherAlgorithm ? ps.cipherAlgorithm : null,
wantAuthnRequestsSigned: ps.wantAuthnRequestsSigned, wantAuthnRequestsSigned: ps.wantAuthnRequestsSigned,
wantAssertionsSigned: ps.wantAssertionsSigned, wantAssertionsSigned: ps.wantAssertionsSigned,
wantEmailAddressNormalized: ps.wantEmailAddressNormalized,
}).then(r => this.singleSignOnServiceProviderRepository.findOneByOrFail({ id: r.identifiers[0].id })); }).then(r => this.singleSignOnServiceProviderRepository.findOneByOrFail({ id: r.identifiers[0].id }));
this.moderationLogService.log(me, 'createSSOServiceProvider', { this.moderationLogService.log(me, 'createSSOServiceProvider', {
@ -178,6 +184,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
cipherAlgorithm: ssoServiceProvider.cipherAlgorithm, cipherAlgorithm: ssoServiceProvider.cipherAlgorithm,
wantAuthnRequestsSigned: ssoServiceProvider.wantAuthnRequestsSigned, wantAuthnRequestsSigned: ssoServiceProvider.wantAuthnRequestsSigned,
wantAssertionsSigned: ssoServiceProvider.wantAssertionsSigned, wantAssertionsSigned: ssoServiceProvider.wantAssertionsSigned,
wantEmailAddressNormalized: ssoServiceProvider.wantEmailAddressNormalized,
}; };
}); });
} }

View file

@ -77,6 +77,10 @@ export const meta = {
type: 'boolean', type: 'boolean',
optional: false, nullable: false, optional: false, nullable: false,
}, },
wantEmailAddressNormalized: {
type: 'boolean',
optional: false, nullable: false,
},
}, },
}, },
}, },
@ -116,6 +120,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
cipherAlgorithm: service.cipherAlgorithm, cipherAlgorithm: service.cipherAlgorithm,
wantAuthnRequestsSigned: service.wantAuthnRequestsSigned, wantAuthnRequestsSigned: service.wantAuthnRequestsSigned,
wantAssertionsSigned: service.wantAssertionsSigned, wantAssertionsSigned: service.wantAssertionsSigned,
wantEmailAddressNormalized: service.wantEmailAddressNormalized,
})); }));
}); });
} }

View file

@ -37,6 +37,7 @@ export const paramDef = {
cipherAlgorithm: { type: 'string', nullable: true }, cipherAlgorithm: { type: 'string', nullable: true },
wantAuthnRequestsSigned: { type: 'boolean', nullable: false }, wantAuthnRequestsSigned: { type: 'boolean', nullable: false },
wantAssertionsSigned: { type: 'boolean', nullable: false }, wantAssertionsSigned: { type: 'boolean', nullable: false },
wantEmailAddressNormalized: { type: 'boolean', nullable: false },
regenerateCertificate: { type: 'boolean', nullable: true }, regenerateCertificate: { type: 'boolean', nullable: true },
secret: { type: 'string', nullable: true }, secret: { type: 'string', nullable: true },
}, },
@ -92,6 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
cipherAlgorithm: ps.cipherAlgorithm !== '' ? ps.cipherAlgorithm : null, cipherAlgorithm: ps.cipherAlgorithm !== '' ? ps.cipherAlgorithm : null,
wantAuthnRequestsSigned: ps.wantAuthnRequestsSigned, wantAuthnRequestsSigned: ps.wantAuthnRequestsSigned,
wantAssertionsSigned: ps.wantAssertionsSigned, wantAssertionsSigned: ps.wantAssertionsSigned,
wantEmailAddressNormalized: ps.wantEmailAddressNormalized,
}); });
const updatedService = await this.singleSignOnServiceProviderRepository.findOneByOrFail({ id: service.id }); const updatedService = await this.singleSignOnServiceProviderRepository.findOneByOrFail({ id: service.id });

View file

@ -180,7 +180,9 @@ export class JWTIdentifyProviderService {
preferred_username: user.username, preferred_username: user.username,
profile: `${this.config.url}/@${user.username}`, profile: `${this.config.url}/@${user.username}`,
picture: user.avatarUrl ?? undefined, 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, email_verified: profile.emailVerified,
mfa_enabled: profile.twoFactorEnabled, mfa_enabled: profile.twoFactorEnabled,
updated_at: Math.floor((user.updatedAt?.getTime() ?? user.createdAt.getTime()) / 1000), updated_at: Math.floor((user.updatedAt?.getTime() ?? user.createdAt.getTime()) / 1000),

View file

@ -444,7 +444,9 @@ export class SAMLIdentifyProviderService {
'saml:Subject': { 'saml:Subject': {
'saml:NameID': { 'saml:NameID': {
'@Format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', '@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': { 'saml:SubjectConfirmation': {
'@Method': 'urn:oasis:names:tc:SAML:2.0:cm:bearer', '@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', '@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
'saml:AttributeValue': { 'saml:AttributeValue': {
'@xsi:type': 'xs:string', '@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}`,
}, },
}, },
{ {

View file

@ -206,6 +206,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="service.wantAssertionsSigned"> <MkSwitch v-model="service.wantAssertionsSigned">
<template #label>Want Assertions Signed</template> <template #label>Want Assertions Signed</template>
</MkSwitch> </MkSwitch>
<MkSwitch v-model="service.wantEmailAddressNormalized">
<template #label>Want Email Address Normalized</template>
</MkSwitch>
<MkSwitch v-model="service.useCertificate" :disabled="!!service.createdAt"> <MkSwitch v-model="service.useCertificate" :disabled="!!service.createdAt">
<template #label>Use Certificate</template> <template #label>Use Certificate</template>
</MkSwitch> </MkSwitch>
@ -422,6 +425,7 @@ function ssoServiceAddNew() {
cipherAlgorithm: '', cipherAlgorithm: '',
wantAuthnRequestsSigned: false, wantAuthnRequestsSigned: false,
wantAssertionsSigned: true, wantAssertionsSigned: true,
wantEmailAddressNormalized: true,
regenerateCertificate: false, regenerateCertificate: false,
}); });
} }
@ -453,6 +457,7 @@ async function ssoServiceSave(service) {
cipherAlgorithm: service.cipherAlgorithm, cipherAlgorithm: service.cipherAlgorithm,
wantAuthnRequestsSigned: service.wantAuthnRequestsSigned, wantAuthnRequestsSigned: service.wantAuthnRequestsSigned,
wantAssertionsSigned: service.wantAssertionsSigned, wantAssertionsSigned: service.wantAssertionsSigned,
wantEmailAddressNormalized: service.wantEmailAddressNormalized,
}; };
if (service.createdAt !== undefined) { if (service.createdAt !== undefined) {

View file

@ -10972,6 +10972,8 @@ export type operations = {
/** @default true */ /** @default true */
wantAssertionsSigned?: boolean; wantAssertionsSigned?: boolean;
/** @default true */ /** @default true */
wantEmailAddressNormalized?: boolean;
/** @default true */
useCertificate: boolean; useCertificate: boolean;
secret?: string | null; secret?: string | null;
}; };
@ -10998,6 +11000,7 @@ export type operations = {
cipherAlgorithm?: string | null; cipherAlgorithm?: string | null;
wantAuthnRequestsSigned: boolean; wantAuthnRequestsSigned: boolean;
wantAssertionsSigned: boolean; wantAssertionsSigned: boolean;
wantEmailAddressNormalized: boolean;
}; };
}; };
}; };
@ -11123,6 +11126,7 @@ export type operations = {
cipherAlgorithm?: string | null; cipherAlgorithm?: string | null;
wantAuthnRequestsSigned: boolean; wantAuthnRequestsSigned: boolean;
wantAssertionsSigned: boolean; wantAssertionsSigned: boolean;
wantEmailAddressNormalized: boolean;
})[]; })[];
}; };
}; };
@ -11179,6 +11183,7 @@ export type operations = {
cipherAlgorithm?: string | null; cipherAlgorithm?: string | null;
wantAuthnRequestsSigned?: boolean; wantAuthnRequestsSigned?: boolean;
wantAssertionsSigned?: boolean; wantAssertionsSigned?: boolean;
wantEmailAddressNormalized?: boolean;
regenerateCertificate?: boolean | null; regenerateCertificate?: boolean | null;
secret?: string | null; secret?: string | null;
}; };