From dcfa2f79554817b0b9bd68503827b0b69d10716a Mon Sep 17 00:00:00 2001 From: Livio Spring Date: Wed, 10 Apr 2024 17:46:30 +0200 Subject: [PATCH] feat(idp): provide option to auto link user (#7734) * init auto linking * prompt handling * working * translations * console * fixes * unify * custom texts * fix tests * linting * fix check of existing user * fix bg translation * set unspecified as default in the form --- .../modules/policies/login-texts/helper.ts | 8 + .../login-texts/login-texts.component.ts | 1 + .../provider-options.component.html | 8 + .../provider-options.component.ts | 11 +- .../provider-options.module.ts | 11 +- .../provider-apple.component.ts | 7 +- .../provider-azure-ad.component.ts | 14 +- .../provider-github-es.component.ts | 7 +- .../provider-github.component.ts | 7 +- .../provider-gitlab-self-hosted.component.ts | 7 +- .../provider-gitlab.component.ts | 7 +- .../provider-google.component.ts | 7 +- .../provider-jwt/provider-jwt.component.ts | 7 +- .../provider-ldap/provider-ldap.component.ts | 7 +- .../provider-oauth.component.ts | 7 +- .../provider-oidc/provider-oidc.component.ts | 7 +- .../provider-saml-sp.component.ts | 7 +- console/src/assets/i18n/bg.json | 9 +- console/src/assets/i18n/cs.json | 9 +- console/src/assets/i18n/de.json | 9 +- console/src/assets/i18n/en.json | 9 +- console/src/assets/i18n/es.json | 9 +- console/src/assets/i18n/fr.json | 9 +- console/src/assets/i18n/it.json | 9 +- console/src/assets/i18n/ja.json | 9 +- console/src/assets/i18n/mk.json | 9 +- console/src/assets/i18n/nl.json | 9 +- console/src/assets/i18n/pl.json | 9 +- console/src/assets/i18n/pt.json | 9 +- console/src/assets/i18n/ru.json | 9 +- console/src/assets/i18n/zh.json | 9 +- .../api/grpc/admin/custom_text_converter.go | 1 + internal/api/grpc/admin/export.go | 1 + internal/api/grpc/idp/converter.go | 28 + .../grpc/management/custom_text_converter.go | 1 + internal/api/grpc/text/custom_text.go | 19 + .../api/ui/login/external_provider_handler.go | 60 +++ internal/api/ui/login/link_prompt_handler.go | 62 +++ internal/api/ui/login/renderer.go | 4 + internal/api/ui/login/router.go | 3 + internal/api/ui/login/static/i18n/bg.yaml | 5 + internal/api/ui/login/static/i18n/cs.yaml | 6 + internal/api/ui/login/static/i18n/de.yaml | 6 + internal/api/ui/login/static/i18n/en.yaml | 6 + internal/api/ui/login/static/i18n/es.yaml | 6 + internal/api/ui/login/static/i18n/fr.yaml | 6 + internal/api/ui/login/static/i18n/it.yaml | 6 + internal/api/ui/login/static/i18n/ja.yaml | 6 + internal/api/ui/login/static/i18n/mk.yaml | 6 + internal/api/ui/login/static/i18n/nl.yaml | 6 + internal/api/ui/login/static/i18n/pl.yaml | 6 + internal/api/ui/login/static/i18n/pt.yaml | 6 + internal/api/ui/login/static/i18n/ru.yaml | 6 + internal/api/ui/login/static/i18n/zh.yaml | 6 + .../static/templates/link_user_prompt.html | 37 ++ internal/auth/repository/auth_request.go | 2 +- .../eventsourcing/eventstore/auth_request.go | 4 +- internal/command/custom_login_text.go | 26 +- internal/command/custom_login_text_model.go | 51 ++ .../instance_custom_login_text_test.go | 121 +++++ .../command/org_custom_login_text_test.go | 117 +++++ internal/domain/custom_login_text.go | 14 + internal/domain/idp.go | 8 + internal/query/custom_text.go | 22 +- internal/query/idp_login_policy_link_test.go | 8 +- internal/query/idp_template.go | 9 + internal/query/idp_template_test.go | 482 ++++++++++-------- internal/query/idp_user_link_test.go | 6 +- internal/query/projection/idp_template.go | 22 +- .../query/projection/idp_template_test.go | 351 ++++++++----- internal/repository/idp/idp.go | 27 +- proto/zitadel/admin.proto | 1 + proto/zitadel/idp.proto | 15 + proto/zitadel/management.proto | 1 + proto/zitadel/text.proto | 8 + 75 files changed, 1432 insertions(+), 418 deletions(-) create mode 100644 internal/api/ui/login/link_prompt_handler.go create mode 100644 internal/api/ui/login/static/templates/link_user_prompt.html diff --git a/console/src/app/modules/policies/login-texts/helper.ts b/console/src/app/modules/policies/login-texts/helper.ts index d7fd2ed457..3cd24ceb48 100644 --- a/console/src/app/modules/policies/login-texts/helper.ts +++ b/console/src/app/modules/policies/login-texts/helper.ts @@ -15,6 +15,7 @@ import { InitPasswordDoneScreenText, InitPasswordScreenText, LinkingUserDoneScreenText, + LinkingUserPromptScreenText, LoginScreenText, LogoutDoneScreenText, MFAProvidersText, @@ -375,5 +376,12 @@ export function mapRequestValues(map: Partial, req: Req): Req { r34.setUsernameLabel(map.externalRegistrationUserOverviewText?.usernameLabel ?? ''); req.setExternalRegistrationUserOverviewText(r34); + const r35 = new LinkingUserPromptScreenText(); + r35.setTitle(map.linkingUserPromptText?.title ?? ''); + r35.setDescription(map.linkingUserPromptText?.description ?? ''); + r35.setLinkButtonText(map.linkingUserPromptText?.linkButtonText ?? ''); + r35.setOtherButtonText(map.linkingUserPromptText?.otherButtonText ?? ''); + req.setLinkingUserPromptText(r35); + return req; } diff --git a/console/src/app/modules/policies/login-texts/login-texts.component.ts b/console/src/app/modules/policies/login-texts/login-texts.component.ts index b0a846cb65..695e757f52 100644 --- a/console/src/app/modules/policies/login-texts/login-texts.component.ts +++ b/console/src/app/modules/policies/login-texts/login-texts.component.ts @@ -41,6 +41,7 @@ const KeyNamesArray = [ 'initPasswordText', 'initializeDoneText', 'initializeUserText', + 'linkingUserPromptText', 'linkingUserDoneText', 'loginText', 'logoutText', diff --git a/console/src/app/modules/provider-options/provider-options.component.html b/console/src/app/modules/provider-options/provider-options.component.html index 4991eee7ae..6dc77284e9 100644 --- a/console/src/app/modules/provider-options/provider-options.component.html +++ b/console/src/app/modules/provider-options/provider-options.component.html @@ -23,4 +23,12 @@ {{ 'IDP.OPTIONS.ISLINKINGALLOWED' | translate }} + +

{{ 'IDP.OPTIONS.AUTOLINKING_DESC' | translate }}

+ + + {{ 'IDP.OPTIONS.AUTOLINKINGTYPE.' + linkingType | translate }} + + +
diff --git a/console/src/app/modules/provider-options/provider-options.component.ts b/console/src/app/modules/provider-options/provider-options.component.ts index 63a75a1dc5..de0d512176 100644 --- a/console/src/app/modules/provider-options/provider-options.component.ts +++ b/console/src/app/modules/provider-options/provider-options.component.ts @@ -1,7 +1,8 @@ import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; import { Subject, takeUntil } from 'rxjs'; -import { Options } from 'src/app/proto/generated/zitadel/idp_pb'; +import { Options, AutoLinkingOption } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AccessTokenType } from '../../proto/generated/zitadel/user_pb'; @Component({ selector: 'cnsl-provider-options', @@ -17,8 +18,15 @@ export class ProviderOptionsComponent implements OnChanges, OnDestroy { isAutoUpdate: new FormControl(false, []), isCreationAllowed: new FormControl(true, []), isLinkingAllowed: new FormControl(true, []), + autoLinking: new FormControl(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED, []), }); + public linkingTypes: AutoLinkingOption[] = [ + AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED, + AutoLinkingOption.AUTO_LINKING_OPTION_USERNAME, + AutoLinkingOption.AUTO_LINKING_OPTION_EMAIL, + ]; + constructor() { this.form.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => { if (value) { @@ -27,6 +35,7 @@ export class ProviderOptionsComponent implements OnChanges, OnDestroy { opt.setIsAutoUpdate(value.isAutoUpdate); opt.setIsCreationAllowed(value.isCreationAllowed); opt.setIsLinkingAllowed(value.isLinkingAllowed); + opt.setAutoLinking(value.autoLinking); this.optionsChanged.emit(opt); } }); diff --git a/console/src/app/modules/provider-options/provider-options.module.ts b/console/src/app/modules/provider-options/provider-options.module.ts index 38d8578417..cef6ee8c0d 100644 --- a/console/src/app/modules/provider-options/provider-options.module.ts +++ b/console/src/app/modules/provider-options/provider-options.module.ts @@ -2,13 +2,22 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatSelectModule } from '@angular/material/select'; import { TranslateModule } from '@ngx-translate/core'; import { InfoSectionModule } from '../info-section/info-section.module'; import { ProviderOptionsComponent } from './provider-options.component'; @NgModule({ declarations: [ProviderOptionsComponent], - imports: [CommonModule, MatCheckboxModule, FormsModule, InfoSectionModule, ReactiveFormsModule, TranslateModule], + imports: [ + CommonModule, + MatCheckboxModule, + MatSelectModule, + FormsModule, + InfoSectionModule, + ReactiveFormsModule, + TranslateModule, + ], exports: [ProviderOptionsComponent], }) export class ProviderOptionsModule {} diff --git a/console/src/app/modules/providers/provider-apple/provider-apple.component.ts b/console/src/app/modules/providers/provider-apple/provider-apple.component.ts index 98c568b6aa..461ab90e34 100644 --- a/console/src/app/modules/providers/provider-apple/provider-apple.component.ts +++ b/console/src/app/modules/providers/provider-apple/provider-apple.component.ts @@ -10,7 +10,7 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateAppleProviderRequest as AdminUpdateAppleProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; import { AddAppleProviderRequest as MgmtAddAppleProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -34,7 +34,10 @@ const MAX_ALLOWED_SIZE = 5 * 1024; }) export class ProviderAppleComponent { public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: use id$ instead public id: string | null = ''; // DEPRECATED: assert service$ instead diff --git a/console/src/app/modules/providers/provider-azure-ad/provider-azure-ad.component.ts b/console/src/app/modules/providers/provider-azure-ad/provider-azure-ad.component.ts index cec3a15f89..230d9152e4 100644 --- a/console/src/app/modules/providers/provider-azure-ad/provider-azure-ad.component.ts +++ b/console/src/app/modules/providers/provider-azure-ad/provider-azure-ad.component.ts @@ -10,7 +10,14 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateAzureADProviderRequest as AdminUpdateAzureADProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { AzureADTenant, AzureADTenantType, IDPOwnerType, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { + AutoLinkingOption, + AzureADTenant, + AzureADTenantType, + IDPOwnerType, + Options, + Provider, +} from 'src/app/proto/generated/zitadel/idp_pb'; import { AddAzureADProviderRequest as MgmtAddAzureADProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -32,7 +39,10 @@ import { ProviderNextService } from '../provider-next/provider-next.service'; }) export class ProviderAzureADComponent { public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: use id$ instead public id: string | null = ''; // DEPRECATED: assert service$ instead diff --git a/console/src/app/modules/providers/provider-github-es/provider-github-es.component.ts b/console/src/app/modules/providers/provider-github-es/provider-github-es.component.ts index 30d10fa17e..6b02c9707e 100644 --- a/console/src/app/modules/providers/provider-github-es/provider-github-es.component.ts +++ b/console/src/app/modules/providers/provider-github-es/provider-github-es.component.ts @@ -10,7 +10,7 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateGitHubEnterpriseServerProviderRequest as AdminUpdateGitHubEnterpriseServerProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; import { AddGitHubEnterpriseServerProviderRequest as MgmtAddGitHubEnterpriseServerProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -32,7 +32,10 @@ import { ProviderNextService } from '../provider-next/provider-next.service'; }) export class ProviderGithubESComponent { public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: use id$ instead public id: string | null = ''; diff --git a/console/src/app/modules/providers/provider-github/provider-github.component.ts b/console/src/app/modules/providers/provider-github/provider-github.component.ts index a9c7bc5c50..eac57d498e 100644 --- a/console/src/app/modules/providers/provider-github/provider-github.component.ts +++ b/console/src/app/modules/providers/provider-github/provider-github.component.ts @@ -10,7 +10,7 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateGitHubProviderRequest as AdminUpdateGithubProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; import { AddGitHubProviderRequest as MgmtAddGithubProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -32,7 +32,10 @@ import { ProviderNextService } from '../provider-next/provider-next.service'; }) export class ProviderGithubComponent { public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: use id$ instead public id: string | null = ''; // DEPRECATED: assert service$ instead diff --git a/console/src/app/modules/providers/provider-gitlab-self-hosted/provider-gitlab-self-hosted.component.ts b/console/src/app/modules/providers/provider-gitlab-self-hosted/provider-gitlab-self-hosted.component.ts index 1adf214a2a..76f1f706db 100644 --- a/console/src/app/modules/providers/provider-gitlab-self-hosted/provider-gitlab-self-hosted.component.ts +++ b/console/src/app/modules/providers/provider-gitlab-self-hosted/provider-gitlab-self-hosted.component.ts @@ -10,7 +10,7 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateGitLabSelfHostedProviderRequest as AdminUpdateGitLabSelfHostedProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; import { AddGitLabSelfHostedProviderRequest as MgmtAddGitLabSelfHostedProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -32,7 +32,10 @@ import { ProviderNextService } from '../provider-next/provider-next.service'; }) export class ProviderGitlabSelfHostedComponent { public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: use id$ instead public id: string | null = ''; // DEPRECATED: assert service$ instead diff --git a/console/src/app/modules/providers/provider-gitlab/provider-gitlab.component.ts b/console/src/app/modules/providers/provider-gitlab/provider-gitlab.component.ts index 0e7b695ed6..23d76584b6 100644 --- a/console/src/app/modules/providers/provider-gitlab/provider-gitlab.component.ts +++ b/console/src/app/modules/providers/provider-gitlab/provider-gitlab.component.ts @@ -10,7 +10,7 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateGitLabProviderRequest as AdminUpdateGitLabProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; import { AddGitLabProviderRequest as MgmtAddGitLabProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -32,7 +32,10 @@ import { ProviderNextService } from '../provider-next/provider-next.service'; }) export class ProviderGitlabComponent { public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: use id$ instead public id: string | null = ''; // DEPRECATED: assert service$ instead diff --git a/console/src/app/modules/providers/provider-google/provider-google.component.ts b/console/src/app/modules/providers/provider-google/provider-google.component.ts index 66a812053e..1759c4b976 100644 --- a/console/src/app/modules/providers/provider-google/provider-google.component.ts +++ b/console/src/app/modules/providers/provider-google/provider-google.component.ts @@ -10,7 +10,7 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateGoogleProviderRequest as AdminUpdateGoogleProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; import { AddGoogleProviderRequest as MgmtAddGoogleProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -32,7 +32,10 @@ import { ProviderNextService } from '../provider-next/provider-next.service'; }) export class ProviderGoogleComponent { public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: use id$ instead public id: string | null = ''; // DEPRECATED: assert service$ instead diff --git a/console/src/app/modules/providers/provider-jwt/provider-jwt.component.ts b/console/src/app/modules/providers/provider-jwt/provider-jwt.component.ts index 4ae12d7679..8045fe8a80 100644 --- a/console/src/app/modules/providers/provider-jwt/provider-jwt.component.ts +++ b/console/src/app/modules/providers/provider-jwt/provider-jwt.component.ts @@ -9,7 +9,7 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateJWTProviderRequest as AdminUpdateJWTProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; import { AddJWTProviderRequest as MgmtAddJWTProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -32,7 +32,10 @@ import { ProviderNextService } from '../provider-next/provider-next.service'; }) export class ProviderJWTComponent { public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: use id$ instead public id: string | null = ''; diff --git a/console/src/app/modules/providers/provider-ldap/provider-ldap.component.ts b/console/src/app/modules/providers/provider-ldap/provider-ldap.component.ts index 2a6e0e46e5..382763a5e6 100644 --- a/console/src/app/modules/providers/provider-ldap/provider-ldap.component.ts +++ b/console/src/app/modules/providers/provider-ldap/provider-ldap.component.ts @@ -9,7 +9,7 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateLDAPProviderRequest as AdminUpdateLDAPProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { LDAPAttributes, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, LDAPAttributes, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; import { AddLDAPProviderRequest as MgmtAddLDAPProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -32,7 +32,10 @@ import { ProviderNextService } from '../provider-next/provider-next.service'; export class ProviderLDAPComponent { public updateBindPassword: boolean = false; public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); public attributes: LDAPAttributes = new LDAPAttributes(); // DEPRECATED: use id$ instead public id: string | null = ''; diff --git a/console/src/app/modules/providers/provider-oauth/provider-oauth.component.ts b/console/src/app/modules/providers/provider-oauth/provider-oauth.component.ts index cb5a2c17a2..c2e6ee602e 100644 --- a/console/src/app/modules/providers/provider-oauth/provider-oauth.component.ts +++ b/console/src/app/modules/providers/provider-oauth/provider-oauth.component.ts @@ -10,7 +10,7 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateGenericOAuthProviderRequest as AdminUpdateGenericOAuthProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; import { AddGenericOAuthProviderRequest as MgmtAddGenericOAuthProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -32,7 +32,10 @@ import { ProviderNextService } from '../provider-next/provider-next.service'; }) export class ProviderOAuthComponent { public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: use id$ instead public id: string | null = ''; diff --git a/console/src/app/modules/providers/provider-oidc/provider-oidc.component.ts b/console/src/app/modules/providers/provider-oidc/provider-oidc.component.ts index 7a791ab381..b3658034cc 100644 --- a/console/src/app/modules/providers/provider-oidc/provider-oidc.component.ts +++ b/console/src/app/modules/providers/provider-oidc/provider-oidc.component.ts @@ -10,7 +10,7 @@ import { GetProviderByIDRequest as AdminGetProviderByIDRequest, UpdateGenericOIDCProviderRequest as AdminUpdateGenericOIDCProviderRequest, } from 'src/app/proto/generated/zitadel/admin_pb'; -import { Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb'; import { AddGenericOIDCProviderRequest as MgmtAddGenericOIDCProviderRequest, GetProviderByIDRequest as MgmtGetProviderByIDRequest, @@ -32,7 +32,10 @@ import { ProviderNextService } from '../provider-next/provider-next.service'; }) export class ProviderOIDCComponent { public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: use id$ instead public id: string | null = ''; diff --git a/console/src/app/modules/providers/provider-saml-sp/provider-saml-sp.component.ts b/console/src/app/modules/providers/provider-saml-sp/provider-saml-sp.component.ts index 27fca823f8..4ac09149e6 100644 --- a/console/src/app/modules/providers/provider-saml-sp/provider-saml-sp.component.ts +++ b/console/src/app/modules/providers/provider-saml-sp/provider-saml-sp.component.ts @@ -1,6 +1,6 @@ import { Component, Injector, Type } from '@angular/core'; import { Location } from '@angular/common'; -import { Options, Provider, SAMLBinding } from '../../../proto/generated/zitadel/idp_pb'; +import { AutoLinkingOption, Options, Provider, SAMLBinding } from '../../../proto/generated/zitadel/idp_pb'; import { AbstractControl, FormGroup, UntypedFormControl, UntypedFormGroup } from '@angular/forms'; import { PolicyComponentServiceType } from '../../policies/policy-component-types.enum'; import { ManagementService } from '../../../services/mgmt.service'; @@ -37,7 +37,10 @@ export class ProviderSamlSpComponent { public provider?: Provider.AsObject; public form!: FormGroup; public showOptional: boolean = false; - public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true); + public options: Options = new Options() + .setIsCreationAllowed(true) + .setIsLinkingAllowed(true) + .setAutoLinking(AutoLinkingOption.AUTO_LINKING_OPTION_UNSPECIFIED); // DEPRECATED: assert service$ instead public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT; // DEPRECATED: use service$ instead diff --git a/console/src/assets/i18n/bg.json b/console/src/assets/i18n/bg.json index 6add8de104..566ea0037b 100644 --- a/console/src/assets/i18n/bg.json +++ b/console/src/assets/i18n/bg.json @@ -1582,6 +1582,7 @@ "initPasswordText": "Инициализиране на парола", "initializeDoneText": "Инициализирането на потребителя е готово", "initializeUserText": "Инициализирайте потребителя", + "linkingUserPromptText": "Потребителският промпт за свързване", "linkingUserDoneText": "Свързването на потребителя е готово", "loginText": "Влизам", "logoutText": "Излез от профила си", @@ -2014,7 +2015,13 @@ "ISCREATIONALLOWED": "Създаването на акаунт е разрешено", "ISCREATIONALLOWED_DESC": "Определя дали могат да се създават акаунти.", "ISLINKINGALLOWED": "Свързването на акаунти е разрешено", - "ISLINKINGALLOWED_DESC": "Определя дали дадена самоличност може да бъде свързана със съществуващ акаунт." + "ISLINKINGALLOWED_DESC": "Определя дали дадена самоличност може да бъде свързана със съществуващ акаунт.", + "AUTOLINKING_DESC": "Определя дали идентичността ще бъде подканена да бъде свързана със съществуващ профил.", + "AUTOLINKINGTYPE": { + "0": "Изключено", + "1": "Проверка за съществуващо потребителско име", + "2": "Проверка за съществуващ имейл" + } }, "OWNERTYPES": { "0": "неизвестен", diff --git a/console/src/assets/i18n/cs.json b/console/src/assets/i18n/cs.json index c2c875c78c..91af40f0f4 100644 --- a/console/src/assets/i18n/cs.json +++ b/console/src/assets/i18n/cs.json @@ -1589,6 +1589,7 @@ "initPasswordText": "Inicializace hesla", "initializeDoneText": "Inicializace uživatele dokončena", "initializeUserText": "Inicializace uživatele", + "linkingUserPromptText": "Uživatelský propojovací text", "linkingUserDoneText": "Propojení uživatele dokončeno", "loginText": "Přihlášení", "logoutText": "Odhlášení", @@ -2025,7 +2026,13 @@ "ISCREATIONALLOWED": "Je povoleno vytváření účtu", "ISCREATIONALLOWED_DESC": "Určuje, zda lze vytvářet účty.", "ISLINKINGALLOWED": "Je povoleno propojení účtů", - "ISLINKINGALLOWED_DESC": "Určuje, zda lze identitu propojit s existujícím účtem." + "ISLINKINGALLOWED_DESC": "Určuje, zda lze identitu propojit s existujícím účtem.", + "AUTOLINKING_DESC": "Určuje, zda se bude identita vyzývat k propojení se stávajícím účtem.", + "AUTOLINKINGTYPE": { + "0": "Vypnuto", + "1": "Kontrola existence uživatelského jména", + "2": "Kontrola existence e-mailu" + } }, "OWNERTYPES": { "0": "neznámý", diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index f4d1cf8e6c..e54be4ce04 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -1588,6 +1588,7 @@ "initPasswordText": "Passwort Initialisierung", "initializeDoneText": "Benutzereinrichtung erfolgreich", "initializeUserText": "Benutzereinrichtung", + "linkingUserPromptText": "Aufforderung zur Benutzerverlinkung", "linkingUserDoneText": "Benutzerverlinkung erfolgreich", "loginText": "Anmelden", "logoutText": "Abmelden", @@ -2015,7 +2016,13 @@ "ISCREATIONALLOWED": "Account erstellen erlaubt", "ISCREATIONALLOWED_DESC": "Legt fest, ob Konten erstellt werden können.", "ISLINKINGALLOWED": "Account linking erlaubt", - "ISLINKINGALLOWED_DESC": "Legt fest, ob eine Identität mit einem bestehenden Konto verknüpft werden kann." + "ISLINKINGALLOWED_DESC": "Legt fest, ob eine Identität mit einem bestehenden Konto verknüpft werden kann.", + "AUTOLINKING_DESC": "Legt fest, ob eine Identität aufgefordert wird, mit einem vorhandenen Konto verknüpft zu werden.", + "AUTOLINKINGTYPE": { + "0": "Deaktiviert", + "1": "Überprüfung auf vorhandenen Benutzernamen", + "2": "Überprüfung auf vorhandene E-Mail" + } }, "OWNERTYPES": { "0": "unknown", diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index 0427a4af5b..73824bef68 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -1589,6 +1589,7 @@ "initPasswordText": "Initialize password", "initializeDoneText": "Initialize user done", "initializeUserText": "Initialize user", + "linkingUserPromptText": "Linking user prompt", "linkingUserDoneText": "Linking user done", "loginText": "Login", "logoutText": "Logout", @@ -2028,7 +2029,13 @@ "ISCREATIONALLOWED": "Account creation allowed", "ISCREATIONALLOWED_DESC": "Determines whether accounts can be created.", "ISLINKINGALLOWED": "Account linking allowed", - "ISLINKINGALLOWED_DESC": "Determines whether an identity can be linked to an existing account." + "ISLINKINGALLOWED_DESC": "Determines whether an identity can be linked to an existing account.", + "AUTOLINKING_DESC": "Determines whether an identity will be prompted to be linked to an existing account.", + "AUTOLINKINGTYPE": { + "0": "Disabled", + "1": "Check for existing Username", + "2": "Check for existing Email" + } }, "OWNERTYPES": { "0": "unknown", diff --git a/console/src/assets/i18n/es.json b/console/src/assets/i18n/es.json index e5630cd0a8..70dd86bc84 100644 --- a/console/src/assets/i18n/es.json +++ b/console/src/assets/i18n/es.json @@ -1590,6 +1590,7 @@ "initPasswordText": "Inicializar contraseña", "initializeDoneText": "Inicializar usuario, hecho", "initializeUserText": "Inicializar usuario", + "linkingUserPromptText": "Mensaje de enlace de usuario", "linkingUserDoneText": "Vinculación de usuario, hecho", "loginText": "Iniciar sesión", "logoutText": "Cerrar sesión", @@ -2021,7 +2022,13 @@ "ISCREATIONALLOWED": "Creación de cuentas permitida", "ISCREATIONALLOWED_DESC": "Determina si se pueden crear cuentas.", "ISLINKINGALLOWED": "Permitida la vinculación de cuentas", - "ISLINKINGALLOWED_DESC": "Determina si una identidad puede vincularse a una cuenta existente." + "ISLINKINGALLOWED_DESC": "Determina si una identidad puede vincularse a una cuenta existente.", + "AUTOLINKING_DESC": "Determina si se pedirá a una identidad que se vincule a una cuenta existente.", + "AUTOLINKINGTYPE": { + "0": "Desactivado", + "1": "Comprobar nombre de usuario existente", + "2": "Comprobar correo electrónico existente" + } }, "OWNERTYPES": { "0": "desconocido", diff --git a/console/src/assets/i18n/fr.json b/console/src/assets/i18n/fr.json index 929dce791a..56d3e7e63e 100644 --- a/console/src/assets/i18n/fr.json +++ b/console/src/assets/i18n/fr.json @@ -1588,6 +1588,7 @@ "initPasswordText": "Initialiser le mot de passe", "initializeDoneText": "Initialiser l'utilisateur terminé", "initializeUserText": "Initialiser l'utilisateur", + "linkingUserPromptText": "Message de liaison utilisateur", "linkingUserDoneText": "Lier l'utilisateur fait", "loginText": "Connexion", "logoutText": "Déconnexion", @@ -2019,7 +2020,13 @@ "ISCREATIONALLOWED": "la création est-elle autorisée", "ISCREATIONALLOWED_DESC": "Détermine si des comptes peuvent être créés.", "ISLINKINGALLOWED": "la liaison est-elle autorisée", - "ISLINKINGALLOWED_DESC": "Détermine si une identité peut être liée à un compte existant." + "ISLINKINGALLOWED_DESC": "Détermine si une identité peut être liée à un compte existant.", + "AUTOLINKING_DESC": "Détermine si une identité sera invitée à être liée à un compte existant.", + "AUTOLINKINGTYPE": { + "0": "Désactivé", + "1": "Vérification de l'existence du nom d'utilisateur", + "2": "Vérification de l'existence de l'e-mail" + } }, "OWNERTYPES": { "0": "inconnu", diff --git a/console/src/assets/i18n/it.json b/console/src/assets/i18n/it.json index 747e857157..8b13f5518a 100644 --- a/console/src/assets/i18n/it.json +++ b/console/src/assets/i18n/it.json @@ -1588,6 +1588,7 @@ "initPasswordText": "Inizializzazione della password", "initializeDoneText": "Inizializzazione utente finita", "initializeUserText": "Inizializzazione utente", + "linkingUserPromptText": "Testo di promemoria per collegare l'utente", "linkingUserDoneText": "Collegamento dell'utente finito", "loginText": "Accesso", "logoutText": "Logout", @@ -2019,7 +2020,13 @@ "ISCREATIONALLOWED": "Creazione consentita", "ISCREATIONALLOWED_DESC": "Determina se i conti possono essere creati.", "ISLINKINGALLOWED": "Collegamento consentito", - "ISLINKINGALLOWED_DESC": "Determina se un'identità può essere collegata a un account esistente." + "ISLINKINGALLOWED_DESC": "Determina se un'identità può essere collegata a un account esistente.", + "AUTOLINKING_DESC": "Determina se un'identità verrà invitata a essere collegata a un account esistente.", + "AUTOLINKINGTYPE": { + "0": "Disabilitato", + "1": "Verifica dell'esistenza del nome utente", + "2": "Verifica dell'esistenza dell'email" + } }, "OWNERTYPES": { "0": "sconosciuto", diff --git a/console/src/assets/i18n/ja.json b/console/src/assets/i18n/ja.json index dd8130b6dd..dcc2484222 100644 --- a/console/src/assets/i18n/ja.json +++ b/console/src/assets/i18n/ja.json @@ -1585,6 +1585,7 @@ "initPasswordText": "パスワードを初期化する", "initializeDoneText": "ユーザーの初期化が完了しました", "initializeUserText": "ユーザーを初期化する", + "linkingUserPromptText": "ユーザーのリンクプロンプト", "linkingUserDoneText": "ユーザーのリンクが完了しました", "loginText": "ログイン", "logoutText": "ログアウト", @@ -2016,7 +2017,13 @@ "ISCREATIONALLOWED": "アカウント作成を許可", "ISCREATIONALLOWED_DESC": "アカウントを作成できるかどうかを決めます。", "ISLINKINGALLOWED": "アカウントリンクを許可", - "ISLINKINGALLOWED_DESC": "IDを既存のアカウントにリンクできるかどうかを決めます。" + "ISLINKINGALLOWED_DESC": "IDを既存のアカウントにリンクできるかどうかを決めます。", + "AUTOLINKING_DESC": "アイデンティティが既存のアカウントにリンクされるように促されるかどうかを決定します。", + "AUTOLINKINGTYPE": { + "0": "無効", + "1": "既存のユーザー名のチェック", + "2": "既存のメールアドレスのチェック" + } }, "OWNERTYPES": { "0": "不明", diff --git a/console/src/assets/i18n/mk.json b/console/src/assets/i18n/mk.json index f12c16f09b..99c55a999d 100644 --- a/console/src/assets/i18n/mk.json +++ b/console/src/assets/i18n/mk.json @@ -1590,6 +1590,7 @@ "initPasswordText": "Иницијализација на лозинка", "initializeDoneText": "Иницијализацијата на корисникот е завршена", "initializeUserText": "Иницијализација на корисник", + "linkingUserPromptText": "Поврзување на кориснички промпт", "linkingUserDoneText": "Поврзувањето на корисникот е завршено", "loginText": "Најава", "logoutText": "Одјава", @@ -2023,7 +2024,13 @@ "ISCREATIONALLOWED": "Дозволено креирање на кориснички сметки", "ISCREATIONALLOWED_DESC": "Одредува дали може да се креираат кориснички сметки.", "ISLINKINGALLOWED": "Дозволено поврзување на кориснички сметки", - "ISLINKINGALLOWED_DESC": "Одредува дали може да се поврзе идентитет со постоечка корисничка сметка." + "ISLINKINGALLOWED_DESC": "Одредува дали може да се поврзе идентитет со постоечка корисничка сметка.", + "AUTOLINKING_DESC": "Одредува дали ќе се бара идентитетот да биде поврзан со постоечки профил.", + "AUTOLINKINGTYPE": { + "0": "Исклучено", + "1": "Проверка за постоечко корисничко име", + "2": "Проверка за постоечка е-пошта" + } }, "OWNERTYPES": { "0": "непознато", diff --git a/console/src/assets/i18n/nl.json b/console/src/assets/i18n/nl.json index 1da10f8e57..ece8acc83f 100644 --- a/console/src/assets/i18n/nl.json +++ b/console/src/assets/i18n/nl.json @@ -1589,6 +1589,7 @@ "initPasswordText": "Initialiseer wachtwoord", "initializeDoneText": "Gebruiker initialisatie voltooid", "initializeUserText": "Initialiseer gebruiker", + "linkingUserPromptText": "Gebruiker koppelingsprompt", "linkingUserDoneText": "Gebruiker koppeling voltooid", "loginText": "Login", "logoutText": "Uitloggen", @@ -2028,7 +2029,13 @@ "ISCREATIONALLOWED": "Account creatie toegestaan", "ISCREATIONALLOWED_DESC": "Bepaalt of accounts kunnen worden aangemaakt.", "ISLINKINGALLOWED": "Account koppeling toegestaan", - "ISLINKINGALLOWED_DESC": "Bepaalt of een identiteit kan worden gekoppeld aan een bestaand account." + "ISLINKINGALLOWED_DESC": "Bepaalt of een identiteit kan worden gekoppeld aan een bestaand account.", + "AUTOLINKING_DESC": "Bepaalt of een identiteit wordt gevraagd om te worden gekoppeld aan een bestaand account.", + "AUTOLINKINGTYPE": { + "0": "Uitgeschakeld", + "1": "Controleren op bestaande gebruikersnaam", + "2": "Controleren op bestaand e-mailadres" + } }, "OWNERTYPES": { "0": "onbekend", diff --git a/console/src/assets/i18n/pl.json b/console/src/assets/i18n/pl.json index ef572b7beb..a39947f8c3 100644 --- a/console/src/assets/i18n/pl.json +++ b/console/src/assets/i18n/pl.json @@ -1588,6 +1588,7 @@ "initPasswordText": "Inicjalizacja hasła", "initializeDoneText": "Inicjalizacja użytkownika zakończona", "initializeUserText": "Inicjalizacja użytkownika", + "linkingUserPromptText": "Komunikat o łączeniu użytkowników", "linkingUserDoneText": "Łączenie użytkownika zakończone", "loginText": "Zaloguj się", "logoutText": "Wyloguj się", @@ -2019,7 +2020,13 @@ "ISCREATIONALLOWED": "tworzenie dozwolone", "ISCREATIONALLOWED_DESC": "Określa, czy można tworzyć konta.", "ISLINKINGALLOWED": "dozwolone łączenie rachunków", - "ISLINKINGALLOWED_DESC": "Określa, czy tożsamość może być powiązana z istniejącym kontem." + "ISLINKINGALLOWED_DESC": "Określa, czy tożsamość może być powiązana z istniejącym kontem.", + "AUTOLINKING_DESC": "Określa, czy tożsamość będzie proszona o połączenie z istniejącym kontem.", + "AUTOLINKINGTYPE": { + "0": "Wyłączone", + "1": "Sprawdź istniejącą nazwę użytkownika", + "2": "Sprawdź istniejący adres e-mail" + } }, "OWNERTYPES": { "0": "nieznany", diff --git a/console/src/assets/i18n/pt.json b/console/src/assets/i18n/pt.json index 9fdb634ab6..ca8ae8b012 100644 --- a/console/src/assets/i18n/pt.json +++ b/console/src/assets/i18n/pt.json @@ -1590,6 +1590,7 @@ "initPasswordText": "Inicialização de senha", "initializeDoneText": "Inicialização de usuário concluída", "initializeUserText": "Inicializaçãode usuário", + "linkingUserPromptText": "Prompt de usuário para vinculação", "linkingUserDoneText": "Vinculação de usuário concluída", "loginText": "Login", "logoutText": "Logout", @@ -2021,7 +2022,13 @@ "ISCREATIONALLOWED": "Criação de Conta Permitida", "ISCREATIONALLOWED_DESC": "Determina se as contas podem ser criadas.", "ISLINKINGALLOWED": "Vinculação de Conta Permitida", - "ISLINKINGALLOWED_DESC": "Determina se uma identidade pode ser vinculada a uma conta existente." + "ISLINKINGALLOWED_DESC": "Determina se uma identidade pode ser vinculada a uma conta existente.", + "AUTOLINKING_DESC": "Determina se uma identidade será solicitada a ser vinculada a uma conta existente.", + "AUTOLINKINGTYPE": { + "0": "Desativado", + "1": "Verificar nome de usuário existente", + "2": "Verificar e-mail existente" + } }, "OWNERTYPES": { "0": "desconhecido", diff --git a/console/src/assets/i18n/ru.json b/console/src/assets/i18n/ru.json index ff38f488d4..538e02956b 100644 --- a/console/src/assets/i18n/ru.json +++ b/console/src/assets/i18n/ru.json @@ -1656,6 +1656,7 @@ "initPasswordText": "Инициализировать пароль", "initializeDoneText": "Инициализация пользователя выполнена", "initializeUserText": "Инициализировать пользователя", + "linkingUserPromptText": "Текст приглашения к привязке пользователя", "linkingUserDoneText": "Привязка пользователя выполнена", "loginText": "Вход", "logoutText": "Выход", @@ -2114,7 +2115,13 @@ "ISCREATIONALLOWED": "Создание учетной записи разрешено", "ISCREATIONALLOWED_DESC": "Определяет, можно ли создавать учетные записи.", "ISLINKINGALLOWED": "Привязка аккаунтов разрешена", - "ISLINKINGALLOWED_DESC": "Определяет, можно ли связать личность с существующей учетной записью." + "ISLINKINGALLOWED_DESC": "Определяет, можно ли связать личность с существующей учетной записью.", + "AUTOLINKING_DESC": "Определяет, будет ли запрошено связать идентификацию с существующим аккаунтом.", + "AUTOLINKINGTYPE": { + "0": "Отключено", + "1": "Проверка существующего имени пользователя", + "2": "Проверка существующего адреса электронной почты" + } }, "OWNERTYPES": { "0": "неизвестен", diff --git a/console/src/assets/i18n/zh.json b/console/src/assets/i18n/zh.json index 57d5ba2490..5ee5139780 100644 --- a/console/src/assets/i18n/zh.json +++ b/console/src/assets/i18n/zh.json @@ -1587,6 +1587,7 @@ "initPasswordText": "初始化密码", "initializeDoneText": "初始化用户完成", "initializeUserText": "初始化用户", + "linkingUserPromptText": "用户链接提示", "linkingUserDoneText": "链接用户完成", "loginText": "登录", "logoutText": "登出", @@ -2018,7 +2019,13 @@ "ISCREATIONALLOWED": "是否允许创作", "ISCREATIONALLOWED_DESC": "确定是否可以创建账户。", "ISLINKINGALLOWED": "是否允许连接", - "ISLINKINGALLOWED_DESC": "确定一个身份是否可以与一个现有的账户相联系。" + "ISLINKINGALLOWED_DESC": "确定一个身份是否可以与一个现有的账户相联系。", + "AUTOLINKING_DESC": "确定是否提示将身份链接到现有帐户。", + "AUTOLINKINGTYPE": { + "0": "已禁用", + "1": "检查现有用户名", + "2": "检查现有电子邮件" + } }, "OWNERTYPES": { "0": "未知", diff --git a/internal/api/grpc/admin/custom_text_converter.go b/internal/api/grpc/admin/custom_text_converter.go index a7471525d9..4bb76b3617 100644 --- a/internal/api/grpc/admin/custom_text_converter.go +++ b/internal/api/grpc/admin/custom_text_converter.go @@ -172,6 +172,7 @@ func SetLoginTextToDomain(req *admin_pb.SetCustomLoginTextsRequest) *domain.Cust result.RegistrationUser = text.RegistrationUserScreenTextPbToDomain(req.RegistrationUserText) result.ExternalRegistrationUserOverview = text.ExternalRegistrationUserOverviewScreenTextPbToDomain(req.ExternalRegistrationUserOverviewText) result.RegistrationOrg = text.RegistrationOrgScreenTextPbToDomain(req.RegistrationOrgText) + result.LinkingUserPrompt = text.LinkingUserPromptScreenTextPbToDomain(req.LinkingUserPromptText) result.LinkingUsersDone = text.LinkingUserDoneScreenTextPbToDomain(req.LinkingUserDoneText) result.ExternalNotFound = text.ExternalUserNotFoundScreenTextPbToDomain(req.ExternalUserNotFoundText) result.LoginSuccess = text.SuccessLoginScreenTextPbToDomain(req.SuccessLoginText) diff --git a/internal/api/grpc/admin/export.go b/internal/api/grpc/admin/export.go index 679ae0271c..13a11c14db 100644 --- a/internal/api/grpc/admin/export.go +++ b/internal/api/grpc/admin/export.go @@ -1060,6 +1060,7 @@ func (s *Server) getCustomLoginTexts(ctx context.Context, org string, languages RegistrationUserText: text_grpc.RegistrationUserScreenTextToPb(text.RegistrationUser), ExternalRegistrationUserOverviewText: text_grpc.ExternalRegistrationUserOverviewScreenTextToPb(text.ExternalRegistrationUserOverview), RegistrationOrgText: text_grpc.RegistrationOrgScreenTextToPb(text.RegistrationOrg), + LinkingUserPromptText: text_grpc.LinkingUserPromptScreenTextToPb(text.LinkingUserPrompt), LinkingUserDoneText: text_grpc.LinkingUserDoneScreenTextToPb(text.LinkingUsersDone), ExternalUserNotFoundText: text_grpc.ExternalUserNotFoundScreenTextToPb(text.ExternalNotFound), SuccessLoginText: text_grpc.SuccessLoginScreenTextToPb(text.LoginSuccess), diff --git a/internal/api/grpc/idp/converter.go b/internal/api/grpc/idp/converter.go index c92f2bd3b0..2acaf4d3d8 100644 --- a/internal/api/grpc/idp/converter.go +++ b/internal/api/grpc/idp/converter.go @@ -274,6 +274,20 @@ func OptionsToCommand(options *idp_pb.Options) idp.Options { IsLinkingAllowed: options.IsLinkingAllowed, IsAutoCreation: options.IsAutoCreation, IsAutoUpdate: options.IsAutoUpdate, + AutoLinkingOption: autoLinkingOptionToCommand(options.AutoLinking), + } +} + +func autoLinkingOptionToCommand(linking idp_pb.AutoLinkingOption) domain.AutoLinkingOption { + switch linking { + case idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME: + return domain.AutoLinkingOptionUsername + case idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_EMAIL: + return domain.AutoLinkingOptionEmail + case idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_UNSPECIFIED: + return domain.AutoLinkingOptionUnspecified + default: + return domain.AutoLinkingOptionUnspecified } } @@ -398,6 +412,7 @@ func configToPb(config *query.IDPTemplate) *idp_pb.ProviderConfig { IsCreationAllowed: config.IsCreationAllowed, IsAutoCreation: config.IsAutoCreation, IsAutoUpdate: config.IsAutoUpdate, + AutoLinking: autoLinkingOptionToPb(config.AutoLinking), }, } if config.OAuthIDPTemplate != nil { @@ -451,6 +466,19 @@ func configToPb(config *query.IDPTemplate) *idp_pb.ProviderConfig { return providerConfig } +func autoLinkingOptionToPb(linking domain.AutoLinkingOption) idp_pb.AutoLinkingOption { + switch linking { + case domain.AutoLinkingOptionUnspecified: + return idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_UNSPECIFIED + case domain.AutoLinkingOptionUsername: + return idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME + case domain.AutoLinkingOptionEmail: + return idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_EMAIL + default: + return idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_UNSPECIFIED + } +} + func oauthConfigToPb(providerConfig *idp_pb.ProviderConfig, template *query.OAuthIDPTemplate) { providerConfig.Config = &idp_pb.ProviderConfig_Oauth{ Oauth: &idp_pb.OAuthConfig{ diff --git a/internal/api/grpc/management/custom_text_converter.go b/internal/api/grpc/management/custom_text_converter.go index 06dfed6a8d..aa5aa05a67 100644 --- a/internal/api/grpc/management/custom_text_converter.go +++ b/internal/api/grpc/management/custom_text_converter.go @@ -171,6 +171,7 @@ func SetLoginCustomTextToDomain(req *mgmt_pb.SetCustomLoginTextsRequest) *domain result.RegistrationUser = text.RegistrationUserScreenTextPbToDomain(req.RegistrationUserText) result.ExternalRegistrationUserOverview = text.ExternalRegistrationUserOverviewScreenTextPbToDomain(req.ExternalRegistrationUserOverviewText) result.RegistrationOrg = text.RegistrationOrgScreenTextPbToDomain(req.RegistrationOrgText) + result.LinkingUserPrompt = text.LinkingUserPromptScreenTextPbToDomain(req.LinkingUserPromptText) result.LinkingUsersDone = text.LinkingUserDoneScreenTextPbToDomain(req.LinkingUserDoneText) result.ExternalNotFound = text.ExternalUserNotFoundScreenTextPbToDomain(req.ExternalUserNotFoundText) result.LoginSuccess = text.SuccessLoginScreenTextPbToDomain(req.SuccessLoginText) diff --git a/internal/api/grpc/text/custom_text.go b/internal/api/grpc/text/custom_text.go index 0f3da00b53..52c9c4af02 100644 --- a/internal/api/grpc/text/custom_text.go +++ b/internal/api/grpc/text/custom_text.go @@ -64,6 +64,7 @@ func CustomLoginTextToPb(text *domain.CustomLoginText) *text_pb.LoginCustomText RegistrationUserText: RegistrationUserScreenTextToPb(text.RegistrationUser), ExternalRegistrationUserOverviewText: ExternalRegistrationUserOverviewScreenTextToPb(text.ExternalRegistrationUserOverview), RegistrationOrgText: RegistrationOrgScreenTextToPb(text.RegistrationOrg), + LinkingUserPromptText: LinkingUserPromptScreenTextToPb(text.LinkingUserPrompt), LinkingUserDoneText: LinkingUserDoneScreenTextToPb(text.LinkingUsersDone), ExternalUserNotFoundText: ExternalUserNotFoundScreenTextToPb(text.ExternalNotFound), SuccessLoginText: SuccessLoginScreenTextToPb(text.LoginSuccess), @@ -422,6 +423,15 @@ func LinkingUserDoneScreenTextToPb(text domain.LinkingUserDoneScreenText) *text_ } } +func LinkingUserPromptScreenTextToPb(text domain.LinkingUserPromptScreenText) *text_pb.LinkingUserPromptScreenText { + return &text_pb.LinkingUserPromptScreenText{ + Title: text.Title, + Description: text.Description, + LinkButtonText: text.LinkButtonText, + OtherButtonText: text.OtherButtonText, + } +} + func ExternalUserNotFoundScreenTextToPb(text domain.ExternalUserNotFoundScreenText) *text_pb.ExternalUserNotFoundScreenText { return &text_pb.ExternalUserNotFoundScreenText{ Title: text.Title, @@ -890,6 +900,15 @@ func RegistrationOrgScreenTextPbToDomain(text *text_pb.RegistrationOrgScreenText } } +func LinkingUserPromptScreenTextPbToDomain(text *text_pb.LinkingUserPromptScreenText) domain.LinkingUserPromptScreenText { + return domain.LinkingUserPromptScreenText{ + Title: text.GetTitle(), + Description: text.GetDescription(), + LinkButtonText: text.GetLinkButtonText(), + OtherButtonText: text.GetOtherButtonText(), + } +} + func LinkingUserDoneScreenTextPbToDomain(text *text_pb.LinkingUserDoneScreenText) domain.LinkingUserDoneScreenText { if text == nil { return domain.LinkingUserDoneScreenText{} diff --git a/internal/api/ui/login/external_provider_handler.go b/internal/api/ui/login/external_provider_handler.go index 14fc930751..98c2dde6ff 100644 --- a/internal/api/ui/login/external_provider_handler.go +++ b/internal/api/ui/login/external_provider_handler.go @@ -449,6 +449,59 @@ func (l *Login) handleExternalUserAuthenticated( callback(w, r, authReq) } +// checkAutoLinking checks if a user with the provided information (username or email) already exists within ZITADEL. +// The decision, which information will be checked is based on the IdP template option. +// The function returns a boolean whether a user was found or not. +func (l *Login) checkAutoLinking(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, provider *query.IDPTemplate, externalUser *domain.ExternalUser) bool { + queries := make([]query.SearchQuery, 0, 2) + var user *query.NotifyUser + switch provider.AutoLinking { + case domain.AutoLinkingOptionUnspecified: + // is auto linking is disable, we shouldn't even get here, but in case we do we can directly return + return false + case domain.AutoLinkingOptionUsername: + // if we're checking for usernames there are to options: + // + // If no specific org has been requested (by id or domain scope), we'll check the provided username against + // all existing loginnames and directly use that result to either prompt or continue with other idp options. + if authReq.RequestedOrgID == "" { + user, err := l.query.GetNotifyUserByLoginName(r.Context(), false, externalUser.PreferredUsername) + if err != nil { + return false + } + l.renderLinkingUserPrompt(w, r, authReq, user, nil) + return true + } + // If a specific org has been requested, we'll check the provided username against usernames (of that org). + usernameQuery, err := query.NewUserUsernameSearchQuery(externalUser.PreferredUsername, query.TextEqualsIgnoreCase) + if err != nil { + return false + } + queries = append(queries, usernameQuery) + case domain.AutoLinkingOptionEmail: + // Email will always be checked against verified email addresses. + emailQuery, err := query.NewUserVerifiedEmailSearchQuery(string(externalUser.Email)) + if err != nil { + return false + } + queries = append(queries, emailQuery) + } + // restrict the possible organization if needed (for email and usernames) + if authReq.RequestedOrgID != "" { + resourceOwnerQuery, err := query.NewUserResourceOwnerSearchQuery(authReq.RequestedOrgID, query.TextEquals) + if err != nil { + return false + } + queries = append(queries, resourceOwnerQuery) + } + user, err := l.query.GetNotifyUser(r.Context(), false, queries...) + if err != nil { + return false + } + l.renderLinkingUserPrompt(w, r, authReq, user, nil) + return true +} + // externalUserNotExisting is called if an externalAuthentication couldn't find a corresponding externalID // possible solutions are: // @@ -470,6 +523,13 @@ func (l *Login) externalUserNotExisting(w http.ResponseWriter, r *http.Request, } human, idpLink, _ := mapExternalUserToLoginUser(externalUser, orgIAMPolicy.UserLoginMustBeDomain) + // let's check if auto-linking is enabled and if the user would be found by the corresponding option + if provider.AutoLinking != domain.AutoLinkingOptionUnspecified { + if l.checkAutoLinking(w, r, authReq, provider, externalUser) { + return + } + } + // if auto creation or creation itself is disabled, send the user to the notFoundOption if !provider.IsCreationAllowed || !provider.IsAutoCreation { l.renderExternalNotFoundOption(w, r, authReq, orgIAMPolicy, human, idpLink, err) diff --git a/internal/api/ui/login/link_prompt_handler.go b/internal/api/ui/login/link_prompt_handler.go new file mode 100644 index 0000000000..04293bd864 --- /dev/null +++ b/internal/api/ui/login/link_prompt_handler.go @@ -0,0 +1,62 @@ +package login + +import ( + "net/http" + + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/query" +) + +const ( + tmplLinkingUserPrompt = "link_user_prompt" +) + +type linkingUserPromptData struct { + userData + Username string + Linking domain.AutoLinkingOption + UserID string +} + +type linkingUserPromptFormData struct { + OtherUser bool `schema:"other"` + UserID string `schema:"userID"` +} + +func (l *Login) renderLinkingUserPrompt(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, user *query.NotifyUser, err error) { + var errID, errMessage string + if err != nil { + errID, errMessage = l.getErrorMessage(r, err) + } + translator := l.getTranslator(r.Context(), authReq) + identification := user.PreferredLoginName + // hide the suffix in case the option is set and the auth request has been started with the primary domain scope + if authReq.RequestedOrgDomain && authReq.LabelPolicy != nil && authReq.LabelPolicy.HideLoginNameSuffix { + identification = user.Username + } + data := &linkingUserPromptData{ + Username: identification, + UserID: user.ID, + userData: l.getUserData(r, authReq, translator, "LinkingUserPrompt.Title", "LinkingUserPrompt.Description", errID, errMessage), + } + l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplLinkingUserPrompt], data, nil) +} + +func (l *Login) handleLinkingUserPrompt(w http.ResponseWriter, r *http.Request) { + data := new(linkingUserPromptFormData) + authReq, err := l.getAuthRequestAndParseData(r, data) + if err != nil { + l.renderLogin(w, r, authReq, err) + return + } + if data.OtherUser { + l.renderExternalNotFoundOption(w, r, authReq, nil, nil, nil, nil) + return + } + err = l.authRepo.SelectUser(r.Context(), authReq.ID, data.UserID, authReq.AgentID) + if err != nil { + l.renderLogin(w, r, authReq, err) + return + } + l.renderNextStep(w, r, authReq) +} diff --git a/internal/api/ui/login/renderer.go b/internal/api/ui/login/renderer.go index f4ca69b771..2154248bae 100644 --- a/internal/api/ui/login/renderer.go +++ b/internal/api/ui/login/renderer.go @@ -83,6 +83,7 @@ func CreateRenderer(pathPrefix string, staticStorage static.Storage, cookieName tmplLDAPLogin: "ldap_login.html", tmplDeviceAuthUserCode: "device_usercode.html", tmplDeviceAuthAction: "device_action.html", + tmplLinkingUserPrompt: "link_user_prompt.html", } funcs := map[string]interface{}{ "resourceUrl": func(file string) string { @@ -235,6 +236,9 @@ func CreateRenderer(pathPrefix string, staticStorage static.Storage, cookieName "ldapUrl": func() string { return path.Join(r.pathPrefix, EndpointLDAPCallback) }, + "linkingUserPromptUrl": func() string { + return path.Join(r.pathPrefix, EndpointLinkingUserPrompt) + }, } var err error r.Renderer, err = renderer.NewRenderer( diff --git a/internal/api/ui/login/router.go b/internal/api/ui/login/router.go index 414ffb1919..1e5a297b06 100644 --- a/internal/api/ui/login/router.go +++ b/internal/api/ui/login/router.go @@ -53,6 +53,8 @@ const ( EndpointDeviceAuth = "/device" EndpointDeviceAuthAction = "/device/{action}" + + EndpointLinkingUserPrompt = "/link/user" ) var ( @@ -122,5 +124,6 @@ func CreateRouter(login *Login, interceptors ...mux.MiddlewareFunc) *mux.Router router.SkipClean(true).Handle("", http.RedirectHandler(HandlerPrefix+"/", http.StatusMovedPermanently)) router.HandleFunc(EndpointDeviceAuth, login.handleDeviceAuthUserCode).Methods(http.MethodGet, http.MethodPost) router.HandleFunc(EndpointDeviceAuthAction, login.handleDeviceAuthAction).Methods(http.MethodGet, http.MethodPost) + router.HandleFunc(EndpointLinkingUserPrompt, login.handleLinkingUserPrompt).Methods(http.MethodPost) return router } diff --git a/internal/api/ui/login/static/i18n/bg.yaml b/internal/api/ui/login/static/i18n/bg.yaml index 2f3d8741ff..12f5e57bd6 100644 --- a/internal/api/ui/login/static/i18n/bg.yaml +++ b/internal/api/ui/login/static/i18n/bg.yaml @@ -316,6 +316,11 @@ LogoutDone: Title: Излязъл Description: Вие излязохте успешно. LoginButtonText: Влизам +LinkingUserPrompt: + Title: Намерен съществуващ потребител + Description: „Искате ли да свържете съществуващия си акаунт:“ + LinkButtonText: Връзка + OtherButtonText: Други възможности LinkingUsersDone: Title: Свързване с потребители Description: Свързването с потребители е готово. diff --git a/internal/api/ui/login/static/i18n/cs.yaml b/internal/api/ui/login/static/i18n/cs.yaml index e1f136ff9a..3b97b14bf2 100644 --- a/internal/api/ui/login/static/i18n/cs.yaml +++ b/internal/api/ui/login/static/i18n/cs.yaml @@ -325,6 +325,12 @@ LogoutDone: Description: Byli jste úspěšně odhlášeni. LoginButtonText: Přihlásit se +LinkingUserPrompt: + Title: Nalezen stávající uživatel + Description: "Chcete propojit svůj stávající účet:" + LinkButtonText: Odkaz + OtherButtonText: Jiné možnosti + LinkingUsersDone: Title: Propojení uživatele Description: Uživatel propojen. diff --git a/internal/api/ui/login/static/i18n/de.yaml b/internal/api/ui/login/static/i18n/de.yaml index 74e4d8cdf1..d258f09aa6 100644 --- a/internal/api/ui/login/static/i18n/de.yaml +++ b/internal/api/ui/login/static/i18n/de.yaml @@ -324,6 +324,12 @@ LogoutDone: Description: Du wurdest erfolgreich abgemeldet. LoginButtonText: Anmelden +LinkingUserPrompt: + Title: Vorhandener Benutzer gefunden + Description: "Möchten Sie Ihr bestehendes Konto verknüpfen:" + LinkButtonText: Verknüpfen + OtherButtonText: Andere Optionen + LinkingUsersDone: Title: Benutzerkonto verknpüfen Description: Benuzterkonto verknpüft. diff --git a/internal/api/ui/login/static/i18n/en.yaml b/internal/api/ui/login/static/i18n/en.yaml index 247029b1d7..338107060d 100644 --- a/internal/api/ui/login/static/i18n/en.yaml +++ b/internal/api/ui/login/static/i18n/en.yaml @@ -325,6 +325,12 @@ LogoutDone: Description: You have logged out successfully. LoginButtonText: Login +LinkingUserPrompt: + Title: Existing User Found + Description: "Do you want to link your existing account:" + LinkButtonText: Link + OtherButtonText: Other options + LinkingUsersDone: Title: Linking User Description: User linked. diff --git a/internal/api/ui/login/static/i18n/es.yaml b/internal/api/ui/login/static/i18n/es.yaml index 1f2a288266..ef091f2910 100644 --- a/internal/api/ui/login/static/i18n/es.yaml +++ b/internal/api/ui/login/static/i18n/es.yaml @@ -325,6 +325,12 @@ LogoutDone: Description: Cerraste la sesión con éxito. LoginButtonText: iniciar sesión +LinkingUserPrompt: + Title: Usuario existente encontrado + Description: "¿Quieres vincular tu cuenta existente?" + LinkButtonText: Vincular + OtherButtonText: Otras opciones + LinkingUsersDone: Title: Vinculación de usuario Description: usuario vinculado con éxito. diff --git a/internal/api/ui/login/static/i18n/fr.yaml b/internal/api/ui/login/static/i18n/fr.yaml index fc101eb627..de5d1ca495 100644 --- a/internal/api/ui/login/static/i18n/fr.yaml +++ b/internal/api/ui/login/static/i18n/fr.yaml @@ -325,6 +325,12 @@ LogoutDone: Description: Vous vous êtes déconnecté avec succès. LoginButtonText: connexion +LinkingUserPrompt: + Title: Utilisateur existant trouvé + Description: "Souhaitez-vous associer votre compte existant:" + LinkButtonText: Lier + OtherButtonText: Autres options + LinkingUsersDone: Title: Userlinking Description: Le lien avec l'utilisateur est terminé. diff --git a/internal/api/ui/login/static/i18n/it.yaml b/internal/api/ui/login/static/i18n/it.yaml index 277513436c..24e9733943 100644 --- a/internal/api/ui/login/static/i18n/it.yaml +++ b/internal/api/ui/login/static/i18n/it.yaml @@ -325,6 +325,12 @@ LogoutDone: Description: Ti sei disconnesso con successo. LoginButtonText: Accedi +LinkingUserPrompt: + Title: Utente esistente trovato + Description: "Desideri collegare il tuo account esistente:" + LinkButtonText: Collegare + OtherButtonText: Altre opzioni + LinkingUsersDone: Title: Collegamento utente Description: Collegamento fatto. diff --git a/internal/api/ui/login/static/i18n/ja.yaml b/internal/api/ui/login/static/i18n/ja.yaml index 18070eebdd..e4ca75bad7 100644 --- a/internal/api/ui/login/static/i18n/ja.yaml +++ b/internal/api/ui/login/static/i18n/ja.yaml @@ -317,6 +317,12 @@ LogoutDone: Description: 正常にログアウトしました。 LoginButtonText: ログイン +LinkingUserPrompt: + Title: 既存のユーザーが見つかりました + Description: "既存のアカウントをリンクしますか:" + LinkButtonText: リンク + OtherButtonText: その他のオプション + LinkingUsersDone: Title: ユーザーリンク Description: ユーザーリンクが完了しました。 diff --git a/internal/api/ui/login/static/i18n/mk.yaml b/internal/api/ui/login/static/i18n/mk.yaml index cbe4fa64e7..dafc520d56 100644 --- a/internal/api/ui/login/static/i18n/mk.yaml +++ b/internal/api/ui/login/static/i18n/mk.yaml @@ -325,6 +325,12 @@ LogoutDone: Description: Успешно сте одјавени. LoginButtonText: најава +LinkingUserPrompt: + Title: Пронајден е постоечки корисник + Description: "Дали сакате да ја поврзете вашата постоечка сметка:" + LinkButtonText: Bрска + OtherButtonText: Други опции + LinkingUsersDone: Title: Поврзување на корисници Description: Поврзувањето на корисници е завршено. diff --git a/internal/api/ui/login/static/i18n/nl.yaml b/internal/api/ui/login/static/i18n/nl.yaml index 6a5cdba1a6..68d8ffa4a1 100644 --- a/internal/api/ui/login/static/i18n/nl.yaml +++ b/internal/api/ui/login/static/i18n/nl.yaml @@ -325,6 +325,12 @@ LogoutDone: Description: U heeft succesvol uitgelogd. LoginButtonText: Inloggen +LinkingUserPrompt: + Title: Bestaande gebruiker gevonden + Description: "Wilt u uw bestaande account koppelen:" + LinkButtonText: Koppeling + OtherButtonText: Andere opties + LinkingUsersDone: Title: Koppeling Gebruiker Description: Gebruiker gekoppeld. diff --git a/internal/api/ui/login/static/i18n/pl.yaml b/internal/api/ui/login/static/i18n/pl.yaml index 5e920c7f2a..e23ce3fa71 100644 --- a/internal/api/ui/login/static/i18n/pl.yaml +++ b/internal/api/ui/login/static/i18n/pl.yaml @@ -325,6 +325,12 @@ LogoutDone: Description: Wylogowano pomyślnie. LoginButtonText: Zaloguj się +LinkingUserPrompt: + Title: Znaleziono istniejącego użytkownika + Description: "Czy chcesz połączyć swoje istniejące konto:" + LinkButtonText: Połączyć + OtherButtonText: Inne opcje + LinkingUsersDone: Title: Łączenie użytkowników Description: Łączenie użytkowników zakończone pomyślnie. diff --git a/internal/api/ui/login/static/i18n/pt.yaml b/internal/api/ui/login/static/i18n/pt.yaml index 3016c49dda..19d00c72f4 100644 --- a/internal/api/ui/login/static/i18n/pt.yaml +++ b/internal/api/ui/login/static/i18n/pt.yaml @@ -321,6 +321,12 @@ LogoutDone: Description: Você fez logout com sucesso. LoginButtonText: login +LinkingUserPrompt: + Title: Usuário existente encontrado + Description: "Deseja vincular sua conta existente:" + LinkButtonText: Link + OtherButtonText: Outras opções + LinkingUsersDone: Title: Vinculação de usuários Description: Vinculação de usuários concluída. diff --git a/internal/api/ui/login/static/i18n/ru.yaml b/internal/api/ui/login/static/i18n/ru.yaml index 5782e026f8..fb88312486 100644 --- a/internal/api/ui/login/static/i18n/ru.yaml +++ b/internal/api/ui/login/static/i18n/ru.yaml @@ -324,6 +324,12 @@ LogoutDone: Description: Вы успешно вышли из системы. LoginButtonText: вход +LinkingUserPrompt: + Title: Существующий пользователь найден + Description: "Хотите ли вы связать существующую учетную запись:" + LinkButtonText: Связь + OtherButtonText: Другие варианты + LinkingUsersDone: Title: Привязка пользователя Description: Привязка пользователя выполнена. diff --git a/internal/api/ui/login/static/i18n/zh.yaml b/internal/api/ui/login/static/i18n/zh.yaml index 58bd349499..bf31d270bb 100644 --- a/internal/api/ui/login/static/i18n/zh.yaml +++ b/internal/api/ui/login/static/i18n/zh.yaml @@ -325,6 +325,12 @@ LogoutDone: Description: 您已成功退出登录。 LoginButtonText: 登录 +LinkingUserPrompt: + Title: 已找到现有用户 + Description: "您想关联您现有的帐户吗:" + LinkButtonText: 关联 + OtherButtonText: 其他选项 + LinkingUsersDone: Title: 用户链接 Description: 用户链接完成。 diff --git a/internal/api/ui/login/static/templates/link_user_prompt.html b/internal/api/ui/login/static/templates/link_user_prompt.html new file mode 100644 index 0000000000..9d6ed36cd1 --- /dev/null +++ b/internal/api/ui/login/static/templates/link_user_prompt.html @@ -0,0 +1,37 @@ +{{template "main-top" .}} + +
+

{{t "LinkingUserPrompt.Title"}}

+

+ {{t "LinkingUserPrompt.Description"}}
+ {{.Username}} +

+
+ + +
+ + {{ .CSRF }} + + + + + {{template "error-message" .}} + +
+ + + + + + +
+ +
+ + + + + + +{{template "main-bottom" .}} diff --git a/internal/auth/repository/auth_request.go b/internal/auth/repository/auth_request.go index d101a436ff..d89eb35a8b 100644 --- a/internal/auth/repository/auth_request.go +++ b/internal/auth/repository/auth_request.go @@ -19,7 +19,7 @@ type AuthRequestRepository interface { CheckExternalUserLogin(ctx context.Context, authReqID, userAgentID string, user *domain.ExternalUser, info *domain.BrowserInfo, migrationCheck bool) error SetExternalUserLogin(ctx context.Context, authReqID, userAgentID string, user *domain.ExternalUser) error SetLinkingUser(ctx context.Context, request *domain.AuthRequest, externalUser *domain.ExternalUser) error - SelectUser(ctx context.Context, id, userID, userAgentID string) error + SelectUser(ctx context.Context, authReqID, userID, userAgentID string) error SelectExternalIDP(ctx context.Context, authReqID, idpConfigID, userAgentID string) error VerifyPassword(ctx context.Context, id, userID, resourceOwner, password, userAgentID string, info *domain.BrowserInfo) error diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 98912c9309..b7c85ab79e 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -304,10 +304,10 @@ func (repo *AuthRequestRepo) setLinkingUser(ctx context.Context, request *domain return repo.AuthRequests.UpdateAuthRequest(ctx, request) } -func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID, userAgentID string) (err error) { +func (repo *AuthRequestRepo) SelectUser(ctx context.Context, authReqID, userID, userAgentID string) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() - request, err := repo.getAuthRequest(ctx, id, userAgentID) + request, err := repo.getAuthRequest(ctx, authReqID, userAgentID) if err != nil { return err } diff --git a/internal/command/custom_login_text.go b/internal/command/custom_login_text.go index 21147c616d..9c22e1e298 100644 --- a/internal/command/custom_login_text.go +++ b/internal/command/custom_login_text.go @@ -42,7 +42,8 @@ func (c *Commands) createAllLoginTextEvents(ctx context.Context, agg *eventstore events = append(events, c.createRegistrationUserEvents(ctx, agg, existingText, text, defaultText)...) events = append(events, c.createExternalRegistrationUserOverviewEvents(ctx, agg, existingText, text, defaultText)...) events = append(events, c.createRegistrationOrgEvents(ctx, agg, existingText, text, defaultText)...) - events = append(events, c.createLinkingUserEvents(ctx, agg, existingText, text, defaultText)...) + events = append(events, c.createLinkingUserPromptEvents(ctx, agg, existingText, text, defaultText)...) + events = append(events, c.createLinkingUserDoneEvents(ctx, agg, existingText, text, defaultText)...) events = append(events, c.createExternalUserNotFoundEvents(ctx, agg, existingText, text, defaultText)...) events = append(events, c.createSuccessLoginEvents(ctx, agg, existingText, text, defaultText)...) events = append(events, c.createLogoutDoneEvents(ctx, agg, existingText, text, defaultText)...) @@ -979,7 +980,28 @@ func (c *Commands) createRegistrationOrgEvents(ctx context.Context, agg *eventst return events } -func (c *Commands) createLinkingUserEvents(ctx context.Context, agg *eventstore.Aggregate, existingText *CustomLoginTextReadModel, text *domain.CustomLoginText, defaultText bool) []eventstore.Command { +func (c *Commands) createLinkingUserPromptEvents(ctx context.Context, agg *eventstore.Aggregate, existingText *CustomLoginTextReadModel, text *domain.CustomLoginText, defaultText bool) []eventstore.Command { + events := make([]eventstore.Command, 0) + event := c.createCustomLoginTextEvent(ctx, agg, domain.LoginKeyLinkingUserPromptTitle, existingText.LinkingUserPromptTitle, text.LinkingUserPrompt.Title, text.Language, defaultText) + if event != nil { + events = append(events, event) + } + event = c.createCustomLoginTextEvent(ctx, agg, domain.LoginKeyLinkingUserPromptDescription, existingText.LinkingUserPromptDescription, text.LinkingUserPrompt.Description, text.Language, defaultText) + if event != nil { + events = append(events, event) + } + event = c.createCustomLoginTextEvent(ctx, agg, domain.LoginKeyLinkingUserPromptLinkButtonText, existingText.LinkingUserPromptLinkButtonText, text.LinkingUserPrompt.LinkButtonText, text.Language, defaultText) + if event != nil { + events = append(events, event) + } + event = c.createCustomLoginTextEvent(ctx, agg, domain.LoginKeyLinkingUserPromptOtherButtonText, existingText.LinkingUserPromptOtherButtonText, text.LinkingUserPrompt.OtherButtonText, text.Language, defaultText) + if event != nil { + events = append(events, event) + } + return events +} + +func (c *Commands) createLinkingUserDoneEvents(ctx context.Context, agg *eventstore.Aggregate, existingText *CustomLoginTextReadModel, text *domain.CustomLoginText, defaultText bool) []eventstore.Command { events := make([]eventstore.Command, 0) event := c.createCustomLoginTextEvent(ctx, agg, domain.LoginKeyLinkingUserDoneTitle, existingText.LinkingUserDoneTitle, text.LinkingUsersDone.Title, text.Language, defaultText) if event != nil { diff --git a/internal/command/custom_login_text_model.go b/internal/command/custom_login_text_model.go index 9815293b94..b2147efefc 100644 --- a/internal/command/custom_login_text_model.go +++ b/internal/command/custom_login_text_model.go @@ -262,6 +262,11 @@ type CustomLoginTextReadModel struct { RegisterOrgPrivacyLinkText string RegisterOrgSaveButtonText string + LinkingUserPromptTitle string + LinkingUserPromptDescription string + LinkingUserPromptLinkButtonText string + LinkingUserPromptOtherButtonText string + LinkingUserDoneTitle string LinkingUserDoneDescription string LinkingUserDoneCancelButtonText string @@ -416,6 +421,10 @@ func (wm *CustomLoginTextReadModel) Reduce() error { wm.handleRegistrationOrgScreenSetEvent(e) continue } + if strings.HasPrefix(e.Key, domain.LoginKeyLinkingUserPrompt) { + wm.handleLinkingUserPromptScreenSetEvent(e) + continue + } if strings.HasPrefix(e.Key, domain.LoginKeyLinkingUserDone) { wm.handleLinkingUserDoneScreenSetEvent(e) continue @@ -556,6 +565,10 @@ func (wm *CustomLoginTextReadModel) Reduce() error { wm.handleRegistrationOrgScreenRemoveEvent(e) continue } + if strings.HasPrefix(e.Key, domain.LoginKeyLinkingUserPrompt) { + wm.handleLinkingUserPromptRemoveEvent(e) + continue + } if strings.HasPrefix(e.Key, domain.LoginKeyLinkingUserDone) { wm.handleLinkingUserDoneRemoveEvent(e) continue @@ -2323,6 +2336,25 @@ func (wm *CustomLoginTextReadModel) handleRegistrationOrgScreenRemoveEvent(e *po } } +func (wm *CustomLoginTextReadModel) handleLinkingUserPromptScreenSetEvent(e *policy.CustomTextSetEvent) { + if e.Key == domain.LoginKeyLinkingUserPromptTitle { + wm.LinkingUserPromptTitle = e.Text + return + } + if e.Key == domain.LoginKeyLinkingUserPromptDescription { + wm.LinkingUserPromptDescription = e.Text + return + } + if e.Key == domain.LoginKeyLinkingUserPromptLinkButtonText { + wm.LinkingUserPromptLinkButtonText = e.Text + return + } + if e.Key == domain.LoginKeyLinkingUserPromptOtherButtonText { + wm.LinkingUserPromptOtherButtonText = e.Text + return + } +} + func (wm *CustomLoginTextReadModel) handleLinkingUserDoneScreenSetEvent(e *policy.CustomTextSetEvent) { if e.Key == domain.LoginKeyLinkingUserDoneTitle { wm.LinkingUserDoneTitle = e.Text @@ -2342,6 +2374,25 @@ func (wm *CustomLoginTextReadModel) handleLinkingUserDoneScreenSetEvent(e *polic } } +func (wm *CustomLoginTextReadModel) handleLinkingUserPromptRemoveEvent(e *policy.CustomTextRemovedEvent) { + if e.Key == domain.LoginKeyLinkingUserPromptTitle { + wm.LinkingUserPromptTitle = "" + return + } + if e.Key == domain.LoginKeyLinkingUserPromptDescription { + wm.LinkingUserPromptDescription = "" + return + } + if e.Key == domain.LoginKeyLinkingUserPromptLinkButtonText { + wm.LinkingUserPromptLinkButtonText = "" + return + } + if e.Key == domain.LoginKeyLinkingUserPromptOtherButtonText { + wm.LinkingUserPromptOtherButtonText = "" + return + } +} + func (wm *CustomLoginTextReadModel) handleLinkingUserDoneRemoveEvent(e *policy.CustomTextRemovedEvent) { if e.Key == domain.LoginKeyLinkingUserDoneTitle { wm.LinkingUserDoneTitle = "" diff --git a/internal/command/instance_custom_login_text_test.go b/internal/command/instance_custom_login_text_test.go index 4ec29acab4..abdaf7307d 100644 --- a/internal/command/instance_custom_login_text_test.go +++ b/internal/command/instance_custom_login_text_test.go @@ -676,6 +676,18 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { instance.NewCustomTextSetEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, "SaveButtonText", language.English, ), + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, "Title", language.English, + ), + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, "Description", language.English, + ), + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, "LinkButtonText", language.English, + ), + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, "OtherButtonText", language.English, + ), instance.NewCustomTextSetEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserDoneTitle, "Title", language.English, ), @@ -1009,6 +1021,12 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { PrivacyLinkText: "PrivacyLinkText", SaveButtonText: "SaveButtonText", }, + LinkingUserPrompt: domain.LinkingUserPromptScreenText{ + Title: "Title", + Description: "Description", + LinkButtonText: "LinkButtonText", + OtherButtonText: "OtherButtonText", + }, LinkingUsersDone: domain.LinkingUserDoneScreenText{ Title: "Title", Description: "Description", @@ -2233,6 +2251,30 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, "SaveButtonText", language.English, ), ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, "Title", language.English, + ), + ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, "Description", language.English, + ), + ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, "LinkButtonText", language.English, + ), + ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, "OtherButtonText", language.English, + ), + ), eventFromEventPusherWithInstanceID( "INSTANCE", instance.NewCustomTextSetEvent(context.Background(), @@ -2967,6 +3009,18 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { instance.NewCustomTextRemovedEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, language.English, ), + instance.NewCustomTextRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, language.English, + ), + instance.NewCustomTextRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, language.English, + ), + instance.NewCustomTextRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, language.English, + ), + instance.NewCustomTextRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, language.English, + ), instance.NewCustomTextRemovedEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserDoneTitle, language.English, ), @@ -3075,6 +3129,7 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { RegistrationUser: domain.RegistrationUserScreenText{}, ExternalRegistrationUserOverview: domain.ExternalRegistrationUserOverviewScreenText{}, RegistrationOrg: domain.RegistrationOrgScreenText{}, + LinkingUserPrompt: domain.LinkingUserPromptScreenText{}, LinkingUsersDone: domain.LinkingUserDoneScreenText{}, ExternalNotFound: domain.ExternalUserNotFoundScreenText{}, LoginSuccess: domain.SuccessLoginScreenText{}, @@ -4271,6 +4326,30 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, "SaveButtonText", language.English, ), ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, "Title", language.English, + ), + ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, "Description", language.English, + ), + ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, "LinkButtonText", language.English, + ), + ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, "OtherButtonText", language.English, + ), + ), eventFromEventPusherWithInstanceID( "INSTANCE", instance.NewCustomTextSetEvent(context.Background(), @@ -5592,6 +5671,30 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, language.English, ), ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, language.English, + ), + ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, language.English, + ), + ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, language.English, + ), + ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, language.English, + ), + ), eventFromEventPusherWithInstanceID( "INSTANCE", instance.NewCustomTextRemovedEvent(context.Background(), @@ -6326,6 +6429,18 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { instance.NewCustomTextSetEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, "SaveButtonText", language.English, ), + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, "Title", language.English, + ), + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, "Description", language.English, + ), + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, "LinkButtonText", language.English, + ), + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, "OtherButtonText", language.English, + ), instance.NewCustomTextSetEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserDoneTitle, "Title", language.English, ), @@ -6659,6 +6774,12 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { PrivacyLinkText: "PrivacyLinkText", SaveButtonText: "SaveButtonText", }, + LinkingUserPrompt: domain.LinkingUserPromptScreenText{ + Title: "Title", + Description: "Description", + LinkButtonText: "LinkButtonText", + OtherButtonText: "OtherButtonText", + }, LinkingUsersDone: domain.LinkingUserDoneScreenText{ Title: "Title", Description: "Description", diff --git a/internal/command/org_custom_login_text_test.go b/internal/command/org_custom_login_text_test.go index ede20ef5c0..0411a533a4 100644 --- a/internal/command/org_custom_login_text_test.go +++ b/internal/command/org_custom_login_text_test.go @@ -1083,6 +1083,26 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, "SaveButtonText", language.English, ), ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, "Title", language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, "Description", language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, "LinkButtonText", language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, "OtherButtonText", language.English, + ), + ), eventFromEventPusher( org.NewCustomTextSetEvent(context.Background(), &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserDoneTitle, "Title", language.English, @@ -1464,6 +1484,12 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { PrivacyLinkText: "PrivacyLinkText", SaveButtonText: "SaveButtonText", }, + LinkingUserPrompt: domain.LinkingUserPromptScreenText{ + Title: "Title", + Description: "Description", + LinkButtonText: "LinkButtonText", + OtherButtonText: "OtherButtonText", + }, LinkingUsersDone: domain.LinkingUserDoneScreenText{ Title: "Title", Description: "Description", @@ -2487,6 +2513,26 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, "SaveButtonText", language.English, ), ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, "Title", language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, "Description", language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, "LinkButtonText", language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, "OtherButtonText", language.English, + ), + ), eventFromEventPusher( org.NewCustomTextSetEvent(context.Background(), &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserDoneTitle, "Title", language.English, @@ -3194,6 +3240,18 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { org.NewCustomTextRemovedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, language.English, ), + org.NewCustomTextRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, language.English, + ), + org.NewCustomTextRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, language.English, + ), + org.NewCustomTextRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, language.English, + ), + org.NewCustomTextRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, language.English, + ), org.NewCustomTextRemovedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserDoneTitle, language.English, ), @@ -3303,6 +3361,7 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { ExternalRegistrationUserOverview: domain.ExternalRegistrationUserOverviewScreenText{}, RegistrationUser: domain.RegistrationUserScreenText{}, RegistrationOrg: domain.RegistrationOrgScreenText{}, + LinkingUserPrompt: domain.LinkingUserPromptScreenText{}, LinkingUsersDone: domain.LinkingUserDoneScreenText{}, ExternalNotFound: domain.ExternalUserNotFoundScreenText{}, LoginSuccess: domain.SuccessLoginScreenText{}, @@ -4297,6 +4356,26 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, "SaveButtonText", language.English, ), ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, "Title", language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, "Description", language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, "LinkButtonText", language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, "OtherButtonText", language.English, + ), + ), eventFromEventPusher( org.NewCustomTextSetEvent(context.Background(), &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserDoneTitle, "Title", language.English, @@ -5392,6 +5471,26 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, language.English, ), ), + eventFromEventPusher( + org.NewCustomTextRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, language.English, + ), + ), + eventFromEventPusher( + org.NewCustomTextRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, language.English, + ), + ), eventFromEventPusher( org.NewCustomTextRemovedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserDoneTitle, language.English, @@ -6099,6 +6198,18 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { org.NewCustomTextSetEvent(context.Background(), &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyRegisterOrgSaveButtonText, "SaveButtonText", language.English, ), + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptTitle, "Title", language.English, + ), + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptDescription, "Description", language.English, + ), + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptLinkButtonText, "LinkButtonText", language.English, + ), + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserPromptOtherButtonText, "OtherButtonText", language.English, + ), org.NewCustomTextSetEvent(context.Background(), &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyLinkingUserDoneTitle, "Title", language.English, ), @@ -6432,6 +6543,12 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { PrivacyLinkText: "PrivacyLinkText", SaveButtonText: "SaveButtonText", }, + LinkingUserPrompt: domain.LinkingUserPromptScreenText{ + Title: "Title", + Description: "Description", + LinkButtonText: "LinkButtonText", + OtherButtonText: "OtherButtonText", + }, LinkingUsersDone: domain.LinkingUserDoneScreenText{ Title: "Title", Description: "Description", diff --git a/internal/domain/custom_login_text.go b/internal/domain/custom_login_text.go index 63a5599eb8..b8f4173b34 100644 --- a/internal/domain/custom_login_text.go +++ b/internal/domain/custom_login_text.go @@ -264,6 +264,12 @@ const ( LoginKeyRegisterOrgSaveButtonText = LoginKeyRegistrationOrg + "SaveButtonText" LoginKeyRegisterOrgBackButtonText = LoginKeyRegistrationOrg + "BackButtonText" + LoginKeyLinkingUserPrompt = "LinkingUserPrompt." + LoginKeyLinkingUserPromptTitle = LoginKeyLinkingUserPrompt + "Title" + LoginKeyLinkingUserPromptDescription = LoginKeyLinkingUserPrompt + "Description" + LoginKeyLinkingUserPromptLinkButtonText = LoginKeyLinkingUserPrompt + "LinkButtonText" + LoginKeyLinkingUserPromptOtherButtonText = LoginKeyLinkingUserPrompt + "OtherButtonText" + LoginKeyLinkingUserDone = "LinkingUsersDone." LoginKeyLinkingUserDoneTitle = LoginKeyLinkingUserDone + "Title" LoginKeyLinkingUserDoneDescription = LoginKeyLinkingUserDone + "Description" @@ -336,6 +342,7 @@ type CustomLoginText struct { RegistrationUser RegistrationUserScreenText ExternalRegistrationUserOverview ExternalRegistrationUserOverviewScreenText RegistrationOrg RegistrationOrgScreenText + LinkingUserPrompt LinkingUserPromptScreenText LinkingUsersDone LinkingUserDoneScreenText ExternalNotFound ExternalUserNotFoundScreenText LoginSuccess SuccessLoginScreenText @@ -607,6 +614,13 @@ type RegistrationOrgScreenText struct { SaveButtonText string } +type LinkingUserPromptScreenText struct { + Title string + Description string + LinkButtonText string + OtherButtonText string +} + type LinkingUserDoneScreenText struct { Title string Description string diff --git a/internal/domain/idp.go b/internal/domain/idp.go index 76c2e38cf9..3df11fa0d7 100644 --- a/internal/domain/idp.go +++ b/internal/domain/idp.go @@ -126,3 +126,11 @@ func (s IDPIntentState) Valid() bool { func (s IDPIntentState) Exists() bool { return s != IDPIntentStateUnspecified && s != IDPIntentStateFailed //TODO: ? } + +type AutoLinkingOption uint8 + +const ( + AutoLinkingOptionUnspecified AutoLinkingOption = iota + AutoLinkingOptionUsername + AutoLinkingOptionEmail +) diff --git a/internal/query/custom_text.go b/internal/query/custom_text.go index 3bd3bf0793..5558585be1 100644 --- a/internal/query/custom_text.go +++ b/internal/query/custom_text.go @@ -409,8 +409,11 @@ func CustomTextsToLoginDomain(instanceID, aggregateID, lang string, texts *Custo if strings.HasPrefix(text.Key, domain.LoginKeyRegistrationOrg) { registrationOrgKeyToDomain(text, result) } + if strings.HasPrefix(text.Key, domain.LoginKeyLinkingUserPrompt) { + linkingUserPromptKeyToDomain(text, result) + } if strings.HasPrefix(text.Key, domain.LoginKeyLinkingUserDone) { - linkingUserKeyToDomain(text, result) + linkingUserDoneKeyToDomain(text, result) } if strings.HasPrefix(text.Key, domain.LoginKeyExternalNotFound) { externalUserNotFoundKeyToDomain(text, result) @@ -1100,7 +1103,22 @@ func registrationOrgKeyToDomain(text *CustomText, result *domain.CustomLoginText } } -func linkingUserKeyToDomain(text *CustomText, result *domain.CustomLoginText) { +func linkingUserPromptKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyLinkingUserPromptTitle { + result.LinkingUserPrompt.Title = text.Text + } + if text.Key == domain.LoginKeyLinkingUserPromptDescription { + result.LinkingUserPrompt.Description = text.Text + } + if text.Key == domain.LoginKeyLinkingUserPromptLinkButtonText { + result.LinkingUserPrompt.LinkButtonText = text.Text + } + if text.Key == domain.LoginKeyLinkingUserPromptOtherButtonText { + result.LinkingUserPrompt.OtherButtonText = text.Text + } +} + +func linkingUserDoneKeyToDomain(text *CustomText, result *domain.CustomLoginText) { if text.Key == domain.LoginKeyLinkingUserDoneTitle { result.LinkingUsersDone.Title = text.Text } diff --git a/internal/query/idp_login_policy_link_test.go b/internal/query/idp_login_policy_link_test.go index 4f720b86af..45cb8e594d 100644 --- a/internal/query/idp_login_policy_link_test.go +++ b/internal/query/idp_login_policy_link_test.go @@ -16,12 +16,12 @@ import ( var ( loginPolicyIDPLinksQuery = regexp.QuoteMeta(`SELECT projections.idp_login_policy_links5.idp_id,` + - ` projections.idp_templates5.name,` + - ` projections.idp_templates5.type,` + - ` projections.idp_templates5.owner_type,` + + ` projections.idp_templates6.name,` + + ` projections.idp_templates6.type,` + + ` projections.idp_templates6.owner_type,` + ` COUNT(*) OVER ()` + ` FROM projections.idp_login_policy_links5` + - ` LEFT JOIN projections.idp_templates5 ON projections.idp_login_policy_links5.idp_id = projections.idp_templates5.id AND projections.idp_login_policy_links5.instance_id = projections.idp_templates5.instance_id` + + ` LEFT JOIN projections.idp_templates6 ON projections.idp_login_policy_links5.idp_id = projections.idp_templates6.id AND projections.idp_login_policy_links5.instance_id = projections.idp_templates6.instance_id` + ` RIGHT JOIN (SELECT login_policy_owner.aggregate_id, login_policy_owner.instance_id, login_policy_owner.owner_removed FROM projections.login_policies5 AS login_policy_owner` + ` WHERE (login_policy_owner.instance_id = $1 AND (login_policy_owner.aggregate_id = $2 OR login_policy_owner.aggregate_id = $3)) ORDER BY login_policy_owner.is_default LIMIT 1) AS login_policy_owner` + ` ON login_policy_owner.aggregate_id = projections.idp_login_policy_links5.resource_owner AND login_policy_owner.instance_id = projections.idp_login_policy_links5.instance_id` + diff --git a/internal/query/idp_template.go b/internal/query/idp_template.go index ef14aeeff0..361389f995 100644 --- a/internal/query/idp_template.go +++ b/internal/query/idp_template.go @@ -35,6 +35,7 @@ type IDPTemplate struct { IsLinkingAllowed bool IsAutoCreation bool IsAutoUpdate bool + AutoLinking domain.AutoLinkingOption *OAuthIDPTemplate *OIDCIDPTemplate *JWTIDPTemplate @@ -227,6 +228,10 @@ var ( name: projection.IDPTemplateIsAutoUpdateCol, table: idpTemplateTable, } + IDPTemplateAutoLinkingCol = Column{ + name: projection.IDPTemplateAutoLinkingCol, + table: idpTemplateTable, + } ) var ( @@ -812,6 +817,7 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se IDPTemplateIsLinkingAllowedCol.identifier(), IDPTemplateIsAutoCreationCol.identifier(), IDPTemplateIsAutoUpdateCol.identifier(), + IDPTemplateAutoLinkingCol.identifier(), // oauth OAuthIDCol.identifier(), OAuthClientIDCol.identifier(), @@ -1037,6 +1043,7 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se &idpTemplate.IsLinkingAllowed, &idpTemplate.IsAutoCreation, &idpTemplate.IsAutoUpdate, + &idpTemplate.AutoLinking, // oauth &oauthID, &oauthClientID, @@ -1297,6 +1304,7 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec IDPTemplateIsLinkingAllowedCol.identifier(), IDPTemplateIsAutoCreationCol.identifier(), IDPTemplateIsAutoUpdateCol.identifier(), + IDPTemplateAutoLinkingCol.identifier(), // oauth OAuthIDCol.identifier(), OAuthClientIDCol.identifier(), @@ -1527,6 +1535,7 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec &idpTemplate.IsLinkingAllowed, &idpTemplate.IsAutoCreation, &idpTemplate.IsAutoUpdate, + &idpTemplate.AutoLinking, // oauth &oauthID, &oauthClientID, diff --git a/internal/query/idp_template_test.go b/internal/query/idp_template_test.go index f95b6da05b..72e5d9f8c8 100644 --- a/internal/query/idp_template_test.go +++ b/internal/query/idp_template_test.go @@ -16,128 +16,129 @@ import ( ) var ( - idpTemplateQuery = `SELECT projections.idp_templates5.id,` + - ` projections.idp_templates5.resource_owner,` + - ` projections.idp_templates5.creation_date,` + - ` projections.idp_templates5.change_date,` + - ` projections.idp_templates5.sequence,` + - ` projections.idp_templates5.state,` + - ` projections.idp_templates5.name,` + - ` projections.idp_templates5.type,` + - ` projections.idp_templates5.owner_type,` + - ` projections.idp_templates5.is_creation_allowed,` + - ` projections.idp_templates5.is_linking_allowed,` + - ` projections.idp_templates5.is_auto_creation,` + - ` projections.idp_templates5.is_auto_update,` + + idpTemplateQuery = `SELECT projections.idp_templates6.id,` + + ` projections.idp_templates6.resource_owner,` + + ` projections.idp_templates6.creation_date,` + + ` projections.idp_templates6.change_date,` + + ` projections.idp_templates6.sequence,` + + ` projections.idp_templates6.state,` + + ` projections.idp_templates6.name,` + + ` projections.idp_templates6.type,` + + ` projections.idp_templates6.owner_type,` + + ` projections.idp_templates6.is_creation_allowed,` + + ` projections.idp_templates6.is_linking_allowed,` + + ` projections.idp_templates6.is_auto_creation,` + + ` projections.idp_templates6.is_auto_update,` + + ` projections.idp_templates6.auto_linking,` + // oauth - ` projections.idp_templates5_oauth2.idp_id,` + - ` projections.idp_templates5_oauth2.client_id,` + - ` projections.idp_templates5_oauth2.client_secret,` + - ` projections.idp_templates5_oauth2.authorization_endpoint,` + - ` projections.idp_templates5_oauth2.token_endpoint,` + - ` projections.idp_templates5_oauth2.user_endpoint,` + - ` projections.idp_templates5_oauth2.scopes,` + - ` projections.idp_templates5_oauth2.id_attribute,` + + ` projections.idp_templates6_oauth2.idp_id,` + + ` projections.idp_templates6_oauth2.client_id,` + + ` projections.idp_templates6_oauth2.client_secret,` + + ` projections.idp_templates6_oauth2.authorization_endpoint,` + + ` projections.idp_templates6_oauth2.token_endpoint,` + + ` projections.idp_templates6_oauth2.user_endpoint,` + + ` projections.idp_templates6_oauth2.scopes,` + + ` projections.idp_templates6_oauth2.id_attribute,` + // oidc - ` projections.idp_templates5_oidc.idp_id,` + - ` projections.idp_templates5_oidc.issuer,` + - ` projections.idp_templates5_oidc.client_id,` + - ` projections.idp_templates5_oidc.client_secret,` + - ` projections.idp_templates5_oidc.scopes,` + - ` projections.idp_templates5_oidc.id_token_mapping,` + + ` projections.idp_templates6_oidc.idp_id,` + + ` projections.idp_templates6_oidc.issuer,` + + ` projections.idp_templates6_oidc.client_id,` + + ` projections.idp_templates6_oidc.client_secret,` + + ` projections.idp_templates6_oidc.scopes,` + + ` projections.idp_templates6_oidc.id_token_mapping,` + // jwt - ` projections.idp_templates5_jwt.idp_id,` + - ` projections.idp_templates5_jwt.issuer,` + - ` projections.idp_templates5_jwt.jwt_endpoint,` + - ` projections.idp_templates5_jwt.keys_endpoint,` + - ` projections.idp_templates5_jwt.header_name,` + + ` projections.idp_templates6_jwt.idp_id,` + + ` projections.idp_templates6_jwt.issuer,` + + ` projections.idp_templates6_jwt.jwt_endpoint,` + + ` projections.idp_templates6_jwt.keys_endpoint,` + + ` projections.idp_templates6_jwt.header_name,` + // azure - ` projections.idp_templates5_azure.idp_id,` + - ` projections.idp_templates5_azure.client_id,` + - ` projections.idp_templates5_azure.client_secret,` + - ` projections.idp_templates5_azure.scopes,` + - ` projections.idp_templates5_azure.tenant,` + - ` projections.idp_templates5_azure.is_email_verified,` + + ` projections.idp_templates6_azure.idp_id,` + + ` projections.idp_templates6_azure.client_id,` + + ` projections.idp_templates6_azure.client_secret,` + + ` projections.idp_templates6_azure.scopes,` + + ` projections.idp_templates6_azure.tenant,` + + ` projections.idp_templates6_azure.is_email_verified,` + // github - ` projections.idp_templates5_github.idp_id,` + - ` projections.idp_templates5_github.client_id,` + - ` projections.idp_templates5_github.client_secret,` + - ` projections.idp_templates5_github.scopes,` + + ` projections.idp_templates6_github.idp_id,` + + ` projections.idp_templates6_github.client_id,` + + ` projections.idp_templates6_github.client_secret,` + + ` projections.idp_templates6_github.scopes,` + // github enterprise - ` projections.idp_templates5_github_enterprise.idp_id,` + - ` projections.idp_templates5_github_enterprise.client_id,` + - ` projections.idp_templates5_github_enterprise.client_secret,` + - ` projections.idp_templates5_github_enterprise.authorization_endpoint,` + - ` projections.idp_templates5_github_enterprise.token_endpoint,` + - ` projections.idp_templates5_github_enterprise.user_endpoint,` + - ` projections.idp_templates5_github_enterprise.scopes,` + + ` projections.idp_templates6_github_enterprise.idp_id,` + + ` projections.idp_templates6_github_enterprise.client_id,` + + ` projections.idp_templates6_github_enterprise.client_secret,` + + ` projections.idp_templates6_github_enterprise.authorization_endpoint,` + + ` projections.idp_templates6_github_enterprise.token_endpoint,` + + ` projections.idp_templates6_github_enterprise.user_endpoint,` + + ` projections.idp_templates6_github_enterprise.scopes,` + // gitlab - ` projections.idp_templates5_gitlab.idp_id,` + - ` projections.idp_templates5_gitlab.client_id,` + - ` projections.idp_templates5_gitlab.client_secret,` + - ` projections.idp_templates5_gitlab.scopes,` + + ` projections.idp_templates6_gitlab.idp_id,` + + ` projections.idp_templates6_gitlab.client_id,` + + ` projections.idp_templates6_gitlab.client_secret,` + + ` projections.idp_templates6_gitlab.scopes,` + // gitlab self hosted - ` projections.idp_templates5_gitlab_self_hosted.idp_id,` + - ` projections.idp_templates5_gitlab_self_hosted.issuer,` + - ` projections.idp_templates5_gitlab_self_hosted.client_id,` + - ` projections.idp_templates5_gitlab_self_hosted.client_secret,` + - ` projections.idp_templates5_gitlab_self_hosted.scopes,` + + ` projections.idp_templates6_gitlab_self_hosted.idp_id,` + + ` projections.idp_templates6_gitlab_self_hosted.issuer,` + + ` projections.idp_templates6_gitlab_self_hosted.client_id,` + + ` projections.idp_templates6_gitlab_self_hosted.client_secret,` + + ` projections.idp_templates6_gitlab_self_hosted.scopes,` + // google - ` projections.idp_templates5_google.idp_id,` + - ` projections.idp_templates5_google.client_id,` + - ` projections.idp_templates5_google.client_secret,` + - ` projections.idp_templates5_google.scopes,` + + ` projections.idp_templates6_google.idp_id,` + + ` projections.idp_templates6_google.client_id,` + + ` projections.idp_templates6_google.client_secret,` + + ` projections.idp_templates6_google.scopes,` + // saml - ` projections.idp_templates5_saml.idp_id,` + - ` projections.idp_templates5_saml.metadata,` + - ` projections.idp_templates5_saml.key,` + - ` projections.idp_templates5_saml.certificate,` + - ` projections.idp_templates5_saml.binding,` + - ` projections.idp_templates5_saml.with_signed_request,` + + ` projections.idp_templates6_saml.idp_id,` + + ` projections.idp_templates6_saml.metadata,` + + ` projections.idp_templates6_saml.key,` + + ` projections.idp_templates6_saml.certificate,` + + ` projections.idp_templates6_saml.binding,` + + ` projections.idp_templates6_saml.with_signed_request,` + // ldap - ` projections.idp_templates5_ldap2.idp_id,` + - ` projections.idp_templates5_ldap2.servers,` + - ` projections.idp_templates5_ldap2.start_tls,` + - ` projections.idp_templates5_ldap2.base_dn,` + - ` projections.idp_templates5_ldap2.bind_dn,` + - ` projections.idp_templates5_ldap2.bind_password,` + - ` projections.idp_templates5_ldap2.user_base,` + - ` projections.idp_templates5_ldap2.user_object_classes,` + - ` projections.idp_templates5_ldap2.user_filters,` + - ` projections.idp_templates5_ldap2.timeout,` + - ` projections.idp_templates5_ldap2.id_attribute,` + - ` projections.idp_templates5_ldap2.first_name_attribute,` + - ` projections.idp_templates5_ldap2.last_name_attribute,` + - ` projections.idp_templates5_ldap2.display_name_attribute,` + - ` projections.idp_templates5_ldap2.nick_name_attribute,` + - ` projections.idp_templates5_ldap2.preferred_username_attribute,` + - ` projections.idp_templates5_ldap2.email_attribute,` + - ` projections.idp_templates5_ldap2.email_verified,` + - ` projections.idp_templates5_ldap2.phone_attribute,` + - ` projections.idp_templates5_ldap2.phone_verified_attribute,` + - ` projections.idp_templates5_ldap2.preferred_language_attribute,` + - ` projections.idp_templates5_ldap2.avatar_url_attribute,` + - ` projections.idp_templates5_ldap2.profile_attribute,` + + ` projections.idp_templates6_ldap2.idp_id,` + + ` projections.idp_templates6_ldap2.servers,` + + ` projections.idp_templates6_ldap2.start_tls,` + + ` projections.idp_templates6_ldap2.base_dn,` + + ` projections.idp_templates6_ldap2.bind_dn,` + + ` projections.idp_templates6_ldap2.bind_password,` + + ` projections.idp_templates6_ldap2.user_base,` + + ` projections.idp_templates6_ldap2.user_object_classes,` + + ` projections.idp_templates6_ldap2.user_filters,` + + ` projections.idp_templates6_ldap2.timeout,` + + ` projections.idp_templates6_ldap2.id_attribute,` + + ` projections.idp_templates6_ldap2.first_name_attribute,` + + ` projections.idp_templates6_ldap2.last_name_attribute,` + + ` projections.idp_templates6_ldap2.display_name_attribute,` + + ` projections.idp_templates6_ldap2.nick_name_attribute,` + + ` projections.idp_templates6_ldap2.preferred_username_attribute,` + + ` projections.idp_templates6_ldap2.email_attribute,` + + ` projections.idp_templates6_ldap2.email_verified,` + + ` projections.idp_templates6_ldap2.phone_attribute,` + + ` projections.idp_templates6_ldap2.phone_verified_attribute,` + + ` projections.idp_templates6_ldap2.preferred_language_attribute,` + + ` projections.idp_templates6_ldap2.avatar_url_attribute,` + + ` projections.idp_templates6_ldap2.profile_attribute,` + // apple - ` projections.idp_templates5_apple.idp_id,` + - ` projections.idp_templates5_apple.client_id,` + - ` projections.idp_templates5_apple.team_id,` + - ` projections.idp_templates5_apple.key_id,` + - ` projections.idp_templates5_apple.private_key,` + - ` projections.idp_templates5_apple.scopes` + - ` FROM projections.idp_templates5` + - ` LEFT JOIN projections.idp_templates5_oauth2 ON projections.idp_templates5.id = projections.idp_templates5_oauth2.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_oauth2.instance_id` + - ` LEFT JOIN projections.idp_templates5_oidc ON projections.idp_templates5.id = projections.idp_templates5_oidc.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_oidc.instance_id` + - ` LEFT JOIN projections.idp_templates5_jwt ON projections.idp_templates5.id = projections.idp_templates5_jwt.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_jwt.instance_id` + - ` LEFT JOIN projections.idp_templates5_azure ON projections.idp_templates5.id = projections.idp_templates5_azure.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_azure.instance_id` + - ` LEFT JOIN projections.idp_templates5_github ON projections.idp_templates5.id = projections.idp_templates5_github.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_github.instance_id` + - ` LEFT JOIN projections.idp_templates5_github_enterprise ON projections.idp_templates5.id = projections.idp_templates5_github_enterprise.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_github_enterprise.instance_id` + - ` LEFT JOIN projections.idp_templates5_gitlab ON projections.idp_templates5.id = projections.idp_templates5_gitlab.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_gitlab.instance_id` + - ` LEFT JOIN projections.idp_templates5_gitlab_self_hosted ON projections.idp_templates5.id = projections.idp_templates5_gitlab_self_hosted.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_gitlab_self_hosted.instance_id` + - ` LEFT JOIN projections.idp_templates5_google ON projections.idp_templates5.id = projections.idp_templates5_google.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_google.instance_id` + - ` LEFT JOIN projections.idp_templates5_saml ON projections.idp_templates5.id = projections.idp_templates5_saml.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_saml.instance_id` + - ` LEFT JOIN projections.idp_templates5_ldap2 ON projections.idp_templates5.id = projections.idp_templates5_ldap2.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_ldap2.instance_id` + - ` LEFT JOIN projections.idp_templates5_apple ON projections.idp_templates5.id = projections.idp_templates5_apple.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_apple.instance_id` + + ` projections.idp_templates6_apple.idp_id,` + + ` projections.idp_templates6_apple.client_id,` + + ` projections.idp_templates6_apple.team_id,` + + ` projections.idp_templates6_apple.key_id,` + + ` projections.idp_templates6_apple.private_key,` + + ` projections.idp_templates6_apple.scopes` + + ` FROM projections.idp_templates6` + + ` LEFT JOIN projections.idp_templates6_oauth2 ON projections.idp_templates6.id = projections.idp_templates6_oauth2.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_oauth2.instance_id` + + ` LEFT JOIN projections.idp_templates6_oidc ON projections.idp_templates6.id = projections.idp_templates6_oidc.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_oidc.instance_id` + + ` LEFT JOIN projections.idp_templates6_jwt ON projections.idp_templates6.id = projections.idp_templates6_jwt.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_jwt.instance_id` + + ` LEFT JOIN projections.idp_templates6_azure ON projections.idp_templates6.id = projections.idp_templates6_azure.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_azure.instance_id` + + ` LEFT JOIN projections.idp_templates6_github ON projections.idp_templates6.id = projections.idp_templates6_github.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_github.instance_id` + + ` LEFT JOIN projections.idp_templates6_github_enterprise ON projections.idp_templates6.id = projections.idp_templates6_github_enterprise.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_github_enterprise.instance_id` + + ` LEFT JOIN projections.idp_templates6_gitlab ON projections.idp_templates6.id = projections.idp_templates6_gitlab.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_gitlab.instance_id` + + ` LEFT JOIN projections.idp_templates6_gitlab_self_hosted ON projections.idp_templates6.id = projections.idp_templates6_gitlab_self_hosted.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_gitlab_self_hosted.instance_id` + + ` LEFT JOIN projections.idp_templates6_google ON projections.idp_templates6.id = projections.idp_templates6_google.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_google.instance_id` + + ` LEFT JOIN projections.idp_templates6_saml ON projections.idp_templates6.id = projections.idp_templates6_saml.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_saml.instance_id` + + ` LEFT JOIN projections.idp_templates6_ldap2 ON projections.idp_templates6.id = projections.idp_templates6_ldap2.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_ldap2.instance_id` + + ` LEFT JOIN projections.idp_templates6_apple ON projections.idp_templates6.id = projections.idp_templates6_apple.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_apple.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` idpTemplateCols = []string{ "id", @@ -153,6 +154,7 @@ var ( "is_linking_allowed", "is_auto_creation", "is_auto_update", + "auto_linking", // oauth config "idp_id", "client_id", @@ -250,129 +252,130 @@ var ( "private_key", "scopes", } - idpTemplatesQuery = `SELECT projections.idp_templates5.id,` + - ` projections.idp_templates5.resource_owner,` + - ` projections.idp_templates5.creation_date,` + - ` projections.idp_templates5.change_date,` + - ` projections.idp_templates5.sequence,` + - ` projections.idp_templates5.state,` + - ` projections.idp_templates5.name,` + - ` projections.idp_templates5.type,` + - ` projections.idp_templates5.owner_type,` + - ` projections.idp_templates5.is_creation_allowed,` + - ` projections.idp_templates5.is_linking_allowed,` + - ` projections.idp_templates5.is_auto_creation,` + - ` projections.idp_templates5.is_auto_update,` + + idpTemplatesQuery = `SELECT projections.idp_templates6.id,` + + ` projections.idp_templates6.resource_owner,` + + ` projections.idp_templates6.creation_date,` + + ` projections.idp_templates6.change_date,` + + ` projections.idp_templates6.sequence,` + + ` projections.idp_templates6.state,` + + ` projections.idp_templates6.name,` + + ` projections.idp_templates6.type,` + + ` projections.idp_templates6.owner_type,` + + ` projections.idp_templates6.is_creation_allowed,` + + ` projections.idp_templates6.is_linking_allowed,` + + ` projections.idp_templates6.is_auto_creation,` + + ` projections.idp_templates6.is_auto_update,` + + ` projections.idp_templates6.auto_linking,` + // oauth - ` projections.idp_templates5_oauth2.idp_id,` + - ` projections.idp_templates5_oauth2.client_id,` + - ` projections.idp_templates5_oauth2.client_secret,` + - ` projections.idp_templates5_oauth2.authorization_endpoint,` + - ` projections.idp_templates5_oauth2.token_endpoint,` + - ` projections.idp_templates5_oauth2.user_endpoint,` + - ` projections.idp_templates5_oauth2.scopes,` + - ` projections.idp_templates5_oauth2.id_attribute,` + + ` projections.idp_templates6_oauth2.idp_id,` + + ` projections.idp_templates6_oauth2.client_id,` + + ` projections.idp_templates6_oauth2.client_secret,` + + ` projections.idp_templates6_oauth2.authorization_endpoint,` + + ` projections.idp_templates6_oauth2.token_endpoint,` + + ` projections.idp_templates6_oauth2.user_endpoint,` + + ` projections.idp_templates6_oauth2.scopes,` + + ` projections.idp_templates6_oauth2.id_attribute,` + // oidc - ` projections.idp_templates5_oidc.idp_id,` + - ` projections.idp_templates5_oidc.issuer,` + - ` projections.idp_templates5_oidc.client_id,` + - ` projections.idp_templates5_oidc.client_secret,` + - ` projections.idp_templates5_oidc.scopes,` + - ` projections.idp_templates5_oidc.id_token_mapping,` + + ` projections.idp_templates6_oidc.idp_id,` + + ` projections.idp_templates6_oidc.issuer,` + + ` projections.idp_templates6_oidc.client_id,` + + ` projections.idp_templates6_oidc.client_secret,` + + ` projections.idp_templates6_oidc.scopes,` + + ` projections.idp_templates6_oidc.id_token_mapping,` + // jwt - ` projections.idp_templates5_jwt.idp_id,` + - ` projections.idp_templates5_jwt.issuer,` + - ` projections.idp_templates5_jwt.jwt_endpoint,` + - ` projections.idp_templates5_jwt.keys_endpoint,` + - ` projections.idp_templates5_jwt.header_name,` + + ` projections.idp_templates6_jwt.idp_id,` + + ` projections.idp_templates6_jwt.issuer,` + + ` projections.idp_templates6_jwt.jwt_endpoint,` + + ` projections.idp_templates6_jwt.keys_endpoint,` + + ` projections.idp_templates6_jwt.header_name,` + // azure - ` projections.idp_templates5_azure.idp_id,` + - ` projections.idp_templates5_azure.client_id,` + - ` projections.idp_templates5_azure.client_secret,` + - ` projections.idp_templates5_azure.scopes,` + - ` projections.idp_templates5_azure.tenant,` + - ` projections.idp_templates5_azure.is_email_verified,` + + ` projections.idp_templates6_azure.idp_id,` + + ` projections.idp_templates6_azure.client_id,` + + ` projections.idp_templates6_azure.client_secret,` + + ` projections.idp_templates6_azure.scopes,` + + ` projections.idp_templates6_azure.tenant,` + + ` projections.idp_templates6_azure.is_email_verified,` + // github - ` projections.idp_templates5_github.idp_id,` + - ` projections.idp_templates5_github.client_id,` + - ` projections.idp_templates5_github.client_secret,` + - ` projections.idp_templates5_github.scopes,` + + ` projections.idp_templates6_github.idp_id,` + + ` projections.idp_templates6_github.client_id,` + + ` projections.idp_templates6_github.client_secret,` + + ` projections.idp_templates6_github.scopes,` + // github enterprise - ` projections.idp_templates5_github_enterprise.idp_id,` + - ` projections.idp_templates5_github_enterprise.client_id,` + - ` projections.idp_templates5_github_enterprise.client_secret,` + - ` projections.idp_templates5_github_enterprise.authorization_endpoint,` + - ` projections.idp_templates5_github_enterprise.token_endpoint,` + - ` projections.idp_templates5_github_enterprise.user_endpoint,` + - ` projections.idp_templates5_github_enterprise.scopes,` + + ` projections.idp_templates6_github_enterprise.idp_id,` + + ` projections.idp_templates6_github_enterprise.client_id,` + + ` projections.idp_templates6_github_enterprise.client_secret,` + + ` projections.idp_templates6_github_enterprise.authorization_endpoint,` + + ` projections.idp_templates6_github_enterprise.token_endpoint,` + + ` projections.idp_templates6_github_enterprise.user_endpoint,` + + ` projections.idp_templates6_github_enterprise.scopes,` + // gitlab - ` projections.idp_templates5_gitlab.idp_id,` + - ` projections.idp_templates5_gitlab.client_id,` + - ` projections.idp_templates5_gitlab.client_secret,` + - ` projections.idp_templates5_gitlab.scopes,` + + ` projections.idp_templates6_gitlab.idp_id,` + + ` projections.idp_templates6_gitlab.client_id,` + + ` projections.idp_templates6_gitlab.client_secret,` + + ` projections.idp_templates6_gitlab.scopes,` + // gitlab self hosted - ` projections.idp_templates5_gitlab_self_hosted.idp_id,` + - ` projections.idp_templates5_gitlab_self_hosted.issuer,` + - ` projections.idp_templates5_gitlab_self_hosted.client_id,` + - ` projections.idp_templates5_gitlab_self_hosted.client_secret,` + - ` projections.idp_templates5_gitlab_self_hosted.scopes,` + + ` projections.idp_templates6_gitlab_self_hosted.idp_id,` + + ` projections.idp_templates6_gitlab_self_hosted.issuer,` + + ` projections.idp_templates6_gitlab_self_hosted.client_id,` + + ` projections.idp_templates6_gitlab_self_hosted.client_secret,` + + ` projections.idp_templates6_gitlab_self_hosted.scopes,` + // google - ` projections.idp_templates5_google.idp_id,` + - ` projections.idp_templates5_google.client_id,` + - ` projections.idp_templates5_google.client_secret,` + - ` projections.idp_templates5_google.scopes,` + + ` projections.idp_templates6_google.idp_id,` + + ` projections.idp_templates6_google.client_id,` + + ` projections.idp_templates6_google.client_secret,` + + ` projections.idp_templates6_google.scopes,` + // saml - ` projections.idp_templates5_saml.idp_id,` + - ` projections.idp_templates5_saml.metadata,` + - ` projections.idp_templates5_saml.key,` + - ` projections.idp_templates5_saml.certificate,` + - ` projections.idp_templates5_saml.binding,` + - ` projections.idp_templates5_saml.with_signed_request,` + + ` projections.idp_templates6_saml.idp_id,` + + ` projections.idp_templates6_saml.metadata,` + + ` projections.idp_templates6_saml.key,` + + ` projections.idp_templates6_saml.certificate,` + + ` projections.idp_templates6_saml.binding,` + + ` projections.idp_templates6_saml.with_signed_request,` + // ldap - ` projections.idp_templates5_ldap2.idp_id,` + - ` projections.idp_templates5_ldap2.servers,` + - ` projections.idp_templates5_ldap2.start_tls,` + - ` projections.idp_templates5_ldap2.base_dn,` + - ` projections.idp_templates5_ldap2.bind_dn,` + - ` projections.idp_templates5_ldap2.bind_password,` + - ` projections.idp_templates5_ldap2.user_base,` + - ` projections.idp_templates5_ldap2.user_object_classes,` + - ` projections.idp_templates5_ldap2.user_filters,` + - ` projections.idp_templates5_ldap2.timeout,` + - ` projections.idp_templates5_ldap2.id_attribute,` + - ` projections.idp_templates5_ldap2.first_name_attribute,` + - ` projections.idp_templates5_ldap2.last_name_attribute,` + - ` projections.idp_templates5_ldap2.display_name_attribute,` + - ` projections.idp_templates5_ldap2.nick_name_attribute,` + - ` projections.idp_templates5_ldap2.preferred_username_attribute,` + - ` projections.idp_templates5_ldap2.email_attribute,` + - ` projections.idp_templates5_ldap2.email_verified,` + - ` projections.idp_templates5_ldap2.phone_attribute,` + - ` projections.idp_templates5_ldap2.phone_verified_attribute,` + - ` projections.idp_templates5_ldap2.preferred_language_attribute,` + - ` projections.idp_templates5_ldap2.avatar_url_attribute,` + - ` projections.idp_templates5_ldap2.profile_attribute,` + + ` projections.idp_templates6_ldap2.idp_id,` + + ` projections.idp_templates6_ldap2.servers,` + + ` projections.idp_templates6_ldap2.start_tls,` + + ` projections.idp_templates6_ldap2.base_dn,` + + ` projections.idp_templates6_ldap2.bind_dn,` + + ` projections.idp_templates6_ldap2.bind_password,` + + ` projections.idp_templates6_ldap2.user_base,` + + ` projections.idp_templates6_ldap2.user_object_classes,` + + ` projections.idp_templates6_ldap2.user_filters,` + + ` projections.idp_templates6_ldap2.timeout,` + + ` projections.idp_templates6_ldap2.id_attribute,` + + ` projections.idp_templates6_ldap2.first_name_attribute,` + + ` projections.idp_templates6_ldap2.last_name_attribute,` + + ` projections.idp_templates6_ldap2.display_name_attribute,` + + ` projections.idp_templates6_ldap2.nick_name_attribute,` + + ` projections.idp_templates6_ldap2.preferred_username_attribute,` + + ` projections.idp_templates6_ldap2.email_attribute,` + + ` projections.idp_templates6_ldap2.email_verified,` + + ` projections.idp_templates6_ldap2.phone_attribute,` + + ` projections.idp_templates6_ldap2.phone_verified_attribute,` + + ` projections.idp_templates6_ldap2.preferred_language_attribute,` + + ` projections.idp_templates6_ldap2.avatar_url_attribute,` + + ` projections.idp_templates6_ldap2.profile_attribute,` + // apple - ` projections.idp_templates5_apple.idp_id,` + - ` projections.idp_templates5_apple.client_id,` + - ` projections.idp_templates5_apple.team_id,` + - ` projections.idp_templates5_apple.key_id,` + - ` projections.idp_templates5_apple.private_key,` + - ` projections.idp_templates5_apple.scopes,` + + ` projections.idp_templates6_apple.idp_id,` + + ` projections.idp_templates6_apple.client_id,` + + ` projections.idp_templates6_apple.team_id,` + + ` projections.idp_templates6_apple.key_id,` + + ` projections.idp_templates6_apple.private_key,` + + ` projections.idp_templates6_apple.scopes,` + ` COUNT(*) OVER ()` + - ` FROM projections.idp_templates5` + - ` LEFT JOIN projections.idp_templates5_oauth2 ON projections.idp_templates5.id = projections.idp_templates5_oauth2.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_oauth2.instance_id` + - ` LEFT JOIN projections.idp_templates5_oidc ON projections.idp_templates5.id = projections.idp_templates5_oidc.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_oidc.instance_id` + - ` LEFT JOIN projections.idp_templates5_jwt ON projections.idp_templates5.id = projections.idp_templates5_jwt.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_jwt.instance_id` + - ` LEFT JOIN projections.idp_templates5_azure ON projections.idp_templates5.id = projections.idp_templates5_azure.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_azure.instance_id` + - ` LEFT JOIN projections.idp_templates5_github ON projections.idp_templates5.id = projections.idp_templates5_github.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_github.instance_id` + - ` LEFT JOIN projections.idp_templates5_github_enterprise ON projections.idp_templates5.id = projections.idp_templates5_github_enterprise.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_github_enterprise.instance_id` + - ` LEFT JOIN projections.idp_templates5_gitlab ON projections.idp_templates5.id = projections.idp_templates5_gitlab.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_gitlab.instance_id` + - ` LEFT JOIN projections.idp_templates5_gitlab_self_hosted ON projections.idp_templates5.id = projections.idp_templates5_gitlab_self_hosted.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_gitlab_self_hosted.instance_id` + - ` LEFT JOIN projections.idp_templates5_google ON projections.idp_templates5.id = projections.idp_templates5_google.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_google.instance_id` + - ` LEFT JOIN projections.idp_templates5_saml ON projections.idp_templates5.id = projections.idp_templates5_saml.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_saml.instance_id` + - ` LEFT JOIN projections.idp_templates5_ldap2 ON projections.idp_templates5.id = projections.idp_templates5_ldap2.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_ldap2.instance_id` + - ` LEFT JOIN projections.idp_templates5_apple ON projections.idp_templates5.id = projections.idp_templates5_apple.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_apple.instance_id` + + ` FROM projections.idp_templates6` + + ` LEFT JOIN projections.idp_templates6_oauth2 ON projections.idp_templates6.id = projections.idp_templates6_oauth2.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_oauth2.instance_id` + + ` LEFT JOIN projections.idp_templates6_oidc ON projections.idp_templates6.id = projections.idp_templates6_oidc.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_oidc.instance_id` + + ` LEFT JOIN projections.idp_templates6_jwt ON projections.idp_templates6.id = projections.idp_templates6_jwt.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_jwt.instance_id` + + ` LEFT JOIN projections.idp_templates6_azure ON projections.idp_templates6.id = projections.idp_templates6_azure.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_azure.instance_id` + + ` LEFT JOIN projections.idp_templates6_github ON projections.idp_templates6.id = projections.idp_templates6_github.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_github.instance_id` + + ` LEFT JOIN projections.idp_templates6_github_enterprise ON projections.idp_templates6.id = projections.idp_templates6_github_enterprise.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_github_enterprise.instance_id` + + ` LEFT JOIN projections.idp_templates6_gitlab ON projections.idp_templates6.id = projections.idp_templates6_gitlab.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_gitlab.instance_id` + + ` LEFT JOIN projections.idp_templates6_gitlab_self_hosted ON projections.idp_templates6.id = projections.idp_templates6_gitlab_self_hosted.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_gitlab_self_hosted.instance_id` + + ` LEFT JOIN projections.idp_templates6_google ON projections.idp_templates6.id = projections.idp_templates6_google.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_google.instance_id` + + ` LEFT JOIN projections.idp_templates6_saml ON projections.idp_templates6.id = projections.idp_templates6_saml.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_saml.instance_id` + + ` LEFT JOIN projections.idp_templates6_ldap2 ON projections.idp_templates6.id = projections.idp_templates6_ldap2.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_ldap2.instance_id` + + ` LEFT JOIN projections.idp_templates6_apple ON projections.idp_templates6.id = projections.idp_templates6_apple.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_apple.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` idpTemplatesCols = []string{ "id", @@ -388,6 +391,7 @@ var ( "is_linking_allowed", "is_auto_creation", "is_auto_update", + "auto_linking", // oauth config "idp_id", "client_id", @@ -538,6 +542,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth "idp-id", "client_id", @@ -651,6 +656,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, OAuthIDPTemplate: &OAuthIDPTemplate{ IDPID: "idp-id", ClientID: "client_id", @@ -684,6 +690,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -797,6 +804,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, OIDCIDPTemplate: &OIDCIDPTemplate{ IDPID: "idp-id", Issuer: "issuer", @@ -828,6 +836,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -941,6 +950,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, JWTIDPTemplate: &JWTIDPTemplate{ IDPID: "idp-id", Issuer: "issuer", @@ -971,6 +981,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -1084,6 +1095,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, GitHubIDPTemplate: &GitHubIDPTemplate{ IDPID: "idp-id", ClientID: "client_id", @@ -1113,6 +1125,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -1226,6 +1239,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, GitLabIDPTemplate: &GitLabIDPTemplate{ IDPID: "idp-id", ClientID: "client_id", @@ -1255,6 +1269,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -1368,6 +1383,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, GitLabSelfHostedIDPTemplate: &GitLabSelfHostedIDPTemplate{ IDPID: "idp-id", Issuer: "issuer", @@ -1398,6 +1414,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -1511,6 +1528,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, GoogleIDPTemplate: &GoogleIDPTemplate{ IDPID: "idp-id", ClientID: "client_id", @@ -1540,6 +1558,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -1653,6 +1672,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, SAMLIDPTemplate: &SAMLIDPTemplate{ IDPID: "idp-id", Metadata: []byte("metadata"), @@ -1684,6 +1704,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -1797,6 +1818,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, LDAPIDPTemplate: &LDAPIDPTemplate{ IDPID: "idp-id", Servers: []string{"server"}, @@ -1846,6 +1868,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -1959,6 +1982,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, AppleIDPTemplate: &AppleIDPTemplate{ IDPID: "idp-id", ClientID: "client_id", @@ -1990,6 +2014,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -2103,6 +2128,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, }, }, { @@ -2162,6 +2188,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -2281,6 +2308,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, LDAPIDPTemplate: &LDAPIDPTemplate{ IDPID: "idp-id", Servers: []string{"server"}, @@ -2333,6 +2361,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -2452,6 +2481,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, }, }, }, @@ -2478,6 +2508,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -2589,6 +2620,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -2700,6 +2732,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -2811,6 +2844,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth "idp-id-oauth", "client_id", @@ -2922,6 +2956,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -3033,6 +3068,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, // oauth nil, nil, @@ -3152,6 +3188,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, LDAPIDPTemplate: &LDAPIDPTemplate{ IDPID: "idp-id-ldap", Servers: []string{"server"}, @@ -3193,6 +3230,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, SAMLIDPTemplate: &SAMLIDPTemplate{ IDPID: "idp-id-saml", Metadata: []byte("metadata"), @@ -3216,6 +3254,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, GoogleIDPTemplate: &GoogleIDPTemplate{ IDPID: "idp-id-google", ClientID: "client_id", @@ -3238,6 +3277,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, OAuthIDPTemplate: &OAuthIDPTemplate{ IDPID: "idp-id-oauth", ClientID: "client_id", @@ -3263,6 +3303,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, OIDCIDPTemplate: &OIDCIDPTemplate{ IDPID: "idp-id-oidc", Issuer: "issuer", @@ -3286,6 +3327,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { IsLinkingAllowed: true, IsAutoCreation: true, IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, JWTIDPTemplate: &JWTIDPTemplate{ IDPID: "idp-id-jwt", Issuer: "issuer", diff --git a/internal/query/idp_user_link_test.go b/internal/query/idp_user_link_test.go index af4e3a54d7..bcbe6c2062 100644 --- a/internal/query/idp_user_link_test.go +++ b/internal/query/idp_user_link_test.go @@ -14,14 +14,14 @@ import ( var ( idpUserLinksQuery = regexp.QuoteMeta(`SELECT projections.idp_user_links3.idp_id,` + ` projections.idp_user_links3.user_id,` + - ` projections.idp_templates5.name,` + + ` projections.idp_templates6.name,` + ` projections.idp_user_links3.external_user_id,` + ` projections.idp_user_links3.display_name,` + - ` projections.idp_templates5.type,` + + ` projections.idp_templates6.type,` + ` projections.idp_user_links3.resource_owner,` + ` COUNT(*) OVER ()` + ` FROM projections.idp_user_links3` + - ` LEFT JOIN projections.idp_templates5 ON projections.idp_user_links3.idp_id = projections.idp_templates5.id AND projections.idp_user_links3.instance_id = projections.idp_templates5.instance_id` + + ` LEFT JOIN projections.idp_templates6 ON projections.idp_user_links3.idp_id = projections.idp_templates6.id AND projections.idp_user_links3.instance_id = projections.idp_templates6.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) idpUserLinksCols = []string{ "idp_id", diff --git a/internal/query/projection/idp_template.go b/internal/query/projection/idp_template.go index 00cac448af..1ecd9d654f 100644 --- a/internal/query/projection/idp_template.go +++ b/internal/query/projection/idp_template.go @@ -17,7 +17,7 @@ import ( ) const ( - IDPTemplateTable = "projections.idp_templates5" + IDPTemplateTable = "projections.idp_templates6" IDPTemplateOAuthTable = IDPTemplateTable + "_" + IDPTemplateOAuthSuffix IDPTemplateOIDCTable = IDPTemplateTable + "_" + IDPTemplateOIDCSuffix IDPTemplateJWTTable = IDPTemplateTable + "_" + IDPTemplateJWTSuffix @@ -59,6 +59,7 @@ const ( IDPTemplateIsLinkingAllowedCol = "is_linking_allowed" IDPTemplateIsAutoCreationCol = "is_auto_creation" IDPTemplateIsAutoUpdateCol = "is_auto_update" + IDPTemplateAutoLinkingCol = "auto_linking" OAuthIDCol = "idp_id" OAuthInstanceIDCol = "instance_id" @@ -197,6 +198,7 @@ func (*idpTemplateProjection) Init() *old_handler.Check { handler.NewColumn(IDPTemplateIsLinkingAllowedCol, handler.ColumnTypeBool, handler.Default(false)), handler.NewColumn(IDPTemplateIsAutoCreationCol, handler.ColumnTypeBool, handler.Default(false)), handler.NewColumn(IDPTemplateIsAutoUpdateCol, handler.ColumnTypeBool, handler.Default(false)), + handler.NewColumn(IDPTemplateAutoLinkingCol, handler.ColumnTypeEnum, handler.Default(0)), }, handler.NewPrimaryKey(IDPTemplateInstanceIDCol, IDPTemplateIDCol), handler.WithIndex(handler.NewIndex("resource_owner", []string{IDPTemplateResourceOwnerCol})), @@ -700,6 +702,7 @@ func (p *idpTemplateProjection) reduceOAuthIDPAdded(event eventstore.Event) (*ha handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -792,6 +795,7 @@ func (p *idpTemplateProjection) reduceOIDCIDPAdded(event eventstore.Event) (*han handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -873,6 +877,7 @@ func (p *idpTemplateProjection) reduceOIDCIDPMigratedAzureAD(event eventstore.Ev handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, []handler.Condition{ handler.NewCond(IDPTemplateIDCol, idpEvent.ID), @@ -924,6 +929,7 @@ func (p *idpTemplateProjection) reduceOIDCIDPMigratedGoogle(event eventstore.Eve handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, []handler.Condition{ handler.NewCond(IDPTemplateIDCol, idpEvent.ID), @@ -982,6 +988,7 @@ func (p *idpTemplateProjection) reduceJWTIDPAdded(event eventstore.Event) (*hand handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -1070,6 +1077,7 @@ func (p *idpTemplateProjection) reduceOldConfigAdded(event eventstore.Event) (*h handler.NewCol(IDPTemplateIsLinkingAllowedCol, true), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.AutoRegister), handler.NewCol(IDPTemplateIsAutoUpdateCol, false), + handler.NewCol(IDPTemplateAutoLinkingCol, domain.AutoLinkingOptionUnspecified), }, ), nil } @@ -1328,6 +1336,7 @@ func (p *idpTemplateProjection) reduceAzureADIDPAdded(event eventstore.Event) (* handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -1418,6 +1427,7 @@ func (p *idpTemplateProjection) reduceGitHubIDPAdded(event eventstore.Event) (*h handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -1465,6 +1475,7 @@ func (p *idpTemplateProjection) reduceGitHubEnterpriseIDPAdded(event eventstore. handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -1597,6 +1608,7 @@ func (p *idpTemplateProjection) reduceGitLabIDPAdded(event eventstore.Event) (*h handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -1685,6 +1697,7 @@ func (p *idpTemplateProjection) reduceGitLabSelfHostedIDPAdded(event eventstore. handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -1774,6 +1787,7 @@ func (p *idpTemplateProjection) reduceGoogleIDPAdded(event eventstore.Event) (*h handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -1862,6 +1876,7 @@ func (p *idpTemplateProjection) reduceLDAPIDPAdded(event eventstore.Event) (*han handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -1970,6 +1985,7 @@ func (p *idpTemplateProjection) reduceSAMLIDPAdded(event eventstore.Event) (*han handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -2061,6 +2077,7 @@ func (p *idpTemplateProjection) reduceAppleIDPAdded(event eventstore.Event) (*ha handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + handler.NewCol(IDPTemplateAutoLinkingCol, idpEvent.AutoLinkingOption), }, ), handler.AddCreateStatement( @@ -2191,6 +2208,9 @@ func reduceIDPChangedTemplateColumns(name *string, creationDate time.Time, seque if optionChanges.IsAutoUpdate != nil { cols = append(cols, handler.NewCol(IDPTemplateIsAutoUpdateCol, *optionChanges.IsAutoUpdate)) } + if optionChanges.AutoLinkingOption != nil { + cols = append(cols, handler.NewCol(IDPTemplateAutoLinkingCol, *optionChanges.AutoLinkingOption)) + } return append(cols, handler.NewCol(IDPTemplateChangeDateCol, creationDate), handler.NewCol(IDPTemplateSequenceCol, sequence), diff --git a/internal/query/projection/idp_template_test.go b/internal/query/projection/idp_template_test.go index 8490810d83..d463054af7 100644 --- a/internal/query/projection/idp_template_test.go +++ b/internal/query/projection/idp_template_test.go @@ -15,12 +15,12 @@ import ( ) var ( - idpTemplateInsertStmt = `INSERT INTO projections.idp_templates5` + - ` (id, creation_date, change_date, sequence, resource_owner, instance_id, state, name, owner_type, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update)` + - ` VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)` - idpTemplateUpdateMinimalStmt = `UPDATE projections.idp_templates5 SET (is_creation_allowed, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)` - idpTemplateUpdateStmt = `UPDATE projections.idp_templates5 SET (name, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update, change_date, sequence)` + - ` = ($1, $2, $3, $4, $5, $6, $7) WHERE (id = $8) AND (instance_id = $9)` + idpTemplateInsertStmt = `INSERT INTO projections.idp_templates6` + + ` (id, creation_date, change_date, sequence, resource_owner, instance_id, state, name, owner_type, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update, auto_linking)` + + ` VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)` + idpTemplateUpdateMinimalStmt = `UPDATE projections.idp_templates6 SET (is_creation_allowed, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)` + idpTemplateUpdateStmt = `UPDATE projections.idp_templates6 SET (name, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update, auto_linking, change_date, sequence)` + + ` = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (id = $9) AND (instance_id = $10)` ) func TestIDPTemplateProjection_reducesRemove(t *testing.T) { @@ -51,7 +51,7 @@ func TestIDPTemplateProjection_reducesRemove(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.idp_templates5 WHERE (instance_id = $1)", + expectedStmt: "DELETE FROM projections.idp_templates6 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, @@ -77,7 +77,7 @@ func TestIDPTemplateProjection_reducesRemove(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.idp_templates5 WHERE (instance_id = $1) AND (resource_owner = $2)", + expectedStmt: "DELETE FROM projections.idp_templates6 WHERE (instance_id = $1) AND (resource_owner = $2)", expectedArgs: []interface{}{ "instance-id", "agg-id", @@ -106,7 +106,7 @@ func TestIDPTemplateProjection_reducesRemove(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.idp_templates5 WHERE (id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.idp_templates6 WHERE (id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -135,7 +135,7 @@ func TestIDPTemplateProjection_reducesRemove(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.idp_templates5 WHERE (id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.idp_templates6 WHERE (id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -195,7 +195,8 @@ func TestIDPTemplateProjection_reducesOAuth(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.OAuthIDPAddedEventMapper), }, @@ -222,10 +223,11 @@ func TestIDPTemplateProjection_reducesOAuth(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_oauth2 (idp_id, instance_id, client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes, id_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.idp_templates6_oauth2 (idp_id, instance_id, client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes, id_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -266,7 +268,8 @@ func TestIDPTemplateProjection_reducesOAuth(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.OAuthIDPAddedEventMapper), }, @@ -293,10 +296,11 @@ func TestIDPTemplateProjection_reducesOAuth(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_oauth2 (idp_id, instance_id, client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes, id_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.idp_templates6_oauth2 (idp_id, instance_id, client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes, id_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -344,7 +348,7 @@ func TestIDPTemplateProjection_reducesOAuth(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_oauth2 SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_oauth2 SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "id", "idp-id", @@ -379,7 +383,8 @@ func TestIDPTemplateProjection_reducesOAuth(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.OAuthIDPChangedEventMapper), }, @@ -397,6 +402,7 @@ func TestIDPTemplateProjection_reducesOAuth(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -404,7 +410,7 @@ func TestIDPTemplateProjection_reducesOAuth(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_oauth2 SET (client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes, id_attribute) = ($1, $2, $3, $4, $5, $6, $7) WHERE (idp_id = $8) AND (instance_id = $9)", + expectedStmt: "UPDATE projections.idp_templates6_oauth2 SET (client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes, id_attribute) = ($1, $2, $3, $4, $5, $6, $7) WHERE (idp_id = $8) AND (instance_id = $9)", expectedArgs: []interface{}{ "client_id", anyArg{}, @@ -489,10 +495,11 @@ func TestIDPTemplateProjection_reducesAzureAD(t *testing.T) { false, false, false, + domain.AutoLinkingOptionUnspecified, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -529,7 +536,8 @@ func TestIDPTemplateProjection_reducesAzureAD(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.AzureADIDPAddedEventMapper), }, @@ -556,10 +564,11 @@ func TestIDPTemplateProjection_reducesAzureAD(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -596,7 +605,8 @@ func TestIDPTemplateProjection_reducesAzureAD(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.AzureADIDPAddedEventMapper), }, @@ -623,10 +633,11 @@ func TestIDPTemplateProjection_reducesAzureAD(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -672,7 +683,7 @@ func TestIDPTemplateProjection_reducesAzureAD(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_azure SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_azure SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "id", "idp-id", @@ -705,7 +716,8 @@ func TestIDPTemplateProjection_reducesAzureAD(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.AzureADIDPChangedEventMapper), }, @@ -723,6 +735,7 @@ func TestIDPTemplateProjection_reducesAzureAD(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -730,7 +743,7 @@ func TestIDPTemplateProjection_reducesAzureAD(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_azure SET (client_id, client_secret, scopes, tenant, is_email_verified) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)", + expectedStmt: "UPDATE projections.idp_templates6_azure SET (client_id, client_secret, scopes, tenant, is_email_verified) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)", expectedArgs: []interface{}{ "client_id", anyArg{}, @@ -791,7 +804,8 @@ func TestIDPTemplateProjection_reducesGitHub(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.GitHubIDPAddedEventMapper), }, @@ -818,10 +832,11 @@ func TestIDPTemplateProjection_reducesGitHub(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_github (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.idp_templates6_github (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -854,7 +869,8 @@ func TestIDPTemplateProjection_reducesGitHub(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.GitHubIDPAddedEventMapper), }, @@ -881,10 +897,11 @@ func TestIDPTemplateProjection_reducesGitHub(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_github (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.idp_templates6_github (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -928,7 +945,7 @@ func TestIDPTemplateProjection_reducesGitHub(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_github SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_github SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "id", "idp-id", @@ -959,7 +976,8 @@ func TestIDPTemplateProjection_reducesGitHub(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.GitHubIDPChangedEventMapper), }, @@ -977,6 +995,7 @@ func TestIDPTemplateProjection_reducesGitHub(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -984,7 +1003,7 @@ func TestIDPTemplateProjection_reducesGitHub(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_github SET (client_id, client_secret, scopes) = ($1, $2, $3) WHERE (idp_id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.idp_templates6_github SET (client_id, client_secret, scopes) = ($1, $2, $3) WHERE (idp_id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ "client_id", anyArg{}, @@ -1046,7 +1065,8 @@ func TestIDPTemplateProjection_reducesGitHubEnterprise(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.GitHubEnterpriseIDPAddedEventMapper), }, @@ -1073,10 +1093,11 @@ func TestIDPTemplateProjection_reducesGitHubEnterprise(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_github_enterprise (idp_id, instance_id, client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", + expectedStmt: "INSERT INTO projections.idp_templates6_github_enterprise (idp_id, instance_id, client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -1115,7 +1136,8 @@ func TestIDPTemplateProjection_reducesGitHubEnterprise(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.GitHubEnterpriseIDPAddedEventMapper), }, @@ -1142,10 +1164,11 @@ func TestIDPTemplateProjection_reducesGitHubEnterprise(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_github_enterprise (idp_id, instance_id, client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", + expectedStmt: "INSERT INTO projections.idp_templates6_github_enterprise (idp_id, instance_id, client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -1192,7 +1215,7 @@ func TestIDPTemplateProjection_reducesGitHubEnterprise(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_github_enterprise SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_github_enterprise SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "id", "idp-id", @@ -1226,7 +1249,8 @@ func TestIDPTemplateProjection_reducesGitHubEnterprise(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.GitHubEnterpriseIDPChangedEventMapper), }, @@ -1244,6 +1268,7 @@ func TestIDPTemplateProjection_reducesGitHubEnterprise(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -1251,7 +1276,7 @@ func TestIDPTemplateProjection_reducesGitHubEnterprise(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_github_enterprise SET (client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes) = ($1, $2, $3, $4, $5, $6) WHERE (idp_id = $7) AND (instance_id = $8)", + expectedStmt: "UPDATE projections.idp_templates6_github_enterprise SET (client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes) = ($1, $2, $3, $4, $5, $6) WHERE (idp_id = $7) AND (instance_id = $8)", expectedArgs: []interface{}{ "client_id", anyArg{}, @@ -1312,7 +1337,8 @@ func TestIDPTemplateProjection_reducesGitLab(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.GitLabIDPAddedEventMapper), }, @@ -1339,10 +1365,11 @@ func TestIDPTemplateProjection_reducesGitLab(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_gitlab (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.idp_templates6_gitlab (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -1374,7 +1401,8 @@ func TestIDPTemplateProjection_reducesGitLab(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.GitLabIDPAddedEventMapper), }, @@ -1401,10 +1429,11 @@ func TestIDPTemplateProjection_reducesGitLab(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_gitlab (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.idp_templates6_gitlab (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -1448,7 +1477,7 @@ func TestIDPTemplateProjection_reducesGitLab(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_gitlab SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_gitlab SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "id", "idp-id", @@ -1479,7 +1508,8 @@ func TestIDPTemplateProjection_reducesGitLab(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.GitLabIDPChangedEventMapper), }, @@ -1497,6 +1527,7 @@ func TestIDPTemplateProjection_reducesGitLab(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -1504,7 +1535,7 @@ func TestIDPTemplateProjection_reducesGitLab(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_gitlab SET (client_id, client_secret, scopes) = ($1, $2, $3) WHERE (idp_id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.idp_templates6_gitlab SET (client_id, client_secret, scopes) = ($1, $2, $3) WHERE (idp_id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ "client_id", anyArg{}, @@ -1564,7 +1595,8 @@ func TestIDPTemplateProjection_reducesGitLabSelfHosted(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.GitLabSelfHostedIDPAddedEventMapper), }, @@ -1591,10 +1623,11 @@ func TestIDPTemplateProjection_reducesGitLabSelfHosted(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_gitlab_self_hosted (idp_id, instance_id, issuer, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5, $6)", + expectedStmt: "INSERT INTO projections.idp_templates6_gitlab_self_hosted (idp_id, instance_id, issuer, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5, $6)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -1629,7 +1662,8 @@ func TestIDPTemplateProjection_reducesGitLabSelfHosted(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.GitLabSelfHostedIDPAddedEventMapper), }, @@ -1656,10 +1690,11 @@ func TestIDPTemplateProjection_reducesGitLabSelfHosted(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_gitlab_self_hosted (idp_id, instance_id, issuer, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5, $6)", + expectedStmt: "INSERT INTO projections.idp_templates6_gitlab_self_hosted (idp_id, instance_id, issuer, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5, $6)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -1704,7 +1739,7 @@ func TestIDPTemplateProjection_reducesGitLabSelfHosted(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_gitlab_self_hosted SET issuer = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_gitlab_self_hosted SET issuer = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "issuer", "idp-id", @@ -1736,7 +1771,8 @@ func TestIDPTemplateProjection_reducesGitLabSelfHosted(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.GitLabSelfHostedIDPChangedEventMapper), }, @@ -1754,6 +1790,7 @@ func TestIDPTemplateProjection_reducesGitLabSelfHosted(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -1761,7 +1798,7 @@ func TestIDPTemplateProjection_reducesGitLabSelfHosted(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_gitlab_self_hosted SET (issuer, client_id, client_secret, scopes) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.idp_templates6_gitlab_self_hosted SET (issuer, client_id, client_secret, scopes) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ "issuer", "client_id", @@ -1820,7 +1857,8 @@ func TestIDPTemplateProjection_reducesGoogle(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.GoogleIDPAddedEventMapper), }, @@ -1847,10 +1885,11 @@ func TestIDPTemplateProjection_reducesGoogle(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_google (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.idp_templates6_google (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -1882,7 +1921,8 @@ func TestIDPTemplateProjection_reducesGoogle(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.GoogleIDPAddedEventMapper), }, @@ -1909,10 +1949,11 @@ func TestIDPTemplateProjection_reducesGoogle(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_google (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.idp_templates6_google (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -1956,7 +1997,7 @@ func TestIDPTemplateProjection_reducesGoogle(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_google SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_google SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "id", "idp-id", @@ -1987,7 +2028,8 @@ func TestIDPTemplateProjection_reducesGoogle(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.GoogleIDPChangedEventMapper), }, @@ -2005,6 +2047,7 @@ func TestIDPTemplateProjection_reducesGoogle(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -2012,7 +2055,7 @@ func TestIDPTemplateProjection_reducesGoogle(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_google SET (client_id, client_secret, scopes) = ($1, $2, $3) WHERE (idp_id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.idp_templates6_google SET (client_id, client_secret, scopes) = ($1, $2, $3) WHERE (idp_id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ "client_id", anyArg{}, @@ -2090,7 +2133,8 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.LDAPIDPAddedEventMapper), }, @@ -2117,10 +2161,11 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_ldap2 (idp_id, instance_id, servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)", + expectedStmt: "INSERT INTO projections.idp_templates6_ldap2 (idp_id, instance_id, servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -2191,7 +2236,8 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.LDAPIDPAddedEventMapper), }, @@ -2218,10 +2264,11 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_ldap2 (idp_id, instance_id, servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)", + expectedStmt: "INSERT INTO projections.idp_templates6_ldap2 (idp_id, instance_id, servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -2274,7 +2321,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (name, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.idp_templates6 SET (name, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ "custom-zitadel-instance", anyArg{}, @@ -2284,7 +2331,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_ldap2 SET base_dn = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_ldap2 SET base_dn = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "basedn", "idp-id", @@ -2334,7 +2381,8 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.LDAPIDPChangedEventMapper), }, @@ -2352,6 +2400,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -2359,7 +2408,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_ldap2 SET (servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22) WHERE (idp_id = $23) AND (instance_id = $24)", + expectedStmt: "UPDATE projections.idp_templates6_ldap2 SET (servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22) WHERE (idp_id = $23) AND (instance_id = $24)", expectedArgs: []interface{}{ database.TextArray[string]{"server"}, false, @@ -2408,7 +2457,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.idp_templates5 WHERE (instance_id = $1) AND (resource_owner = $2)", + expectedStmt: "DELETE FROM projections.idp_templates6 WHERE (instance_id = $1) AND (resource_owner = $2)", expectedArgs: []interface{}{ "instance-id", "agg-id", @@ -2464,7 +2513,8 @@ func TestIDPTemplateProjection_reducesApple(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.AppleIDPAddedEventMapper), }, @@ -2491,10 +2541,11 @@ func TestIDPTemplateProjection_reducesApple(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_apple (idp_id, instance_id, client_id, team_id, key_id, private_key, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_apple (idp_id, instance_id, client_id, team_id, key_id, private_key, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -2529,7 +2580,8 @@ func TestIDPTemplateProjection_reducesApple(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.AppleIDPAddedEventMapper), }, @@ -2556,10 +2608,11 @@ func TestIDPTemplateProjection_reducesApple(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_apple (idp_id, instance_id, client_id, team_id, key_id, private_key, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_apple (idp_id, instance_id, client_id, team_id, key_id, private_key, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -2604,7 +2657,7 @@ func TestIDPTemplateProjection_reducesApple(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_apple SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_apple SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "id", "idp-id", @@ -2636,7 +2689,8 @@ func TestIDPTemplateProjection_reducesApple(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.AppleIDPChangedEventMapper), }, @@ -2654,6 +2708,7 @@ func TestIDPTemplateProjection_reducesApple(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -2661,7 +2716,7 @@ func TestIDPTemplateProjection_reducesApple(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_apple SET (client_id, team_id, key_id, private_key, scopes) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)", + expectedStmt: "UPDATE projections.idp_templates6_apple SET (client_id, team_id, key_id, private_key, scopes) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)", expectedArgs: []interface{}{ "client_id", "team_id", @@ -2723,7 +2778,8 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.SAMLIDPAddedEventMapper), }, @@ -2750,10 +2806,11 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_saml (idp_id, instance_id, metadata, key, certificate, binding, with_signed_request) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_saml (idp_id, instance_id, metadata, key, certificate, binding, with_signed_request) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -2789,7 +2846,8 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.SAMLIDPAddedEventMapper), }, @@ -2816,10 +2874,11 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_saml (idp_id, instance_id, metadata, key, certificate, binding, with_signed_request) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_saml (idp_id, instance_id, metadata, key, certificate, binding, with_signed_request) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -2854,7 +2913,7 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (name, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.idp_templates6 SET (name, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ "custom-zitadel-instance", anyArg{}, @@ -2864,7 +2923,7 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_saml SET binding = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_saml SET binding = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "binding", "idp-id", @@ -2896,7 +2955,8 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.SAMLIDPChangedEventMapper), }, @@ -2914,6 +2974,7 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -2921,7 +2982,7 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_saml SET (metadata, key, certificate, binding, with_signed_request) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)", + expectedStmt: "UPDATE projections.idp_templates6_saml SET (metadata, key, certificate, binding, with_signed_request) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)", expectedArgs: []interface{}{ []byte("metadata"), anyArg{}, @@ -2952,7 +3013,7 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.idp_templates5 WHERE (instance_id = $1) AND (resource_owner = $2)", + expectedStmt: "DELETE FROM projections.idp_templates6 WHERE (instance_id = $1) AND (resource_owner = $2)", expectedArgs: []interface{}{ "instance-id", "agg-id", @@ -3009,7 +3070,8 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.OIDCIDPAddedEventMapper), }, @@ -3036,10 +3098,11 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_oidc (idp_id, instance_id, issuer, client_id, client_secret, scopes, id_token_mapping) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_oidc (idp_id, instance_id, issuer, client_id, client_secret, scopes, id_token_mapping) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -3075,7 +3138,8 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.OIDCIDPAddedEventMapper), }, @@ -3102,10 +3166,11 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_oidc (idp_id, instance_id, issuer, client_id, client_secret, scopes, id_token_mapping) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_oidc (idp_id, instance_id, issuer, client_id, client_secret, scopes, id_token_mapping) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -3151,7 +3216,7 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_oidc SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_oidc SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "id", "idp-id", @@ -3184,7 +3249,8 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.OIDCIDPChangedEventMapper), }, @@ -3202,6 +3268,7 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -3209,7 +3276,7 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_oidc SET (client_id, client_secret, issuer, scopes, id_token_mapping) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)", + expectedStmt: "UPDATE projections.idp_templates6_oidc SET (client_id, client_secret, issuer, scopes, id_token_mapping) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)", expectedArgs: []interface{}{ "client_id", anyArg{}, @@ -3245,7 +3312,8 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.OIDCIDPMigratedAzureADEventMapper), }, @@ -3256,7 +3324,7 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence, name, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (id = $9) AND (instance_id = $10)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence, name, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update, auto_linking) = ($1, $2, $3, $4, $5, $6, $7, $8, $9) WHERE (id = $10) AND (instance_id = $11)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -3266,19 +3334,20 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, "idp-id", "instance-id", }, }, { - expectedStmt: "DELETE FROM projections.idp_templates5_oidc WHERE (idp_id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.idp_templates6_oidc WHERE (idp_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "idp-id", "instance-id", }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -3314,7 +3383,8 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.OIDCIDPMigratedAzureADEventMapper), }, @@ -3325,7 +3395,7 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence, name, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (id = $9) AND (instance_id = $10)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence, name, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update, auto_linking) = ($1, $2, $3, $4, $5, $6, $7, $8, $9) WHERE (id = $10) AND (instance_id = $11)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -3335,19 +3405,20 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, "idp-id", "instance-id", }, }, { - expectedStmt: "DELETE FROM projections.idp_templates5_oidc WHERE (idp_id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.idp_templates6_oidc WHERE (idp_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "idp-id", "instance-id", }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -3381,7 +3452,8 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.OIDCIDPMigratedGoogleEventMapper), }, @@ -3392,7 +3464,7 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence, name, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (id = $9) AND (instance_id = $10)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence, name, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update, auto_linking) = ($1, $2, $3, $4, $5, $6, $7, $8, $9) WHERE (id = $10) AND (instance_id = $11)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -3402,19 +3474,20 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, "idp-id", "instance-id", }, }, { - expectedStmt: "DELETE FROM projections.idp_templates5_oidc WHERE (idp_id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.idp_templates6_oidc WHERE (idp_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "idp-id", "instance-id", }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_google (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.idp_templates6_google (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -3446,7 +3519,8 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.OIDCIDPMigratedGoogleEventMapper), }, @@ -3457,7 +3531,7 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence, name, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (id = $9) AND (instance_id = $10)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence, name, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update, auto_linking) = ($1, $2, $3, $4, $5, $6, $7, $8, $9) WHERE (id = $10) AND (instance_id = $11)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -3467,19 +3541,20 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, "idp-id", "instance-id", }, }, { - expectedStmt: "DELETE FROM projections.idp_templates5_oidc WHERE (idp_id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.idp_templates6_oidc WHERE (idp_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "idp-id", "instance-id", }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_google (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.idp_templates6_google (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -3557,6 +3632,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { true, true, false, + domain.AutoLinkingOptionUnspecified, }, }, }, @@ -3602,6 +3678,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { true, true, false, + domain.AutoLinkingOptionUnspecified, }, }, }, @@ -3630,7 +3707,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (name, is_auto_creation, change_date, sequence) = ($1, $2, $3, $4) WHERE (id = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.idp_templates6 SET (name, is_auto_creation, change_date, sequence) = ($1, $2, $3, $4) WHERE (id = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ "custom-zitadel-instance", true, @@ -3666,7 +3743,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (name, is_auto_creation, change_date, sequence) = ($1, $2, $3, $4) WHERE (id = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.idp_templates6 SET (name, is_auto_creation, change_date, sequence) = ($1, $2, $3, $4) WHERE (id = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ "custom-zitadel-instance", true, @@ -3709,7 +3786,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence, type) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence, type) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -3719,7 +3796,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_oidc (idp_id, instance_id, issuer, client_id, client_secret, scopes, id_token_mapping) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_oidc (idp_id, instance_id, issuer, client_id, client_secret, scopes, id_token_mapping) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-config-id", "instance-id", @@ -3763,7 +3840,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence, type) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence, type) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -3773,7 +3850,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_oidc (idp_id, instance_id, issuer, client_id, client_secret, scopes, id_token_mapping) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO projections.idp_templates6_oidc (idp_id, instance_id, issuer, client_id, client_secret, scopes, id_token_mapping) VALUES ($1, $2, $3, $4, $5, $6, $7)", expectedArgs: []interface{}{ "idp-config-id", "instance-id", @@ -3817,7 +3894,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -3826,7 +3903,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_oidc SET (client_id, client_secret, issuer, scopes) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.idp_templates6_oidc SET (client_id, client_secret, issuer, scopes) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ "client-id", anyArg{}, @@ -3869,7 +3946,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -3878,7 +3955,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_oidc SET (client_id, client_secret, issuer, scopes) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.idp_templates6_oidc SET (client_id, client_secret, issuer, scopes) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ "client-id", anyArg{}, @@ -3915,7 +3992,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence, type) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence, type) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -3925,7 +4002,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_jwt (idp_id, instance_id, issuer, jwt_endpoint, keys_endpoint, header_name) VALUES ($1, $2, $3, $4, $5, $6)", + expectedStmt: "INSERT INTO projections.idp_templates6_jwt (idp_id, instance_id, issuer, jwt_endpoint, keys_endpoint, header_name) VALUES ($1, $2, $3, $4, $5, $6)", expectedArgs: []interface{}{ "idp-config-id", "instance-id", @@ -3963,7 +4040,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence, type) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence, type) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -3973,7 +4050,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_jwt (idp_id, instance_id, issuer, jwt_endpoint, keys_endpoint, header_name) VALUES ($1, $2, $3, $4, $5, $6)", + expectedStmt: "INSERT INTO projections.idp_templates6_jwt (idp_id, instance_id, issuer, jwt_endpoint, keys_endpoint, header_name) VALUES ($1, $2, $3, $4, $5, $6)", expectedArgs: []interface{}{ "idp-config-id", "instance-id", @@ -4010,7 +4087,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -4019,7 +4096,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_jwt SET (jwt_endpoint, keys_endpoint, header_name, issuer) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.idp_templates6_jwt SET (jwt_endpoint, keys_endpoint, header_name, issuer) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ "https://api.zitadel.ch/jwt", "https://api.zitadel.ch/keys", @@ -4056,7 +4133,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.idp_templates6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -4065,7 +4142,7 @@ func TestIDPTemplateProjection_reducesOldConfig(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_jwt SET (jwt_endpoint, keys_endpoint, header_name, issuer) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.idp_templates6_jwt SET (jwt_endpoint, keys_endpoint, header_name, issuer) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ "https://api.zitadel.ch/jwt", "https://api.zitadel.ch/keys", @@ -4121,7 +4198,8 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.JWTIDPAddedEventMapper), }, @@ -4148,10 +4226,11 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_jwt (idp_id, instance_id, issuer, jwt_endpoint, keys_endpoint, header_name) VALUES ($1, $2, $3, $4, $5, $6)", + expectedStmt: "INSERT INTO projections.idp_templates6_jwt (idp_id, instance_id, issuer, jwt_endpoint, keys_endpoint, header_name) VALUES ($1, $2, $3, $4, $5, $6)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -4181,7 +4260,8 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), org.JWTIDPAddedEventMapper), }, @@ -4208,10 +4288,11 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) { true, true, true, + domain.AutoLinkingOptionUsername, }, }, { - expectedStmt: "INSERT INTO projections.idp_templates5_jwt (idp_id, instance_id, issuer, jwt_endpoint, keys_endpoint, header_name) VALUES ($1, $2, $3, $4, $5, $6)", + expectedStmt: "INSERT INTO projections.idp_templates6_jwt (idp_id, instance_id, issuer, jwt_endpoint, keys_endpoint, header_name) VALUES ($1, $2, $3, $4, $5, $6)", expectedArgs: []interface{}{ "idp-id", "instance-id", @@ -4256,7 +4337,7 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_jwt SET jwt_endpoint = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.idp_templates6_jwt SET jwt_endpoint = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "jwt", "idp-id", @@ -4283,7 +4364,8 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) { "isCreationAllowed": true, "isLinkingAllowed": true, "isAutoCreation": true, - "isAutoUpdate": true + "isAutoUpdate": true, + "autoLinkingOption": 1 }`), ), instance.JWTIDPChangedEventMapper), }, @@ -4294,12 +4376,13 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.idp_templates5 SET (is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update, change_date, sequence) = ($1, $2, $3, $4, $5, $6) WHERE (id = $7) AND (instance_id = $8)", + expectedStmt: "UPDATE projections.idp_templates6 SET (is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update, auto_linking, change_date, sequence) = ($1, $2, $3, $4, $5, $6, $7) WHERE (id = $8) AND (instance_id = $9)", expectedArgs: []interface{}{ true, true, true, true, + domain.AutoLinkingOptionUsername, anyArg{}, uint64(15), "idp-id", @@ -4307,7 +4390,7 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.idp_templates5_jwt SET (jwt_endpoint, keys_endpoint, header_name, issuer) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.idp_templates6_jwt SET (jwt_endpoint, keys_endpoint, header_name, issuer) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ "jwt", "keys", diff --git a/internal/repository/idp/idp.go b/internal/repository/idp/idp.go index 341caebb85..41ea7c3d23 100644 --- a/internal/repository/idp/idp.go +++ b/internal/repository/idp/idp.go @@ -1,22 +1,25 @@ package idp import ( + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/zerrors" ) type Options struct { - IsCreationAllowed bool `json:"isCreationAllowed,omitempty"` - IsLinkingAllowed bool `json:"isLinkingAllowed,omitempty"` - IsAutoCreation bool `json:"isAutoCreation,omitempty"` - IsAutoUpdate bool `json:"isAutoUpdate,omitempty"` + IsCreationAllowed bool `json:"isCreationAllowed,omitempty"` + IsLinkingAllowed bool `json:"isLinkingAllowed,omitempty"` + IsAutoCreation bool `json:"isAutoCreation,omitempty"` + IsAutoUpdate bool `json:"isAutoUpdate,omitempty"` + AutoLinkingOption domain.AutoLinkingOption `json:"autoLinkingOption,omitempty"` } type OptionChanges struct { - IsCreationAllowed *bool `json:"isCreationAllowed,omitempty"` - IsLinkingAllowed *bool `json:"isLinkingAllowed,omitempty"` - IsAutoCreation *bool `json:"isAutoCreation,omitempty"` - IsAutoUpdate *bool `json:"isAutoUpdate,omitempty"` + IsCreationAllowed *bool `json:"isCreationAllowed,omitempty"` + IsLinkingAllowed *bool `json:"isLinkingAllowed,omitempty"` + IsAutoCreation *bool `json:"isAutoCreation,omitempty"` + IsAutoUpdate *bool `json:"isAutoUpdate,omitempty"` + AutoLinkingOption *domain.AutoLinkingOption `json:"autoLinkingOption,omitempty"` } func (o *Options) Changes(options Options) OptionChanges { @@ -33,6 +36,9 @@ func (o *Options) Changes(options Options) OptionChanges { if o.IsAutoUpdate != options.IsAutoUpdate { opts.IsAutoUpdate = &options.IsAutoUpdate } + if o.AutoLinkingOption != options.AutoLinkingOption { + opts.AutoLinkingOption = &options.AutoLinkingOption + } return opts } @@ -49,10 +55,13 @@ func (o *Options) ReduceChanges(changes OptionChanges) { if changes.IsAutoUpdate != nil { o.IsAutoUpdate = *changes.IsAutoUpdate } + if changes.AutoLinkingOption != nil { + o.AutoLinkingOption = *changes.AutoLinkingOption + } } func (o *OptionChanges) IsZero() bool { - return o.IsCreationAllowed == nil && o.IsLinkingAllowed == nil && o.IsAutoCreation == nil && o.IsAutoUpdate == nil + return o.IsCreationAllowed == nil && o.IsLinkingAllowed == nil && o.IsAutoCreation == nil && o.IsAutoUpdate == nil && o.AutoLinkingOption == nil } type RemovedEvent struct { diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto index 5211995f23..b8a0d0290e 100644 --- a/proto/zitadel/admin.proto +++ b/proto/zitadel/admin.proto @@ -7494,6 +7494,7 @@ message SetCustomLoginTextsRequest { zitadel.text.v1.PasswordlessRegistrationScreenText passwordless_registration_text = 33; zitadel.text.v1.PasswordlessRegistrationDoneScreenText passwordless_registration_done_text = 34; zitadel.text.v1.ExternalRegistrationUserOverviewScreenText external_registration_user_overview_text = 35; + zitadel.text.v1.LinkingUserPromptScreenText linking_user_prompt_text = 36; } message SetCustomLoginTextsResponse { diff --git a/proto/zitadel/idp.proto b/proto/zitadel/idp.proto index 6171471075..1beab90a5f 100644 --- a/proto/zitadel/idp.proto +++ b/proto/zitadel/idp.proto @@ -515,6 +515,21 @@ message Options { description: "Enable if a the ZITADEL account fields should be updated automatically on each login."; } ]; + AutoLinkingOption auto_linking = 5 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Enable if users should get prompted to link an existing ZITADEL user to an external account if the selected attribute matches."; + } + ]; +} + +enum AutoLinkingOption { + // AUTO_LINKING_OPTION_UNSPECIFIED disables the auto linking prompt. + AUTO_LINKING_OPTION_UNSPECIFIED = 0; + // AUTO_LINKING_OPTION_USERNAME will use the username of the external user to check for a corresponding ZITADEL user. + AUTO_LINKING_OPTION_USERNAME = 1; + // AUTO_LINKING_OPTION_EMAIL will use the email of the external user to check for a corresponding ZITADEL user with the same verified email + // Note that in case multiple users match, no prompt will be shown. + AUTO_LINKING_OPTION_EMAIL = 2; } message LDAPAttributes { diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto index aca7ae17ca..0b1a3b6a10 100644 --- a/proto/zitadel/management.proto +++ b/proto/zitadel/management.proto @@ -10948,6 +10948,7 @@ message SetCustomLoginTextsRequest { zitadel.text.v1.PasswordlessRegistrationScreenText passwordless_registration_text = 33; zitadel.text.v1.PasswordlessRegistrationDoneScreenText passwordless_registration_done_text = 34; zitadel.text.v1.ExternalRegistrationUserOverviewScreenText external_registration_user_overview_text = 35; + zitadel.text.v1.LinkingUserPromptScreenText linking_user_prompt_text = 36; } message SetCustomLoginTextsResponse { diff --git a/proto/zitadel/text.proto b/proto/zitadel/text.proto index 602f7f9caf..b6632a1b42 100644 --- a/proto/zitadel/text.proto +++ b/proto/zitadel/text.proto @@ -92,6 +92,7 @@ message LoginCustomText { PasswordlessRegistrationDoneScreenText passwordless_registration_done_text = 34; ExternalRegistrationUserOverviewScreenText external_registration_user_overview_text = 35; bool is_default = 36; + LinkingUserPromptScreenText linking_user_prompt_text = 37; } message SelectAccountScreenText { @@ -357,6 +358,13 @@ message RegistrationOrgScreenText { string save_button_text = 19 [(validate.rules).string = {max_len: 200}]; } +message LinkingUserPromptScreenText { + string title = 1 [(validate.rules).string = {max_len: 200}]; + string description = 2 [(validate.rules).string = {max_len: 500}]; + string link_button_text = 3 [(validate.rules).string = {max_len: 100}]; + string other_button_text = 4 [(validate.rules).string = {max_len: 100}]; +} + message LinkingUserDoneScreenText { string title = 1 [(validate.rules).string = {max_len: 200}]; string description = 2 [(validate.rules).string = {max_len: 500}];