diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 30f9db44a5..ca20382ed8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,31 +11,30 @@ permissions: pull-requests: write jobs: - core: uses: ./.github/workflows/core.yml with: - node_version: '18' - buf_version: 'latest' - go_version: '1.21' - + node_version: "20" + buf_version: "latest" + go_version: "1.21" + console: uses: ./.github/workflows/console.yml with: - node_version: '18' - buf_version: 'latest' + node_version: "20" + buf_version: "latest" version: uses: ./.github/workflows/version.yml with: - semantic_version: '19.0.2' + semantic_version: "19.0.2" dry_run: true compile: needs: [core, console, version] uses: ./.github/workflows/compile.yml with: - go_version: '1.21' + go_version: "1.21" core_cache_key: ${{ needs.core.outputs.cache_key }} console_cache_key: ${{ needs.console.outputs.cache_key }} core_cache_path: ${{ needs.core.outputs.cache_path }} @@ -46,26 +45,26 @@ jobs: needs: core uses: ./.github/workflows/core-unit-test.yml with: - go_version: '1.21' + go_version: "1.21" core_cache_key: ${{ needs.core.outputs.cache_key }} core_cache_path: ${{ needs.core.outputs.cache_path }} - + core-integration-test: needs: core uses: ./.github/workflows/core-integration-test.yml with: - go_version: '1.21' + go_version: "1.21" core_cache_key: ${{ needs.core.outputs.cache_key }} core_cache_path: ${{ needs.core.outputs.cache_path }} - + lint: needs: [core, console] uses: ./.github/workflows/lint.yml with: - go_version: '1.21' - node_version: '18' - buf_version: 'latest' - go_lint_version: 'v1.53.2' + go_version: "1.21" + node_version: "18" + buf_version: "latest" + go_lint_version: "v1.53.2" core_cache_key: ${{ needs.core.outputs.cache_key }} core_cache_path: ${{ needs.core.outputs.cache_path }} @@ -77,7 +76,7 @@ jobs: packages: write if: ${{ github.event_name == 'workflow_dispatch' }} with: - build_image_name: 'ghcr.io/zitadel/zitadel-build' + build_image_name: "ghcr.io/zitadel/zitadel-build" e2e: uses: ./.github/workflows/e2e.yml @@ -90,12 +89,13 @@ jobs: contents: write issues: write pull-requests: write - needs: [version, core-unit-test, core-integration-test, lint, container, e2e] + needs: + [version, core-unit-test, core-integration-test, lint, container, e2e] if: ${{ needs.version.outputs.published == 'true' && github.event_name == 'workflow_dispatch' }} secrets: GCR_JSON_KEY_BASE64: ${{ secrets.GCR_JSON_KEY_BASE64 }} with: build_image_name: ${{ needs.container.outputs.build_image }} - semantic_version: '19.0.2' - image_name: 'ghcr.io/zitadel/zitadel' + semantic_version: "19.0.2" + image_name: "ghcr.io/zitadel/zitadel" google_image_name: "europe-docker.pkg.dev/zitadel-common/zitadel-repo/zitadel" diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml index c95170e2bd..891828941e 100644 --- a/cmd/defaults.yaml +++ b/cmd/defaults.yaml @@ -595,7 +595,7 @@ DefaultInstance: MaxAgeDays: 0 # ZITADEL_DEFAULTINSTANCE_PASSWORDAGEPOLICY_MAXAGEDAYS DomainPolicy: UserLoginMustBeDomain: false # ZITADEL_DEFAULTINSTANCE_DOMAINPOLICY_USERLOGINMUSTBEDOMAIN - ValidateOrgDomains: true # ZITADEL_DEFAULTINSTANCE_DOMAINPOLICY_VALIDATEORGDOMAINS + ValidateOrgDomains: false # ZITADEL_DEFAULTINSTANCE_DOMAINPOLICY_VALIDATEORGDOMAINS SMTPSenderAddressMatchesInstanceDomain: false # ZITADEL_DEFAULTINSTANCE_DOMAINPOLICY_SMTPSENDERADDRESSMATCHESINSTANCEDOMAIN LoginPolicy: AllowUsernamePassword: true # ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_ALLOWUSERNAMEPASSWORD @@ -604,7 +604,7 @@ DefaultInstance: ForceMFA: false # ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_FORCEMFA HidePasswordReset: false # ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_HIDEPASSWORDRESET IgnoreUnknownUsernames: false # ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_IGNOREUNKNOWNUSERNAMES - AllowDomainDiscovery: false # ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_ALLOWDOMAINDISCOVERY + AllowDomainDiscovery: true # ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_ALLOWDOMAINDISCOVERY # 1 is allowed, 0 is not allowed PasswordlessType: 1 # ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_PASSWORDLESSTYPE # DefaultRedirectURL is empty by default because we use the Console UI @@ -761,6 +761,8 @@ DefaultInstance: Greeting: Hello {{.DisplayName}}, Text: The password of your user has changed. If this change was not done by you, please be advised to immediately reset your password. ButtonText: Login + Features: + - FeatureLoginDefaultOrg: true Quotas: # Items take a slice of quota configurations, whereas, for each unit type and instance, one or zero quotas may exist. @@ -819,6 +821,7 @@ InternalAuthZ: - "iam.flow.read" - "iam.flow.write" - "iam.flow.delete" + - "iam.feature.write" - "org.read" - "org.global.read" - "org.create" diff --git a/cmd/ready/config.go b/cmd/ready/config.go index 2b7b56ac00..b9137519ee 100644 --- a/cmd/ready/config.go +++ b/cmd/ready/config.go @@ -23,6 +23,7 @@ func MustNewConfig(v *viper.Viper) *Config { mapstructure.StringToTimeDurationHookFunc(), mapstructure.StringToTimeHookFunc(time.RFC3339), mapstructure.StringToSliceHookFunc(","), + hook.StringToFeatureHookFunc(), )), ) logging.OnError(err).Fatal("unable to read default config") diff --git a/cmd/setup/03.go b/cmd/setup/03.go index ffa4769a42..4de513be40 100644 --- a/cmd/setup/03.go +++ b/cmd/setup/03.go @@ -24,6 +24,7 @@ type FirstInstance struct { Org command.InstanceOrgSetup MachineKeyPath string PatPath string + Features map[domain.Feature]any instanceSetup command.InstanceSetup userEncryptionKey *crypto.KeyConfig diff --git a/cmd/setup/config.go b/cmd/setup/config.go index 3290b59e6a..068f524b92 100644 --- a/cmd/setup/config.go +++ b/cmd/setup/config.go @@ -43,6 +43,7 @@ func MustNewConfig(v *viper.Viper) *Config { mapstructure.StringToTimeHookFunc(time.RFC3339), mapstructure.StringToSliceHookFunc(","), database.DecodeHook, + hook.StringToFeatureHookFunc(), )), ) logging.OnError(err).Fatal("unable to read default config") @@ -99,6 +100,7 @@ func MustNewSteps(v *viper.Viper) *Steps { mapstructure.StringToTimeDurationHookFunc(), mapstructure.StringToTimeHookFunc(time.RFC3339), mapstructure.StringToSliceHookFunc(","), + hook.StringToFeatureHookFunc(), )), ) logging.OnError(err).Fatal("unable to read steps") diff --git a/cmd/start/config.go b/cmd/start/config.go index 6280fdea40..6ef844feed 100644 --- a/cmd/start/config.go +++ b/cmd/start/config.go @@ -92,6 +92,7 @@ func MustNewConfig(v *viper.Viper) *Config { database.DecodeHook, actions.HTTPConfigDecodeHook, systemAPIUsersDecodeHook, + hook.StringToFeatureHookFunc(), )), ) logging.OnError(err).Fatal("unable to read config") diff --git a/cmd/start/start.go b/cmd/start/start.go index b7eb8e59c3..4c6a92c4e8 100644 --- a/cmd/start/start.go +++ b/cmd/start/start.go @@ -27,6 +27,7 @@ import ( "github.com/zitadel/zitadel/cmd/build" "github.com/zitadel/zitadel/cmd/key" cmd_tls "github.com/zitadel/zitadel/cmd/tls" + "github.com/zitadel/zitadel/feature" "github.com/zitadel/zitadel/internal/actions" admin_es "github.com/zitadel/zitadel/internal/admin/repository/eventsourcing" "github.com/zitadel/zitadel/internal/api" @@ -40,7 +41,7 @@ import ( "github.com/zitadel/zitadel/internal/api/grpc/session/v2" "github.com/zitadel/zitadel/internal/api/grpc/settings/v2" "github.com/zitadel/zitadel/internal/api/grpc/system" - "github.com/zitadel/zitadel/internal/api/grpc/user/v2" + user_v2 "github.com/zitadel/zitadel/internal/api/grpc/user/v2" http_util "github.com/zitadel/zitadel/internal/api/http" "github.com/zitadel/zitadel/internal/api/http/middleware" "github.com/zitadel/zitadel/internal/api/idp" @@ -356,7 +357,7 @@ func startAPIs( if err := apis.RegisterServer(ctx, auth.CreateServer(commands, queries, authRepo, config.SystemDefaults, keys.User, config.ExternalSecure, config.AuditLogRetention)); err != nil { return err } - if err := apis.RegisterService(ctx, user.CreateServer(commands, queries, keys.User, keys.IDPConfig, idp.CallbackURL(config.ExternalSecure))); err != nil { + if err := apis.RegisterService(ctx, user_v2.CreateServer(commands, queries, keys.User, keys.IDPConfig, idp.CallbackURL(config.ExternalSecure), idp.SAMLRootURL(config.ExternalSecure))); err != nil { return err } if err := apis.RegisterService(ctx, session.CreateServer(commands, queries, permissionCheck)); err != nil { @@ -375,7 +376,7 @@ func startAPIs( apis.RegisterHandlerOnPrefix(idp.HandlerPrefix, idp.NewHandler(commands, queries, keys.IDPConfig, config.ExternalSecure, instanceInterceptor.Handler)) - userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, id.SonyFlakeGenerator(), config.ExternalSecure, login.EndpointResources, login.EndpointExternalLoginCallbackFormPost) + userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, id.SonyFlakeGenerator(), config.ExternalSecure, login.EndpointResources, login.EndpointExternalLoginCallbackFormPost, login.EndpointSAMLACS) if err != nil { return err } @@ -412,7 +413,27 @@ func startAPIs( } apis.RegisterHandlerOnPrefix(console.HandlerPrefix, c) - l, err := login.CreateLogin(config.Login, commands, queries, authRepo, store, console.HandlerPrefix+"/", op.AuthCallbackURL(oidcProvider), provider.AuthCallbackURL(samlProvider), config.ExternalSecure, userAgentInterceptor, op.NewIssuerInterceptor(oidcProvider.IssuerFromRequest).Handler, provider.NewIssuerInterceptor(samlProvider.IssuerFromRequest).Handler, instanceInterceptor.Handler, assetsCache.Handler, limitingAccessInterceptor.WithoutLimiting().Handle, keys.User, keys.IDPConfig, keys.CSRFCookieKey) + l, err := login.CreateLogin( + config.Login, + commands, + queries, + authRepo, + store, + console.HandlerPrefix+"/", + op.AuthCallbackURL(oidcProvider), + provider.AuthCallbackURL(samlProvider), + config.ExternalSecure, + userAgentInterceptor, + op.NewIssuerInterceptor(oidcProvider.IssuerFromRequest).Handler, + provider.NewIssuerInterceptor(samlProvider.IssuerFromRequest).Handler, + instanceInterceptor.Handler, + assetsCache.Handler, + limitingAccessInterceptor.WithoutLimiting().Handle, + keys.User, + keys.IDPConfig, + keys.CSRFCookieKey, + feature.NewCheck(eventstore), + ) if err != nil { return fmt.Errorf("unable to start login: %w", err) } diff --git a/console/package.json b/console/package.json index a1be7a18c6..5b677e6c25 100644 --- a/console/package.json +++ b/console/package.json @@ -25,7 +25,7 @@ "@angular/router": "^16.2.0", "@angular/service-worker": "^16.2.0", "@ctrl/ngx-codemirror": "^6.1.0", - "@grpc/grpc-js": "^1.8.14", + "@grpc/grpc-js": "^1.9.3", "@ngx-translate/core": "^14.0.0", "angular-oauth2-oidc": "^15.0.1", "angularx-qrcode": "^16.0.0", @@ -41,8 +41,8 @@ "libphonenumber-js": "^1.10.30", "material-design-icons-iconfont": "^6.1.1", "moment": "^2.29.4", - "opentype.js": "^1.3.4", "ngx-color": "^9.0.0", + "opentype.js": "^1.3.4", "rxjs": "~7.8.0", "tinycolor2": "^1.6.0", "tslib": "^2.4.1", @@ -62,17 +62,17 @@ "@bufbuild/buf": "^1.23.1", "@types/file-saver": "^2.0.2", "@types/google-protobuf": "^3.15.3", - "@types/jasmine": "~4.3.3", + "@types/jasmine": "~4.3.6", "@types/jasminewd2": "~2.0.10", "@types/jsonwebtoken": "^9.0.1", - "@types/node": "^18.15.11", + "@types/node": "^20.7.0", "@types/opentype.js": "^1.3.4", - "@types/qrcode": "^1.5.0", + "@types/qrcode": "^1.5.2", "@types/uuid": "^9.0.2", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.60.1", "codelyzer": "^6.0.2", - "eslint": "^8.44.0", + "eslint": "^8.50.0", "jasmine-core": "~4.6.0", "jasmine-spec-reporter": "~7.0.0", "karma": "^6.4.2", @@ -80,9 +80,9 @@ "karma-coverage-istanbul-reporter": "^3.0.3", "karma-jasmine": "^5.1.0", "karma-jasmine-html-reporter": "^2.1.0", - "prettier": "^2.8.7", + "prettier": "^3.0.3", "prettier-plugin-organize-imports": "^3.2.2", "protractor": "~7.0.0", "typescript": "^4.9.5" } -} \ No newline at end of file +} diff --git a/console/src/app/app-routing.module.ts b/console/src/app/app-routing.module.ts index c1ff7b0c43..624c32a819 100644 --- a/console/src/app/app-routing.module.ts +++ b/console/src/app/app-routing.module.ts @@ -158,14 +158,6 @@ const routes: Routes = [ requiresAll: true, }, }, - { - path: 'domains', - loadChildren: () => import('./pages/domains/domains.module'), - canActivate: [AuthGuard, RoleGuard], - data: { - roles: ['org.read'], - }, - }, { path: 'org-settings', loadChildren: () => import('./pages/org-settings/org-settings.module'), diff --git a/console/src/app/guards/user.guard.ts b/console/src/app/guards/user.guard.ts index 8a037f2426..c57b896ece 100644 --- a/console/src/app/guards/user.guard.ts +++ b/console/src/app/guards/user.guard.ts @@ -9,7 +9,10 @@ import { GrpcAuthService } from '../services/grpc-auth.service'; providedIn: 'root', }) export class UserGuard { - constructor(private authService: GrpcAuthService, private router: Router) {} + constructor( + private authService: GrpcAuthService, + private router: Router, + ) {} public canActivate( route: ActivatedRouteSnapshot, diff --git a/console/src/app/modules/accounts-card/accounts-card.component.ts b/console/src/app/modules/accounts-card/accounts-card.component.ts index ea2c3c3019..617a41bf6d 100644 --- a/console/src/app/modules/accounts-card/accounts-card.component.ts +++ b/console/src/app/modules/accounts-card/accounts-card.component.ts @@ -18,7 +18,11 @@ export class AccountsCardComponent implements OnInit { public sessions: Session.AsObject[] = []; public loadingUsers: boolean = false; public UserState: any = UserState; - constructor(public authService: AuthenticationService, private router: Router, private userService: GrpcAuthService) { + constructor( + public authService: AuthenticationService, + private router: Router, + private userService: GrpcAuthService, + ) { this.userService .listMyUserSessions() .then((sessions) => { diff --git a/console/src/app/modules/add-key-dialog/add-key-dialog.component.html b/console/src/app/modules/add-key-dialog/add-key-dialog.component.html index 050983c737..ed79c5940e 100644 --- a/console/src/app/modules/add-key-dialog/add-key-dialog.component.html +++ b/console/src/app/modules/add-key-dialog/add-key-dialog.component.html @@ -18,7 +18,7 @@ {{ 'USER.MACHINE.CHOOSEDATEAFTER' | translate }}: - {{ dateControl.errors['matDatepickerMin'].min.toDate() | localizedDate : 'EEE dd. MMM' }} + {{ dateControl.errors['matDatepickerMin'].min.toDate() | localizedDate: 'EEE dd. MMM' }} diff --git a/console/src/app/modules/add-key-dialog/add-key-dialog.component.ts b/console/src/app/modules/add-key-dialog/add-key-dialog.component.ts index 84b2e40088..c072b974b0 100644 --- a/console/src/app/modules/add-key-dialog/add-key-dialog.component.ts +++ b/console/src/app/modules/add-key-dialog/add-key-dialog.component.ts @@ -22,7 +22,10 @@ export class AddKeyDialogComponent { public type!: KeyType; public dateControl: UntypedFormControl = new UntypedFormControl('', []); - constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any) { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + ) { this.types = [KeyType.KEY_TYPE_JSON]; this.type = KeyType.KEY_TYPE_JSON; const today = new Date(); diff --git a/console/src/app/modules/add-member-roles-dialog/add-member-roles-dialog.component.html b/console/src/app/modules/add-member-roles-dialog/add-member-roles-dialog.component.html index d2bf166572..14d48a887f 100644 --- a/console/src/app/modules/add-member-roles-dialog/add-member-roles-dialog.component.html +++ b/console/src/app/modules/add-member-roles-dialog/add-member-roles-dialog.component.html @@ -1,6 +1,6 @@ {{ 'MEMBER.EDITROLE' | translate }}
-

{{ 'MEMBER.EDITFOR' | translate : { value: data.user } }}

+

{{ 'MEMBER.EDITFOR' | translate: { value: data.user } }}

, @Inject(MAT_DIALOG_DATA) public data: any) { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + ) { this.allRoles = Object.assign([], data.allRoles); this.selectedRoles = Object.assign([], data.selectedRoles); } diff --git a/console/src/app/modules/add-token-dialog/add-token-dialog.component.html b/console/src/app/modules/add-token-dialog/add-token-dialog.component.html index 6a97cb0623..3cdd3067de 100644 --- a/console/src/app/modules/add-token-dialog/add-token-dialog.component.html +++ b/console/src/app/modules/add-token-dialog/add-token-dialog.component.html @@ -9,7 +9,7 @@ {{ 'USER.PERSONALACCESSTOKEN.ADD.CHOOSEDATEAFTER' | translate }}: - {{ dateControl.errors['matDatepickerMin'].min.toDate() | localizedDate : 'EEE dd. MMM' }} + {{ dateControl.errors['matDatepickerMin'].min.toDate() | localizedDate: 'EEE dd. MMM' }}
diff --git a/console/src/app/modules/add-token-dialog/add-token-dialog.component.ts b/console/src/app/modules/add-token-dialog/add-token-dialog.component.ts index a89491a715..d41cb3fc2f 100644 --- a/console/src/app/modules/add-token-dialog/add-token-dialog.component.ts +++ b/console/src/app/modules/add-token-dialog/add-token-dialog.component.ts @@ -14,7 +14,10 @@ export class AddTokenDialogComponent { public startDate: Date = new Date(); public dateControl: UntypedFormControl = new UntypedFormControl('', []); - constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any) { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + ) { const today = new Date(); this.startDate.setDate(today.getDate() + 1); } diff --git a/console/src/app/modules/avatar/avatar.component.scss b/console/src/app/modules/avatar/avatar.component.scss index 86ee99cb29..742728c5ba 100644 --- a/console/src/app/modules/avatar/avatar.component.scss +++ b/console/src/app/modules/avatar/avatar.component.scss @@ -17,7 +17,11 @@ outline: none; color: white; font-weight: bold; - font-family: 'Lato', -apple-system, BlinkMacSystemFont, sans-serif; + font-family: + 'Lato', + -apple-system, + BlinkMacSystemFont, + sans-serif; // box-shadow: 0 0 3px #0000001a; img { diff --git a/console/src/app/modules/changes/changes.component.html b/console/src/app/modules/changes/changes.component.html index 2504b19b99..1cf65b3db8 100644 --- a/console/src/app/modules/changes/changes.component.html +++ b/console/src/app/modules/changes/changes.component.html @@ -5,7 +5,7 @@
  • - {{ hist.values[0].dates[0] | timestampToDate | localizedDate : 'dd. MMM YYYY' }} + {{ hist.values[0].dates[0] | timestampToDate | localizedDate: 'dd. MMM YYYY' }}
    @@ -28,8 +28,8 @@ {{ action.localizedMessage }} {{ dayelement.dates[j] | timestampToDate | localizedDate : 'HH:mm' }}{{ dayelement.dates[j] | timestampToDate | localizedDate: 'HH:mm' }}
    diff --git a/console/src/app/modules/changes/changes.component.ts b/console/src/app/modules/changes/changes.component.ts index 615449a14d..d4ae9b030c 100644 --- a/console/src/app/modules/changes/changes.component.ts +++ b/console/src/app/modules/changes/changes.component.ts @@ -70,7 +70,10 @@ export class ChangesComponent implements OnInit, OnDestroy { ); public changes!: ListChanges; private destroyed$: Subject = new Subject(); - constructor(private mgmtUserService: ManagementService, private authUserService: GrpcAuthService) {} + constructor( + private mgmtUserService: ManagementService, + private authUserService: GrpcAuthService, + ) {} ngOnInit(): void { this.init(); diff --git a/console/src/app/modules/client-keys/client-keys.component.html b/console/src/app/modules/client-keys/client-keys.component.html index f41c1672f8..80c6d42682 100644 --- a/console/src/app/modules/client-keys/client-keys.component.html +++ b/console/src/app/modules/client-keys/client-keys.component.html @@ -52,14 +52,14 @@ {{ 'USER.MACHINE.CREATIONDATE' | translate }} - {{ key.details.creationDate | timestampToDate | localizedDate : 'fromNow' }} + {{ key.details.creationDate | timestampToDate | localizedDate: 'fromNow' }} {{ 'USER.MACHINE.EXPIRATIONDATE' | translate }} - {{ key.expirationDate | timestampToDate | localizedDate : 'fromNow' }} + {{ key.expirationDate | timestampToDate | localizedDate: 'fromNow' }} diff --git a/console/src/app/modules/display-json-dialog/display-json-dialog.component.html b/console/src/app/modules/display-json-dialog/display-json-dialog.component.html index 54f838f5a4..1e38fd5c52 100644 --- a/console/src/app/modules/display-json-dialog/display-json-dialog.component.html +++ b/console/src/app/modules/display-json-dialog/display-json-dialog.component.html @@ -8,7 +8,7 @@
    {{ 'IAM.EVENTS.CREATIONDATE' | translate }} - {{ event.creationDate | timestampToDate | localizedDate : 'EEE dd. MMM, HH:mm' }} + {{ event.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}
    diff --git a/console/src/app/modules/display-json-dialog/display-json-dialog.component.ts b/console/src/app/modules/display-json-dialog/display-json-dialog.component.ts index 9bf57fc232..1e0a781294 100644 --- a/console/src/app/modules/display-json-dialog/display-json-dialog.component.ts +++ b/console/src/app/modules/display-json-dialog/display-json-dialog.component.ts @@ -16,7 +16,10 @@ export class DisplayJsonDialogComponent { public payload: any = ''; public opened$ = this.dialogRef.afterOpened().pipe(mapTo(true)); - constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any) { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + ) { this.event = data.event; if ((data.event as Event) && data.event.payload) { } diff --git a/console/src/app/pages/domains/add-domain-dialog/add-domain-dialog.component.html b/console/src/app/modules/domains/add-domain-dialog/add-domain-dialog.component.html similarity index 100% rename from console/src/app/pages/domains/add-domain-dialog/add-domain-dialog.component.html rename to console/src/app/modules/domains/add-domain-dialog/add-domain-dialog.component.html diff --git a/console/src/app/pages/domains/add-domain-dialog/add-domain-dialog.component.scss b/console/src/app/modules/domains/add-domain-dialog/add-domain-dialog.component.scss similarity index 100% rename from console/src/app/pages/domains/add-domain-dialog/add-domain-dialog.component.scss rename to console/src/app/modules/domains/add-domain-dialog/add-domain-dialog.component.scss diff --git a/console/src/app/pages/domains/add-domain-dialog/add-domain-dialog.component.spec.ts b/console/src/app/modules/domains/add-domain-dialog/add-domain-dialog.component.spec.ts similarity index 100% rename from console/src/app/pages/domains/add-domain-dialog/add-domain-dialog.component.spec.ts rename to console/src/app/modules/domains/add-domain-dialog/add-domain-dialog.component.spec.ts diff --git a/console/src/app/pages/domains/add-domain-dialog/add-domain-dialog.component.ts b/console/src/app/modules/domains/add-domain-dialog/add-domain-dialog.component.ts similarity index 81% rename from console/src/app/pages/domains/add-domain-dialog/add-domain-dialog.component.ts rename to console/src/app/modules/domains/add-domain-dialog/add-domain-dialog.component.ts index 2c11d0f650..9adce8dc15 100644 --- a/console/src/app/pages/domains/add-domain-dialog/add-domain-dialog.component.ts +++ b/console/src/app/modules/domains/add-domain-dialog/add-domain-dialog.component.ts @@ -11,7 +11,10 @@ import { }) export class AddDomainDialogComponent { public newdomain: string = ''; - constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any) {} + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + ) {} public closeDialog(): void { this.dialogRef.close(false); diff --git a/console/src/app/pages/domains/add-domain-dialog/add-domain-dialog.module.ts b/console/src/app/modules/domains/add-domain-dialog/add-domain-dialog.module.ts similarity index 100% rename from console/src/app/pages/domains/add-domain-dialog/add-domain-dialog.module.ts rename to console/src/app/modules/domains/add-domain-dialog/add-domain-dialog.module.ts diff --git a/console/src/app/pages/domains/domain-verification/domain-verification.component.html b/console/src/app/modules/domains/domain-verification/domain-verification.component.html similarity index 99% rename from console/src/app/pages/domains/domain-verification/domain-verification.component.html rename to console/src/app/modules/domains/domain-verification/domain-verification.component.html index 364bcb659f..7eb53f6e4a 100644 --- a/console/src/app/pages/domains/domain-verification/domain-verification.component.html +++ b/console/src/app/modules/domains/domain-verification/domain-verification.component.html @@ -10,7 +10,7 @@ *ngIf="domain?.validationType !== DomainValidationType.DOMAIN_VALIDATION_TYPE_UNSPECIFIED && !(dns || http)" class="desc" > - {{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION_VALIDATION_ONGOING' | translate : domain }} + {{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION_VALIDATION_ONGOING' | translate: domain }} {{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION_VALIDATION_ONGOING_TYPE' | translate }} {{ 'ORG.PAGES.ORGDOMAIN.TYPES.' + domain?.validationType | translate }}

    diff --git a/console/src/app/pages/domains/domain-verification/domain-verification.component.scss b/console/src/app/modules/domains/domain-verification/domain-verification.component.scss similarity index 100% rename from console/src/app/pages/domains/domain-verification/domain-verification.component.scss rename to console/src/app/modules/domains/domain-verification/domain-verification.component.scss diff --git a/console/src/app/pages/domains/domain-verification/domain-verification.component.spec.ts b/console/src/app/modules/domains/domain-verification/domain-verification.component.spec.ts similarity index 100% rename from console/src/app/pages/domains/domain-verification/domain-verification.component.spec.ts rename to console/src/app/modules/domains/domain-verification/domain-verification.component.spec.ts diff --git a/console/src/app/pages/domains/domain-verification/domain-verification.component.ts b/console/src/app/modules/domains/domain-verification/domain-verification.component.ts similarity index 100% rename from console/src/app/pages/domains/domain-verification/domain-verification.component.ts rename to console/src/app/modules/domains/domain-verification/domain-verification.component.ts diff --git a/console/src/app/modules/domains/domains.component.html b/console/src/app/modules/domains/domains.component.html new file mode 100644 index 0000000000..b44caeca9f --- /dev/null +++ b/console/src/app/modules/domains/domains.component.html @@ -0,0 +1,67 @@ + +
    +
    +
    +

    {{ 'ORG.DOMAINS.TITLE' | translate }}

    + + + +
    +

    {{ 'ORG.DOMAINS.DESCRIPTION' | translate }}

    +
    + + + +
    + + +
    + {{ domain.domainName }} + + + + {{ 'ORG.DOMAINS.SETPRIMARY' | translate }} + + + + +
    +
    +
    diff --git a/console/src/app/pages/domains/domains.component.scss b/console/src/app/modules/domains/domains.component.scss similarity index 97% rename from console/src/app/pages/domains/domains.component.scss rename to console/src/app/modules/domains/domains.component.scss index f60a63e2e8..8a324173b5 100644 --- a/console/src/app/pages/domains/domains.component.scss +++ b/console/src/app/modules/domains/domains.component.scss @@ -1,7 +1,6 @@ .domain-top-view { display: flex; align-items: center; - padding-top: 2rem; .domain-title-row { display: flex; diff --git a/console/src/app/pages/domains/domains.component.spec.ts b/console/src/app/modules/domains/domains.component.spec.ts similarity index 100% rename from console/src/app/pages/domains/domains.component.spec.ts rename to console/src/app/modules/domains/domains.component.spec.ts diff --git a/console/src/app/pages/domains/domains.component.ts b/console/src/app/modules/domains/domains.component.ts similarity index 87% rename from console/src/app/pages/domains/domains.component.ts rename to console/src/app/modules/domains/domains.component.ts index 6b1b5abd56..6a957e06c9 100644 --- a/console/src/app/pages/domains/domains.component.ts +++ b/console/src/app/modules/domains/domains.component.ts @@ -19,6 +19,7 @@ export class DomainsComponent implements OnInit { public domains: Domain.AsObject[] = []; public primaryDomain: string = ''; public InfoSectionType: any = InfoSectionType; + public verifyOrgDomains: boolean | undefined; constructor( private mgmtService: ManagementService, @@ -38,6 +39,10 @@ export class DomainsComponent implements OnInit { } public loadDomains(): void { + this.mgmtService.getDomainPolicy().then((result) => { + this.verifyOrgDomains = result.policy?.validateOrgDomains; + }); + this.mgmtService.listOrgDomains().then((result) => { this.domains = result.resultList; this.primaryDomain = this.domains.find((domain) => domain.isPrimary)?.domainName ?? ''; @@ -68,13 +73,14 @@ export class DomainsComponent implements OnInit { .addOrgDomain(domainName) .then(() => { this.toast.showInfo('ORG.TOAST.DOMAINADDED', true); - this.verifyDomain({ - domainName: domainName, - validationType: DomainValidationType.DOMAIN_VALIDATION_TYPE_UNSPECIFIED, - }); - setTimeout(() => { + if (this.verifyOrgDomains) { + this.verifyDomain({ + domainName: domainName, + validationType: DomainValidationType.DOMAIN_VALIDATION_TYPE_UNSPECIFIED, + }); + } else { this.loadDomains(); - }, 1000); + } }) .catch((error) => { this.toast.showError(error); @@ -120,10 +126,8 @@ export class DomainsComponent implements OnInit { width: '500px', }); - dialogRef.afterClosed().subscribe((reload: boolean) => { - if (reload) { - this.loadDomains(); - } + dialogRef.afterClosed().subscribe(() => { + this.loadDomains(); }); } } diff --git a/console/src/app/pages/domains/domains.module.ts b/console/src/app/modules/domains/domains.module.ts similarity index 94% rename from console/src/app/pages/domains/domains.module.ts rename to console/src/app/modules/domains/domains.module.ts index 0b089e1f34..15fb0c0605 100644 --- a/console/src/app/pages/domains/domains.module.ts +++ b/console/src/app/modules/domains/domains.module.ts @@ -14,13 +14,11 @@ import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.mod import { AddDomainDialogModule } from './add-domain-dialog/add-domain-dialog.module'; import { DomainVerificationComponent } from './domain-verification/domain-verification.component'; -import { DomainsRoutingModule } from './domains-routing.module'; import { DomainsComponent } from './domains.component'; @NgModule({ declarations: [DomainsComponent, DomainVerificationComponent], imports: [ - DomainsRoutingModule, AddDomainDialogModule, CommonModule, MatIconModule, @@ -36,5 +34,6 @@ import { DomainsComponent } from './domains.component'; InfoSectionModule, MatProgressSpinnerModule, ], + exports: [DomainsComponent], }) export default class DomainsModule {} diff --git a/console/src/app/modules/filter-org/filter-org.component.ts b/console/src/app/modules/filter-org/filter-org.component.ts index 1927a23807..6a1d677e67 100644 --- a/console/src/app/modules/filter-org/filter-org.component.ts +++ b/console/src/app/modules/filter-org/filter-org.component.ts @@ -24,7 +24,10 @@ export class FilterOrgComponent extends FilterComponent implements OnInit { public states: OrgState[] = [OrgState.ORG_STATE_ACTIVE, OrgState.ORG_STATE_INACTIVE, OrgState.ORG_STATE_REMOVED]; - constructor(router: Router, protected override route: ActivatedRoute) { + constructor( + router: Router, + protected override route: ActivatedRoute, + ) { super(router, route); } diff --git a/console/src/app/modules/filter/filter.component.ts b/console/src/app/modules/filter/filter.component.ts index 1c503acd8f..ce2cc15c08 100644 --- a/console/src/app/modules/filter/filter.component.ts +++ b/console/src/app/modules/filter/filter.component.ts @@ -64,7 +64,10 @@ export class FilterComponent implements OnDestroy { this.destroy$.complete(); } - constructor(private router: Router, protected route: ActivatedRoute) { + constructor( + private router: Router, + protected route: ActivatedRoute, + ) { const changes$ = this.filterChanged.asObservable(); changes$.pipe(takeUntil(this.destroy$)).subscribe((queries) => { const filters: Array | undefined = queries diff --git a/console/src/app/modules/form-field/field/form-field.component.html b/console/src/app/modules/form-field/field/form-field.component.html index 245bc57f80..2ba38b9e2a 100644 --- a/console/src/app/modules/form-field/field/form-field.component.html +++ b/console/src/app/modules/form-field/field/form-field.component.html @@ -20,7 +20,7 @@
    - {{ err.i18nKey | translate : err.params }} + {{ err.i18nKey | translate: err.params }}
    diff --git a/console/src/app/modules/header/header.component.html b/console/src/app/modules/header/header.component.html index 002efa3b0d..318d916c7d 100644 --- a/console/src/app/modules/header/header.component.html +++ b/console/src/app/modules/header/header.component.html @@ -195,7 +195,7 @@
  • - + @@ -144,14 +144,14 @@

    {{ 'ORG.PAGES.CREATIONDATE' | translate }}

    - {{ org.details.creationDate | timestampToDate | localizedDate : 'dd. MMMM YYYY, HH:mm' }} + {{ org.details.creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}

    {{ 'ORG.PAGES.DATECHANGED' | translate }}

    - {{ org.details.changeDate | timestampToDate | localizedDate : 'dd. MMMM YYYY, HH:mm' }} + {{ org.details.changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}

    @@ -179,14 +179,14 @@

    {{ 'PROJECT.PAGES.CREATEDON' | translate }}

    - {{ project.details.creationDate | timestampToDate | localizedDate : 'dd. MMMM YYYY, HH:mm' }} + {{ project.details.creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}

    {{ 'PROJECT.PAGES.LASTMODIFIED' | translate }}

    - {{ project.details.changeDate | timestampToDate | localizedDate : 'dd. MMMM YYYY, HH:mm' }} + {{ project.details.changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}

    @@ -219,14 +219,14 @@

    {{ 'PROJECT.PAGES.CREATEDON' | translate }}

    - {{ grantedProject.details.creationDate | timestampToDate | localizedDate : 'dd. MMMM YYYY, HH:mm' }} + {{ grantedProject.details.creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}

    {{ 'PROJECT.PAGES.LASTMODIFIED' | translate }}

    - {{ grantedProject.details.changeDate | timestampToDate | localizedDate : 'dd. MMMM YYYY, HH:mm' }} + {{ grantedProject.details.changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}

    @@ -252,14 +252,14 @@

    {{ 'APP.PAGES.DATECREATED' | translate }}

    - {{ app.details.creationDate | timestampToDate | localizedDate : 'dd. MMMM YYYY, HH:mm' }} + {{ app.details.creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}

    {{ 'APP.PAGES.DATECHANGED' | translate }}

    - {{ app.details.changeDate | timestampToDate | localizedDate : 'dd. MMMM YYYY, HH:mm' }} + {{ app.details.changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}

    @@ -305,14 +305,14 @@

    {{ 'IDP.DETAIL.DATECREATED' | translate }}

    - {{ idp.details.creationDate | timestampToDate | localizedDate : 'dd. MMMM YYYY, HH:mm' }} + {{ idp.details.creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}

    {{ 'IDP.DETAIL.DATECHANGED' | translate }}

    - {{ idp.details.changeDate | timestampToDate | localizedDate : 'dd. MMMM YYYY, HH:mm' }} + {{ idp.details.changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}

    diff --git a/console/src/app/modules/machine-keys/machine-keys.component.html b/console/src/app/modules/machine-keys/machine-keys.component.html index 3229928160..49cd48855a 100644 --- a/console/src/app/modules/machine-keys/machine-keys.component.html +++ b/console/src/app/modules/machine-keys/machine-keys.component.html @@ -52,14 +52,14 @@ {{ 'USER.MACHINE.CREATIONDATE' | translate }} - {{ key.details?.creationDate | timestampToDate | localizedDate : 'fromNow' }} + {{ key.details?.creationDate | timestampToDate | localizedDate: 'fromNow' }} {{ 'USER.MACHINE.EXPIRATIONDATE' | translate }} - {{ key.expirationDate | timestampToDate | localizedDate : 'fromNow' }} + {{ key.expirationDate | timestampToDate | localizedDate: 'fromNow' }} diff --git a/console/src/app/modules/members-table/members-table.component.html b/console/src/app/modules/members-table/members-table.component.html index f92b598674..7f69806bc5 100644 --- a/console/src/app/modules/members-table/members-table.component.html +++ b/console/src/app/modules/members-table/members-table.component.html @@ -99,7 +99,7 @@ diff --git a/console/src/app/modules/memberships-table/memberships-datasource.ts b/console/src/app/modules/memberships-table/memberships-datasource.ts index 21de06081d..28e3c1a3ef 100644 --- a/console/src/app/modules/memberships-table/memberships-datasource.ts +++ b/console/src/app/modules/memberships-table/memberships-datasource.ts @@ -13,7 +13,10 @@ export class MembershipsDataSource extends DataSource { private loadingSubject: BehaviorSubject = new BehaviorSubject(false); public loading$: Observable = this.loadingSubject.asObservable(); - constructor(private auth: GrpcAuthService, private service: ManagementService) { + constructor( + private auth: GrpcAuthService, + private service: ManagementService, + ) { super(); } diff --git a/console/src/app/modules/metadata/metadata-dialog/metadata-dialog.component.html b/console/src/app/modules/metadata/metadata-dialog/metadata-dialog.component.html index 2b68716459..5c04eb96d2 100644 --- a/console/src/app/modules/metadata/metadata-dialog/metadata-dialog.component.html +++ b/console/src/app/modules/metadata/metadata-dialog/metadata-dialog.component.html @@ -1,7 +1,7 @@

    {{ 'METADATA.TITLE' | translate }}

    -

    {{ ts | timestampToDate | localizedDate : 'dd. MMM, HH:mm' }}

    +

    {{ ts | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}

    {{ 'METADATA.DESCRIPTION' | translate }}

    diff --git a/console/src/app/modules/name-dialog/name-dialog.component.ts b/console/src/app/modules/name-dialog/name-dialog.component.ts index bd05e1ea7e..7baa65b2a7 100644 --- a/console/src/app/modules/name-dialog/name-dialog.component.ts +++ b/console/src/app/modules/name-dialog/name-dialog.component.ts @@ -11,7 +11,10 @@ import { }) export class NameDialogComponent { public name: string = ''; - constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any) { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + ) { this.name = data.name ?? ''; } diff --git a/console/src/app/modules/nav-toggle/nav-toggle.component.html b/console/src/app/modules/nav-toggle/nav-toggle.component.html index 7454498bf1..81b1b216d6 100644 --- a/console/src/app/modules/nav-toggle/nav-toggle.component.html +++ b/console/src/app/modules/nav-toggle/nav-toggle.component.html @@ -1,4 +1,4 @@ - -
    diff --git a/console/src/app/modules/policies/general-settings/general-settings.component.ts b/console/src/app/modules/policies/general-settings/general-settings.component.ts index 397aa616c9..d549b74fe4 100644 --- a/console/src/app/modules/policies/general-settings/general-settings.component.ts +++ b/console/src/app/modules/policies/general-settings/general-settings.component.ts @@ -13,7 +13,10 @@ export class GeneralSettingsComponent implements OnInit { public defaultLanguageOptions: string[] = []; public loading: boolean = false; - constructor(private service: AdminService, private toast: ToastService) {} + constructor( + private service: AdminService, + private toast: ToastService, + ) {} ngOnInit(): void { this.fetchData(); diff --git a/console/src/app/modules/policies/login-policy/factor-table/dialog-add-type/dialog-add-type.component.ts b/console/src/app/modules/policies/login-policy/factor-table/dialog-add-type/dialog-add-type.component.ts index e9f401e080..5a71691ed0 100644 --- a/console/src/app/modules/policies/login-policy/factor-table/dialog-add-type/dialog-add-type.component.ts +++ b/console/src/app/modules/policies/login-policy/factor-table/dialog-add-type/dialog-add-type.component.ts @@ -20,7 +20,10 @@ export class DialogAddTypeComponent { public availableMfaTypes: Array = []; public newMfaType!: MultiFactorType | SecondFactorType; - constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any) { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + ) { this.availableMfaTypes = data.types; this.newMfaType = data.types && data.types[0] ? data.types[0] : undefined; } diff --git a/console/src/app/modules/policies/login-policy/factor-table/factor-table.component.ts b/console/src/app/modules/policies/login-policy/factor-table/factor-table.component.ts index 916e805d01..f1f050dc1a 100644 --- a/console/src/app/modules/policies/login-policy/factor-table/factor-table.component.ts +++ b/console/src/app/modules/policies/login-policy/factor-table/factor-table.component.ts @@ -51,7 +51,11 @@ export class FactorTableComponent { public PolicyComponentServiceType: any = PolicyComponentServiceType; - constructor(public translate: TranslateService, private toast: ToastService, private dialog: MatDialog) {} + constructor( + public translate: TranslateService, + private toast: ToastService, + private dialog: MatDialog, + ) {} public removeMfa(type: MultiFactorType | SecondFactorType): void { const dialogRef = this.dialog.open(WarnDialogComponent, { diff --git a/console/src/app/modules/policies/login-texts/login-texts.component.html b/console/src/app/modules/policies/login-texts/login-texts.component.html index 83321681f7..ae14b6c135 100644 --- a/console/src/app/modules/policies/login-texts/login-texts.component.html +++ b/console/src/app/modules/policies/login-texts/login-texts.component.html @@ -10,11 +10,11 @@

    {{ 'POLICY.LOGIN_TEXTS.NEWERVERSIONEXISTS' | translate }}

    {{ 'POLICY.LOGIN_TEXTS.CHANGEDATE' | translate }}: - {{ newerPolicyChangeDate | timestampToDate | localizedDate : 'dd. MMMM YYYY, HH:mm:ss' }} + {{ newerPolicyChangeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm:ss' }}

    {{ 'POLICY.LOGIN_TEXTS.CURRENTDATE' | translate }}: - {{ currentPolicyChangeDate | timestampToDate | localizedDate : 'dd. MMMM YYYY, HH:mm:ss' }} + {{ currentPolicyChangeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm:ss' }}

    diff --git a/console/src/app/modules/user-grants/user-grants.component.ts b/console/src/app/modules/user-grants/user-grants.component.ts index af53b6ba92..4003458657 100644 --- a/console/src/app/modules/user-grants/user-grants.component.ts +++ b/console/src/app/modules/user-grants/user-grants.component.ts @@ -56,7 +56,7 @@ export class UserGrantsComponent implements OnInit, AfterViewInit { @ViewChild('input') public filter!: MatInput; public projectRoleOptions: Role.AsObject[] = []; - public routerLink: any = ['']; + public routerLink: any = undefined; public loadedId: string = ''; public loadedProjectId: string = ''; diff --git a/console/src/app/modules/warn-dialog/warn-dialog.component.html b/console/src/app/modules/warn-dialog/warn-dialog.component.html index b3ecbd2766..17c21f28b6 100644 --- a/console/src/app/modules/warn-dialog/warn-dialog.component.html +++ b/console/src/app/modules/warn-dialog/warn-dialog.component.html @@ -1,18 +1,18 @@ -{{ data.titleKey | translate : data.titleParam }} +{{ data.titleKey | translate: data.titleParam }}
    -

    {{ data.descriptionKey | translate : data.descriptionParam }}

    +

    {{ data.descriptionKey | translate: data.descriptionParam }}

    {{ data.warnSectionKey | translate }} -

    {{ data.hintKey | translate : { value: data.confirmation } }}

    +

    {{ data.hintKey | translate: { value: data.confirmation } }}

    - {{ data.confirmationKey | translate : { value: data.confirmation } }} + {{ data.confirmationKey | translate: { value: data.confirmation } }}
    diff --git a/console/src/app/modules/warn-dialog/warn-dialog.component.ts b/console/src/app/modules/warn-dialog/warn-dialog.component.ts index 070c9d52fa..701fe8f398 100644 --- a/console/src/app/modules/warn-dialog/warn-dialog.component.ts +++ b/console/src/app/modules/warn-dialog/warn-dialog.component.ts @@ -14,7 +14,10 @@ import { InfoSectionType } from '../info-section/info-section.component'; export class WarnDialogComponent { public confirm: string = ''; InfoSectionType: any = InfoSectionType; - constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any) {} + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + ) {} public closeDialog(): void { this.dialogRef.close(false); diff --git a/console/src/app/pages/actions/actions.component.html b/console/src/app/pages/actions/actions.component.html index b7c3ab3c3e..a951aa70c2 100644 --- a/console/src/app/pages/actions/actions.component.html +++ b/console/src/app/pages/actions/actions.component.html @@ -4,7 +4,7 @@

    {{ 'FLOWS.DESCRIPTION' | translate }}

    {{ 'FLOWS.ACTIONSMAX' | translate : { value: maxActions } }} + >{{ 'FLOWS.ACTIONSMAX' | translate: { value: maxActions } }} diff --git a/console/src/app/pages/actions/actions.component.scss b/console/src/app/pages/actions/actions.component.scss index 5092052ee7..104c37257d 100644 --- a/console/src/app/pages/actions/actions.component.scss +++ b/console/src/app/pages/actions/actions.component.scss @@ -150,7 +150,10 @@ border-radius: 0.5rem; padding: 0 0.5rem; background-color: $primary-color; - box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12); + box-shadow: + 0 5px 5px -3px rgba(0, 0, 0, 0.2), + 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12); i { margin-right: 0.5rem; diff --git a/console/src/app/pages/app-create/app-create.component.html b/console/src/app/pages/app-create/app-create.component.html index ad12e1bc3d..3ea6614d82 100644 --- a/console/src/app/pages/app-create/app-create.component.html +++ b/console/src/app/pages/app-create/app-create.component.html @@ -9,7 +9,7 @@ > -
    +
    -
    - - -
    - {{ domain.domainName }} - - - - {{ 'ORG.DOMAINS.SETPRIMARY' | translate }} - - - - -
    -
    - -
    diff --git a/console/src/app/pages/events/events.component.html b/console/src/app/pages/events/events.component.html index 155c42dbef..acc8c3677a 100644 --- a/console/src/app/pages/events/events.component.html +++ b/console/src/app/pages/events/events.component.html @@ -84,7 +84,7 @@ {{ 'IAM.EVENTS.CREATIONDATE' | translate }} - {{ event?.creationDate | timestampToDate | localizedDate : 'EEE dd. MMM, HH:mm' }} + {{ event?.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }} diff --git a/console/src/app/pages/failed-events/failed-events.component.html b/console/src/app/pages/failed-events/failed-events.component.html index f35650e253..136a41a4df 100644 --- a/console/src/app/pages/failed-events/failed-events.component.html +++ b/console/src/app/pages/failed-events/failed-events.component.html @@ -32,7 +32,7 @@ {{ 'IAM.FAILEDEVENTS.LASTFAILED' | translate }} - {{ event?.lastFailed | timestampToDate | localizedDate : 'EEE dd. MMM, HH:mm' }} + {{ event?.lastFailed | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }} diff --git a/console/src/app/pages/home/home.component.ts b/console/src/app/pages/home/home.component.ts index f834856547..f84e91fc05 100644 --- a/console/src/app/pages/home/home.component.ts +++ b/console/src/app/pages/home/home.component.ts @@ -21,7 +21,11 @@ export class HomeComponent { public dark: boolean = true; - constructor(public authService: GrpcAuthService, breadcrumbService: BreadcrumbService, public themeService: ThemeService) { + constructor( + public authService: GrpcAuthService, + breadcrumbService: BreadcrumbService, + public themeService: ThemeService, + ) { const bread: Breadcrumb = { type: BreadcrumbType.ORG, routerLink: ['/org'], diff --git a/console/src/app/pages/iam-views/iam-views.component.html b/console/src/app/pages/iam-views/iam-views.component.html index 4e8b92fa01..7b2930d03c 100644 --- a/console/src/app/pages/iam-views/iam-views.component.html +++ b/console/src/app/pages/iam-views/iam-views.component.html @@ -22,14 +22,14 @@ {{ 'IAM.VIEWS.EVENTTIMESTAMP' | translate }} - {{ view?.eventTimestamp | timestampToDate | localizedDate : 'EEE dd. MMM, HH:mm' }} + {{ view?.eventTimestamp | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }} {{ 'IAM.VIEWS.LASTSPOOL' | translate }} - {{ view?.lastSuccessfulSpoolerRun | timestampToDate | localizedDate : 'EEE dd. MMM, HH:mm' }} + {{ view?.lastSuccessfulSpoolerRun | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }} diff --git a/console/src/app/pages/iam-views/iam-views.component.ts b/console/src/app/pages/iam-views/iam-views.component.ts index 61af85a051..91913e5cb7 100644 --- a/console/src/app/pages/iam-views/iam-views.component.ts +++ b/console/src/app/pages/iam-views/iam-views.component.ts @@ -23,7 +23,10 @@ export class IamViewsComponent implements AfterViewInit { private loadingSubject: BehaviorSubject = new BehaviorSubject(false); public loading$: Observable = this.loadingSubject.asObservable(); - constructor(private adminService: AdminService, private breadcrumbService: BreadcrumbService) { + constructor( + private adminService: AdminService, + private breadcrumbService: BreadcrumbService, + ) { this.loadViews(); const breadcrumbs = [ diff --git a/console/src/app/pages/instance-settings/instance-settings.component.html b/console/src/app/pages/instance-settings/instance-settings.component.html index 5256753af6..b01323e94f 100644 --- a/console/src/app/pages/instance-settings/instance-settings.component.html +++ b/console/src/app/pages/instance-settings/instance-settings.component.html @@ -8,9 +8,11 @@ - + + + diff --git a/console/src/app/pages/instance-settings/instance-settings.component.ts b/console/src/app/pages/instance-settings/instance-settings.component.ts index ec45aba460..8b7db8b85f 100644 --- a/console/src/app/pages/instance-settings/instance-settings.component.ts +++ b/console/src/app/pages/instance-settings/instance-settings.component.ts @@ -1,10 +1,11 @@ -import { Component, OnDestroy } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute, Params } from '@angular/router'; -import { Subject, takeUntil } from 'rxjs'; +import { Observable, of, Subject, takeUntil } from 'rxjs'; import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum'; import { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component'; import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service'; +import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; import { BRANDING, COMPLEXITY, @@ -27,10 +28,10 @@ import { templateUrl: './instance-settings.component.html', styleUrls: ['./instance-settings.component.scss'], }) -export class InstanceSettingsComponent implements OnDestroy { +export class InstanceSettingsComponent implements OnInit, OnDestroy { public id: string = ''; public PolicyComponentServiceType: any = PolicyComponentServiceType; - public settingsList: SidenavSetting[] = [ + public defaultSettingsList: SidenavSetting[] = [ GENERAL, // notifications // { showWarn: true, ...NOTIFICATIONS }, @@ -53,8 +54,14 @@ export class InstanceSettingsComponent implements OnDestroy { SECURITY, ]; + public settingsList: Observable = of([]); + private destroy$: Subject = new Subject(); - constructor(breadcrumbService: BreadcrumbService, activatedRoute: ActivatedRoute) { + constructor( + breadcrumbService: BreadcrumbService, + activatedRoute: ActivatedRoute, + public authService: GrpcAuthService, + ) { const breadcrumbs = [ new Breadcrumb({ type: BreadcrumbType.INSTANCE, @@ -72,6 +79,10 @@ export class InstanceSettingsComponent implements OnDestroy { }); } + ngOnInit(): void { + this.settingsList = this.authService.isAllowedMapper(this.defaultSettingsList, (setting) => setting.requiredRoles.admin); + } + ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); diff --git a/console/src/app/pages/org-settings/org-settings.component.html b/console/src/app/pages/org-settings/org-settings.component.html index beae1ba352..b55771d68e 100644 --- a/console/src/app/pages/org-settings/org-settings.component.html +++ b/console/src/app/pages/org-settings/org-settings.component.html @@ -8,9 +8,11 @@ - + + + diff --git a/console/src/app/pages/org-settings/org-settings.component.ts b/console/src/app/pages/org-settings/org-settings.component.ts index 04b05cc717..b0914fb7cc 100644 --- a/console/src/app/pages/org-settings/org-settings.component.ts +++ b/console/src/app/pages/org-settings/org-settings.component.ts @@ -1,10 +1,11 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Params } from '@angular/router'; -import { take } from 'rxjs'; +import { Observable, of, take } from 'rxjs'; import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum'; import { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component'; import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service'; +import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; import { BRANDING, COMPLEXITY, @@ -16,6 +17,7 @@ import { MESSAGETEXTS, NOTIFICATION_POLICY, PRIVACYPOLICY, + VERIFIED_DOMAINS, } from '../../modules/settings-list/settings'; @Component({ @@ -23,15 +25,17 @@ import { templateUrl: './org-settings.component.html', styleUrls: ['./org-settings.component.scss'], }) -export class OrgSettingsComponent { +export class OrgSettingsComponent implements OnInit { public id: string = ''; public PolicyComponentServiceType: any = PolicyComponentServiceType; - public settingsList: SidenavSetting[] = [ + + private defaultSettingsList: SidenavSetting[] = [ LOGIN, IDP, COMPLEXITY, LOCKOUT, NOTIFICATION_POLICY, + VERIFIED_DOMAINS, DOMAIN, BRANDING, MESSAGETEXTS, @@ -39,7 +43,13 @@ export class OrgSettingsComponent { PRIVACYPOLICY, ]; - constructor(breadcrumbService: BreadcrumbService, activatedRoute: ActivatedRoute) { + public settingsList: Observable> = of([]); + + constructor( + breadcrumbService: BreadcrumbService, + activatedRoute: ActivatedRoute, + public authService: GrpcAuthService, + ) { const breadcrumbs = [ new Breadcrumb({ type: BreadcrumbType.ORG, @@ -55,4 +65,10 @@ export class OrgSettingsComponent { } }); } + + ngOnInit(): void { + this.settingsList = this.authService + .isAllowedMapper(this.defaultSettingsList, (setting) => setting.requiredRoles.mgmt) + .pipe(take(1)); + } } diff --git a/console/src/app/pages/projects/apps/app-detail/app-detail.component.html b/console/src/app/pages/projects/apps/app-detail/app-detail.component.html index a83b7a7bc8..486090f6e1 100644 --- a/console/src/app/pages/projects/apps/app-detail/app-detail.component.html +++ b/console/src/app/pages/projects/apps/app-detail/app-detail.component.html @@ -401,7 +401,7 @@
    diff --git a/console/src/app/pages/projects/apps/app-detail/auth-method-dialog/auth-method-dialog.component.ts b/console/src/app/pages/projects/apps/app-detail/auth-method-dialog/auth-method-dialog.component.ts index 71a7983f03..8b1436b716 100644 --- a/console/src/app/pages/projects/apps/app-detail/auth-method-dialog/auth-method-dialog.component.ts +++ b/console/src/app/pages/projects/apps/app-detail/auth-method-dialog/auth-method-dialog.component.ts @@ -11,7 +11,10 @@ import { }) export class AuthMethodDialogComponent { public authmethod: string = ''; - constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any) { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + ) { this.authmethod = data.initialAuthMethod; } diff --git a/console/src/app/pages/projects/apps/app-secret-dialog/app-secret-dialog.component.ts b/console/src/app/pages/projects/apps/app-secret-dialog/app-secret-dialog.component.ts index c893cb7c63..2ae0fd94de 100644 --- a/console/src/app/pages/projects/apps/app-secret-dialog/app-secret-dialog.component.ts +++ b/console/src/app/pages/projects/apps/app-secret-dialog/app-secret-dialog.component.ts @@ -11,7 +11,10 @@ import { }) export class AppSecretDialogComponent { public copied: string = ''; - constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any) {} + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + ) {} public closeDialog(): void { this.dialogRef.close(false); diff --git a/console/src/app/pages/projects/apps/redirect-uris/redirect-uris.component.html b/console/src/app/pages/projects/apps/redirect-uris/redirect-uris.component.html index 970dbe3a0f..a0baf9717b 100644 --- a/console/src/app/pages/projects/apps/redirect-uris/redirect-uris.component.html +++ b/console/src/app/pages/projects/apps/redirect-uris/redirect-uris.component.html @@ -15,7 +15,7 @@
    -
    +
    {{ 'PROJECT.ROLE.CREATIONDATE' | translate }} {{ - app.details.creationDate | timestampToDate | localizedDate : 'dd. MMM, HH:mm' + app.details.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }} @@ -79,7 +79,7 @@ {{ 'PROJECT.ROLE.CHANGEDATE' | translate }} {{ - app.details.changeDate | timestampToDate | localizedDate : 'dd. MMM, HH:mm' + app.details.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }} diff --git a/console/src/app/pages/projects/owned-projects/project-grants/project-grants-datasource.ts b/console/src/app/pages/projects/owned-projects/project-grants/project-grants-datasource.ts index 749361eee4..b56c763df9 100644 --- a/console/src/app/pages/projects/owned-projects/project-grants/project-grants-datasource.ts +++ b/console/src/app/pages/projects/owned-projects/project-grants/project-grants-datasource.ts @@ -17,7 +17,10 @@ export class ProjectGrantsDataSource extends DataSource private loadingSubject: BehaviorSubject = new BehaviorSubject(false); public loading$: Observable = this.loadingSubject.asObservable(); - constructor(private mgmtService: ManagementService, private toast: ToastService) { + constructor( + private mgmtService: ManagementService, + private toast: ToastService, + ) { super(); } diff --git a/console/src/app/pages/projects/owned-projects/project-grants/project-grants.component.html b/console/src/app/pages/projects/owned-projects/project-grants/project-grants.component.html index b0a9321a30..bcf572400d 100644 --- a/console/src/app/pages/projects/owned-projects/project-grants/project-grants.component.html +++ b/console/src/app/pages/projects/owned-projects/project-grants/project-grants.component.html @@ -71,7 +71,7 @@ *matCellDef="let grant" [routerLink]="['/projects', grant.projectId, 'projectgrants', grant.grantId]" > - {{ grant.details.creationDate | timestampToDate | localizedDate : 'dd. MMM, HH:mm' }} + {{ grant.details.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }} @@ -83,7 +83,7 @@ *matCellDef="let grant" [routerLink]="['/projects', grant.projectId, 'projectgrants', grant.grantId]" > - {{ grant.details.changeDate | timestampToDate | localizedDate : 'dd. MMM, HH:mm' }} + {{ grant.details.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }} diff --git a/console/src/app/pages/projects/project-grid/project-grid.component.html b/console/src/app/pages/projects/project-grid/project-grid.component.html index f9f8178bc7..df618192ed 100644 --- a/console/src/app/pages/projects/project-grid/project-grid.component.html +++ b/console/src/app/pages/projects/project-grid/project-grid.component.html @@ -14,7 +14,7 @@
    {{ 'PROJECT.PAGES.LASTMODIFIED' | translate }} - {{ item.details.changeDate | timestampToDate | localizedDate : 'EEE dd. MMM, HH:mm' }}
    {{ $any(item).name }} @@ -31,7 +31,7 @@ {{ 'PROJECT.PAGES.CREATEDON' | translate }} - {{ item.details.creationDate | timestampToDate | localizedDate : 'EEE dd. MMM, HH:mm' }}
    @@ -52,7 +52,7 @@
    {{ 'PROJECT.PAGES.LASTMODIFIED' | translate }} - {{ item.details.changeDate | timestampToDate | localizedDate : 'EEE dd. MMM, HH:mm' }}
    {{ $any(item).name }} @@ -70,7 +70,7 @@ {{ $any(item).projectOwnerName }} {{ 'PROJECT.PAGES.CREATEDON' | translate }} - {{ item.details.creationDate | timestampToDate | localizedDate : 'EEE dd. MMM, HH:mm' }}
    diff --git a/console/src/app/pages/projects/project-list/project-list.component.html b/console/src/app/pages/projects/project-list/project-list.component.html index da82c60965..695868c881 100644 --- a/console/src/app/pages/projects/project-list/project-list.component.html +++ b/console/src/app/pages/projects/project-list/project-list.component.html @@ -111,7 +111,7 @@ *matCellDef="let project" > {{ - project.details.creationDate | timestampToDate | localizedDate : 'EEE dd. MMM, HH:mm' + project.details.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }} @@ -129,7 +129,7 @@ *matCellDef="let project" > {{ - project.details.changeDate | timestampToDate | localizedDate : 'EEE dd. MMM, HH:mm' + project.details.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }} diff --git a/console/src/app/pages/user-grant-create/user-grant-create.component.html b/console/src/app/pages/user-grant-create/user-grant-create.component.html index 489adf3a87..e2ebbf9583 100644 --- a/console/src/app/pages/user-grant-create/user-grant-create.component.html +++ b/console/src/app/pages/user-grant-create/user-grant-create.component.html @@ -6,7 +6,7 @@ >

    - {{ 'PROJECT.GRANT.CREATE.ORG_DESCRIPTION' | translate : org }} + {{ 'PROJECT.GRANT.CREATE.ORG_DESCRIPTION' | translate: org }}
    {{ 'PROJECT.GRANT.CREATE.ORG_DESCRIPTION_DESC' | translate }}

    diff --git a/console/src/app/pages/users/user-detail/auth-user-detail/auth-passwordless/auth-passwordless.component.ts b/console/src/app/pages/users/user-detail/auth-user-detail/auth-passwordless/auth-passwordless.component.ts index 4ccb85902a..b3f5ac95ed 100644 --- a/console/src/app/pages/users/user-detail/auth-user-detail/auth-passwordless/auth-passwordless.component.ts +++ b/console/src/app/pages/users/user-detail/auth-user-detail/auth-passwordless/auth-passwordless.component.ts @@ -39,7 +39,11 @@ export class AuthPasswordlessComponent implements OnInit, OnDestroy { public AuthFactorState: any = AuthFactorState; public error: string = ''; - constructor(private service: GrpcAuthService, private toast: ToastService, private dialog: MatDialog) {} + constructor( + private service: GrpcAuthService, + private toast: ToastService, + private dialog: MatDialog, + ) {} public ngOnInit(): void { this.getPasswordless(); diff --git a/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-mfa/auth-user-mfa.component.ts b/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-mfa/auth-user-mfa.component.ts index a4fbcaaa89..6a81838c44 100644 --- a/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-mfa/auth-user-mfa.component.ts +++ b/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-mfa/auth-user-mfa.component.ts @@ -42,7 +42,11 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy { public otpSmsDisabled$ = new BehaviorSubject(true); public otpEmailDisabled$ = new BehaviorSubject(true); - constructor(private service: GrpcAuthService, private toast: ToastService, private dialog: MatDialog) {} + constructor( + private service: GrpcAuthService, + private toast: ToastService, + private dialog: MatDialog, + ) {} public ngOnInit(): void { this.getMFAs(); diff --git a/console/src/app/pages/users/user-detail/auth-user-detail/code-dialog/code-dialog.component.ts b/console/src/app/pages/users/user-detail/auth-user-detail/code-dialog/code-dialog.component.ts index c0e13f8771..d09a8f9da3 100644 --- a/console/src/app/pages/users/user-detail/auth-user-detail/code-dialog/code-dialog.component.ts +++ b/console/src/app/pages/users/user-detail/auth-user-detail/code-dialog/code-dialog.component.ts @@ -11,7 +11,10 @@ import { }) export class CodeDialogComponent { public code: string = ''; - constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any) {} + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + ) {} closeDialog(code: string = ''): void { this.dialogRef.close(code); diff --git a/console/src/app/pages/users/user-detail/auth-user-detail/resend-email-dialog/resend-email-dialog.component.ts b/console/src/app/pages/users/user-detail/auth-user-detail/resend-email-dialog/resend-email-dialog.component.ts index c0f4ed7bb5..15bd7b43eb 100644 --- a/console/src/app/pages/users/user-detail/auth-user-detail/resend-email-dialog/resend-email-dialog.component.ts +++ b/console/src/app/pages/users/user-detail/auth-user-detail/resend-email-dialog/resend-email-dialog.component.ts @@ -11,7 +11,10 @@ import { }) export class ResendEmailDialogComponent { public email: string = ''; - constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any) { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + ) { if (data.email) { this.email = data.email; } diff --git a/console/src/app/pages/users/user-detail/contact/contact.component.ts b/console/src/app/pages/users/user-detail/contact/contact.component.ts index 659a6db4b5..eab184c203 100644 --- a/console/src/app/pages/users/user-detail/contact/contact.component.ts +++ b/console/src/app/pages/users/user-detail/contact/contact.component.ts @@ -26,7 +26,10 @@ export class ContactComponent { public UserState: any = UserState; public EditDialogType: any = EditDialogType; - constructor(private dialog: MatDialog, private authService: GrpcAuthService) {} + constructor( + private dialog: MatDialog, + private authService: GrpcAuthService, + ) {} async emitDeletePhone(): Promise { const { resultList } = await this.authService.listMyMultiFactors(); diff --git a/console/src/app/pages/users/user-detail/detail-form/detail-form.component.ts b/console/src/app/pages/users/user-detail/detail-form/detail-form.component.ts index 6b0ca90808..9c5b4607b0 100644 --- a/console/src/app/pages/users/user-detail/detail-form/detail-form.component.ts +++ b/console/src/app/pages/users/user-detail/detail-form/detail-form.component.ts @@ -29,7 +29,10 @@ export class DetailFormComponent implements OnDestroy, OnChanges { private sub: Subscription = new Subscription(); - constructor(private fb: UntypedFormBuilder, private dialog: MatDialog) { + constructor( + private fb: UntypedFormBuilder, + private dialog: MatDialog, + ) { this.profileForm = this.fb.group({ userName: [{ value: '', disabled: true }, [requiredValidator]], firstName: [{ value: '', disabled: this.disabled }, requiredValidator], diff --git a/console/src/app/pages/users/user-detail/external-idps/external-idps.component.ts b/console/src/app/pages/users/user-detail/external-idps/external-idps.component.ts index b6513e0f8c..e80b1ee453 100644 --- a/console/src/app/pages/users/user-detail/external-idps/external-idps.component.ts +++ b/console/src/app/pages/users/user-detail/external-idps/external-idps.component.ts @@ -35,7 +35,10 @@ export class ExternalIdpsComponent implements OnInit, OnDestroy { 'actions', ]; - constructor(private toast: ToastService, private dialog: MatDialog) {} + constructor( + private toast: ToastService, + private dialog: MatDialog, + ) {} ngOnInit(): void { this.getData(10, 0); diff --git a/console/src/app/pages/users/user-detail/user-detail/machine-secret-dialog/machine-secret-dialog.component.ts b/console/src/app/pages/users/user-detail/user-detail/machine-secret-dialog/machine-secret-dialog.component.ts index 2faa99ccdc..2c560f3d05 100644 --- a/console/src/app/pages/users/user-detail/user-detail/machine-secret-dialog/machine-secret-dialog.component.ts +++ b/console/src/app/pages/users/user-detail/user-detail/machine-secret-dialog/machine-secret-dialog.component.ts @@ -11,7 +11,10 @@ import { }) export class MachineSecretDialogComponent { public copied: string = ''; - constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any) {} + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + ) {} public closeDialog(): void { this.dialogRef.close(false); diff --git a/console/src/app/pages/users/user-detail/user-detail/passwordless/passwordless.component.ts b/console/src/app/pages/users/user-detail/user-detail/passwordless/passwordless.component.ts index adf9d107d9..33b24de024 100644 --- a/console/src/app/pages/users/user-detail/user-detail/passwordless/passwordless.component.ts +++ b/console/src/app/pages/users/user-detail/user-detail/passwordless/passwordless.component.ts @@ -37,7 +37,11 @@ export class PasswordlessComponent implements OnInit, OnDestroy { public AuthFactorState: any = AuthFactorState; public error: string = ''; - constructor(private service: ManagementService, private toast: ToastService, private dialog: MatDialog) {} + constructor( + private service: ManagementService, + private toast: ToastService, + private dialog: MatDialog, + ) {} public ngOnInit(): void { this.getPasswordless(); diff --git a/console/src/app/pages/users/user-detail/user-detail/user-detail.component.html b/console/src/app/pages/users/user-detail/user-detail/user-detail.component.html index b8d6e10dae..f59cc6dc7c 100644 --- a/console/src/app/pages/users/user-detail/user-detail/user-detail.component.html +++ b/console/src/app/pages/users/user-detail/user-detail/user-detail.component.html @@ -78,7 +78,7 @@ - + { const { type } = params; if (type && type === 'human') { diff --git a/console/src/app/pages/users/user-list/user-table/user-table.component.html b/console/src/app/pages/users/user-list/user-table/user-table.component.html index d51fdf2197..4bfa8472bb 100644 --- a/console/src/app/pages/users/user-list/user-table/user-table.component.html +++ b/console/src/app/pages/users/user-list/user-table/user-table.component.html @@ -124,6 +124,20 @@ + + + {{ 'USER.PROFILE.PREFERREDLOGINNAME' | translate }} + + + {{ user.preferredLoginName }} + + + {{ 'USER.TABLE.CREATIONDATE' | translate }} - {{ user.details.creationDate | timestampToDate | localizedDate : 'regular' }} + {{ user.details.creationDate | timestampToDate | localizedDate: 'regular' }} {{ 'USER.TABLE.CHANGEDATE' | translate }} - {{ user.details.changeDate | timestampToDate | localizedDate : 'regular' }} + {{ user.details.changeDate | timestampToDate | localizedDate: 'regular' }} diff --git a/console/src/app/pages/users/user-list/user-table/user-table.component.ts b/console/src/app/pages/users/user-list/user-table/user-table.component.ts index 415fd6110e..3406d931b8 100644 --- a/console/src/app/pages/users/user-list/user-table/user-table.component.ts +++ b/console/src/app/pages/users/user-list/user-table/user-table.component.ts @@ -51,7 +51,7 @@ export class UserTableComponent implements OnInit { @Input() public displayedColumnsHuman: string[] = [ 'select', 'displayName', - 'username', + 'preferredLoginName', 'email', 'state', 'creationDate', @@ -194,6 +194,10 @@ export class UserTableComponent implements OnInit { case 'username': sortingField = UserFieldName.USER_FIELD_NAME_USER_NAME; break; + case 'preferredLoginName': + // TODO: replace with preferred username sorting once implemented + sortingField = UserFieldName.USER_FIELD_NAME_USER_NAME; + break; case 'email': sortingField = UserFieldName.USER_FIELD_NAME_EMAIL; break; diff --git a/console/src/app/services/admin.service.ts b/console/src/app/services/admin.service.ts index a4217e3ab8..7929e62bc7 100644 --- a/console/src/app/services/admin.service.ts +++ b/console/src/app/services/admin.service.ts @@ -390,7 +390,10 @@ export class AdminService { public progressTotal: BehaviorSubject = new BehaviorSubject(0); public progressAllDone: BehaviorSubject = new BehaviorSubject(true); - constructor(private readonly grpcService: GrpcService, private storageService: StorageService) { + constructor( + private readonly grpcService: GrpcService, + private storageService: StorageService, + ) { this.progressEvents$.subscribe(this.progressEvents); this.hideOnboarding = diff --git a/console/src/app/services/asset.service.ts b/console/src/app/services/asset.service.ts index 320150ff46..75427e7f63 100644 --- a/console/src/app/services/asset.service.ts +++ b/console/src/app/services/asset.service.ts @@ -71,7 +71,11 @@ export const ENDPOINT = { }) export class AssetService { private accessToken: string = ''; - constructor(private envService: EnvironmentService, private http: HttpClient, private storageService: StorageService) { + constructor( + private envService: EnvironmentService, + private http: HttpClient, + private storageService: StorageService, + ) { const aT = this.storageService.getItem(accessTokenStorageKey); if (aT) { this.accessToken = aT; diff --git a/console/src/app/services/authentication.service.ts b/console/src/app/services/authentication.service.ts index c5de101e68..c53047c047 100644 --- a/console/src/app/services/authentication.service.ts +++ b/console/src/app/services/authentication.service.ts @@ -12,7 +12,10 @@ export class AuthenticationService { private _authenticated: boolean = false; private readonly _authenticationChanged: BehaviorSubject = new BehaviorSubject(this.authenticated); - constructor(private oauthService: OAuthService, private statehandler: StatehandlerService) {} + constructor( + private oauthService: OAuthService, + private statehandler: StatehandlerService, + ) {} public initConfig(data: AuthConfig): void { this.authConfig = data; diff --git a/console/src/app/services/environment.service.ts b/console/src/app/services/environment.service.ts index b2dbc59515..1fcc40ba36 100644 --- a/console/src/app/services/environment.service.ts +++ b/console/src/app/services/environment.service.ts @@ -36,7 +36,10 @@ export class EnvironmentService { private environment$: Observable; private wellKnown$: Observable; - constructor(private http: HttpClient, private exhaustedSvc: ExhaustedService) { + constructor( + private http: HttpClient, + private exhaustedSvc: ExhaustedService, + ) { this.environment$ = this.createEnvironment(); this.wellKnown$ = this.createWellKnown(this.environment$); } diff --git a/console/src/app/services/grpc-auth.service.ts b/console/src/app/services/grpc-auth.service.ts index c51bee4c7b..18fc7f928d 100644 --- a/console/src/app/services/grpc-auth.service.ts +++ b/console/src/app/services/grpc-auth.service.ts @@ -309,10 +309,7 @@ export class GrpcAuthService { filter(([hL, p]) => { return hL === true && !!p.length; }), - map(([_, zroles]) => { - const what = this.hasRoles(zroles, roles, requiresAll); - return what; - }), + map(([_, zroles]) => this.hasRoles(zroles, roles, requiresAll)), distinctUntilChanged(), ); } else { @@ -320,6 +317,31 @@ export class GrpcAuthService { } } + /** + * filters objects based on roles + * @param objects array of objects + * @param mapper mapping function which maps to a string[] or Regexp[] of roles + * @param requiresAll wheter all, or just a single roles is required to fulfill + */ + public isAllowedMapper( + objects: T[], + mapper: (attr: any) => string[] | RegExp[], + requiresAll: boolean = false, + ): Observable { + return this.fetchedZitadelPermissions.pipe( + withLatestFrom(this.zitadelPermissions), + filter(([hL, p]) => { + return hL === true && !!p.length; + }), + map(([_, zroles]) => { + return objects.filter((obj) => { + const roles = mapper(obj); + return this.hasRoles(zroles, roles, requiresAll); + }); + }), + ); + } + /** * returns true if user has one of the provided roles * @param userRoles roles of the user diff --git a/console/src/app/services/interceptors/exhausted.grpc.interceptor.ts b/console/src/app/services/interceptors/exhausted.grpc.interceptor.ts index 7486b525ca..446e7aa8dd 100644 --- a/console/src/app/services/interceptors/exhausted.grpc.interceptor.ts +++ b/console/src/app/services/interceptors/exhausted.grpc.interceptor.ts @@ -8,7 +8,10 @@ import { ExhaustedService } from '../exhausted.service'; */ @Injectable({ providedIn: 'root' }) export class ExhaustedGrpcInterceptor implements UnaryInterceptor { - constructor(private exhaustedSvc: ExhaustedService, private envSvc: EnvironmentService) {} + constructor( + private exhaustedSvc: ExhaustedService, + private envSvc: EnvironmentService, + ) {} public async intercept( request: Request, diff --git a/console/src/app/services/interceptors/exhausted.http.interceptor.ts b/console/src/app/services/interceptors/exhausted.http.interceptor.ts index 898f9ccd98..29248616fe 100644 --- a/console/src/app/services/interceptors/exhausted.http.interceptor.ts +++ b/console/src/app/services/interceptors/exhausted.http.interceptor.ts @@ -9,7 +9,10 @@ import { ExhaustedService } from '../exhausted.service'; */ @Injectable() export class ExhaustedHttpInterceptor implements HttpInterceptor { - constructor(private exhaustedSvc: ExhaustedService, private envSvc: EnvironmentService) {} + constructor( + private exhaustedSvc: ExhaustedService, + private envSvc: EnvironmentService, + ) {} intercept(req: HttpRequest, next: HttpHandler): Observable> { return next.handle(req).pipe( diff --git a/console/src/app/services/navigation.service.ts b/console/src/app/services/navigation.service.ts index f237668d79..83c5e1c99d 100644 --- a/console/src/app/services/navigation.service.ts +++ b/console/src/app/services/navigation.service.ts @@ -8,7 +8,10 @@ import { NavigationEnd, Router } from '@angular/router'; export class NavigationService { private history: string[] = []; - constructor(private router: Router, private location: Location) { + constructor( + private router: Router, + private location: Location, + ) { this.router.events.subscribe((event) => { if (event instanceof NavigationEnd) { this.history.push(event.urlAfterRedirects); diff --git a/console/src/app/services/overlay/overlay-workflow.service.ts b/console/src/app/services/overlay/overlay-workflow.service.ts index 4c40527e14..f641ea294d 100644 --- a/console/src/app/services/overlay/overlay-workflow.service.ts +++ b/console/src/app/services/overlay/overlay-workflow.service.ts @@ -39,7 +39,11 @@ export class OverlayWorkflowService implements OnDestroy { public openRef: CnslOverlayRef | null = null; public highlightedIds: { [id: string]: number } = {}; public callback: Function | null = null; - constructor(private mediaMatcher: MediaMatcher, overlayService: OverlayService, private authService: GrpcAuthService) { + constructor( + private mediaMatcher: MediaMatcher, + overlayService: OverlayService, + private authService: GrpcAuthService, + ) { this.currentWorkflow$.pipe(takeUntil(this.destroy$)).subscribe((workflow) => { if (this.openRef) { this.openRef.close(); diff --git a/console/src/app/services/overlay/overlay.service.ts b/console/src/app/services/overlay/overlay.service.ts index 0215010fc6..d680703534 100644 --- a/console/src/app/services/overlay/overlay.service.ts +++ b/console/src/app/services/overlay/overlay.service.ts @@ -20,7 +20,10 @@ const DEFAULT_CONFIG: Partial = { providedIn: 'root', }) export class OverlayService { - constructor(private overlay: Overlay, private injector: Injector) {} + constructor( + private overlay: Overlay, + private injector: Injector, + ) {} public open(overlay: CnslOverlay) { const dialogConfig: InfoOverlayConfig = { ...DEFAULT_CONFIG, ...overlay }; diff --git a/console/src/app/services/statehandler/statehandler-processor.service.ts b/console/src/app/services/statehandler/statehandler-processor.service.ts index 5576415b15..5d15a71f27 100644 --- a/console/src/app/services/statehandler/statehandler-processor.service.ts +++ b/console/src/app/services/statehandler/statehandler-processor.service.ts @@ -10,7 +10,10 @@ export abstract class StatehandlerProcessorService { @Injectable() export class StatehandlerProcessorServiceImpl implements StatehandlerProcessorService { - constructor(private location: Location, private router: Router) {} + constructor( + private location: Location, + private router: Router, + ) {} public createState(url: string): string { const externalUrl = url; diff --git a/console/src/app/services/statehandler/statehandler.service.ts b/console/src/app/services/statehandler/statehandler.service.ts index df9253e4d2..fa034e56fa 100644 --- a/console/src/app/services/statehandler/statehandler.service.ts +++ b/console/src/app/services/statehandler/statehandler.service.ts @@ -16,7 +16,11 @@ export class StatehandlerServiceImpl implements StatehandlerService, OnDestroy { private events?: Observable; private unsubscribe$: Subject = new Subject(); - constructor(oauthService: OAuthService, private injector: Injector, private processor: StatehandlerProcessorService) { + constructor( + oauthService: OAuthService, + private injector: Injector, + private processor: StatehandlerProcessorService, + ) { oauthService.events .pipe( filter((event) => event.type === 'token_received'), diff --git a/console/src/app/services/toast.service.ts b/console/src/app/services/toast.service.ts index e9d37a588e..b5ca742814 100644 --- a/console/src/app/services/toast.service.ts +++ b/console/src/app/services/toast.service.ts @@ -15,7 +15,10 @@ export class ToastService { horizontalPosition: MatSnackBarHorizontalPosition = 'end'; verticalPosition: MatSnackBarVerticalPosition = 'top'; - constructor(private snackBar: MatSnackBar, private translate: TranslateService) {} + constructor( + private snackBar: MatSnackBar, + private translate: TranslateService, + ) {} public showInfo(message: string, i18nkey: boolean = false): void { if (i18nkey) { diff --git a/console/src/app/services/update.service.ts b/console/src/app/services/update.service.ts index e1c22e7bc1..a6a29b6a55 100644 --- a/console/src/app/services/update.service.ts +++ b/console/src/app/services/update.service.ts @@ -6,7 +6,10 @@ import { SwUpdate } from '@angular/service-worker'; providedIn: 'root', }) export class UpdateService { - constructor(private swUpdate: SwUpdate, snackbar: MatSnackBar) { + constructor( + private swUpdate: SwUpdate, + snackbar: MatSnackBar, + ) { this.swUpdate.available.subscribe((evt) => { const snack = snackbar.open('Update Available', 'Reload'); diff --git a/console/src/assets/i18n/bg.json b/console/src/assets/i18n/bg.json index 01a19176c3..33f53c9c56 100644 --- a/console/src/assets/i18n/bg.json +++ b/console/src/assets/i18n/bg.json @@ -95,7 +95,6 @@ "EVENTS": "Събития", "FAILEDEVENTS": "Неуспешни събития", "ORGANIZATION": "Организация", - "DOMAINS": "Домейни", "PROJECT": "Проекти", "PROJECTOVERVIEW": "Преглед", "PROJECTGRANTS": "Грантове", @@ -489,6 +488,7 @@ "LASTNAME": "Фамилия", "NICKNAME": "Псевдоним", "DISPLAYNAME": "Екранно име", + "PREFERREDLOGINNAME": "Предпочитано име за вход", "PREFERRED_LANGUAGE": "език", "GENDER": "Пол", "PASSWORD": "Парола", @@ -789,7 +789,7 @@ }, "PAGES": { "STATE": "Статус", - "DOMAINLIST": "Домейни" + "DOMAINLIST": "Лични домейни" }, "STATE": { "0": "Неуточнено", @@ -946,7 +946,7 @@ }, "DOMAINS": { "NEW": "Добавете домейн", - "TITLE": "Домейни", + "TITLE": "Проверени домейни", "DESCRIPTION": "Конфигурирайте вашите домейни. ", "SETPRIMARY": "Задайте като основен", "DELETE": { @@ -1016,6 +1016,7 @@ "NOTIFICATIONS_DESC": "Настройки за SMTP и SMS", "MESSAGETEXTS": "Текстове на съобщения", "IDP": "Доставчици на идентичност", + "VERIFIED_DOMAINS": "Проверени домейни", "DOMAIN": "Настройки на домейна", "LOGINTEXTS": "Текстове на интерфейса за влизане", "BRANDING": "Брандиране", @@ -1344,7 +1345,7 @@ "MAXAGEDAYS": "Максимална възраст в дни", "USERLOGINMUSTBEDOMAIN": "Добавяне на домейн на организация като суфикс към имената за вход", "USERLOGINMUSTBEDOMAIN_DESCRIPTION": "Ако активирате тази настройка, всички имена за вход ще имат суфикс с домейна на организацията. ", - "VALIDATEORGDOMAINS": "Валидиране на организационни домейни", + "VALIDATEORGDOMAINS": "Верификация на домейна на организацията е необходима (DNS или HTTP предизвикателство)", "SMTPSENDERADDRESSMATCHESINSTANCEDOMAIN": "SMTP адресът на изпращача съвпада с домейна на екземпляра", "ALLOWUSERNAMEPASSWORD": "Потребителско име Паролата е разрешена", "ALLOWEXTERNALIDP": "Допуска се външен IDP", diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index 4d70add212..9e2b39c8de 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -95,7 +95,6 @@ "EVENTS": "Events", "FAILEDEVENTS": "Fehlerhafte Events", "ORGANIZATION": "Organisation", - "DOMAINS": "Domänen", "PROJECT": "Projekte", "PROJECTOVERVIEW": "Übersicht", "PROJECTGRANTS": "Berechtigte Organisationen", @@ -495,6 +494,7 @@ "LASTNAME": "Nachname", "NICKNAME": "Spitzname", "DISPLAYNAME": "Anzeigename", + "PREFERREDLOGINNAME": "Bevorzugter Anmeldename", "PREFERRED_LANGUAGE": "Sprache", "GENDER": "Geschlecht", "PASSWORD": "Passwort", @@ -795,7 +795,7 @@ }, "PAGES": { "STATE": "Status", - "DOMAINLIST": "Domains" + "DOMAINLIST": "Custom Domains" }, "STATE": { "0": "Unspezifisch", @@ -952,16 +952,16 @@ }, "DOMAINS": { "NEW": "Domain hinzufügen", - "TITLE": "Domänen", - "DESCRIPTION": "Konfiguriere die Domains, mit denen sich Deine Benutzer anmelden können.", + "TITLE": "Verifizierte Domains", + "DESCRIPTION": "Konfiguriere die Domains, die für Domain discovery und als Suffix für die Benutzer verwendet werden können.", "SETPRIMARY": "Primäre Domain setzen", "DELETE": { "TITLE": "Domain löschen?", - "DESCRIPTION": "Du bist im Begriff, eine Domain aus Deiner Organisation zu löschen. Deine Benutzer können diese nach dem Löschen nicht mehr für den Login nutzen." + "DESCRIPTION": "Du bist im Begriff, eine Domain aus deiner Organisation zu löschen." }, "ADD": { "TITLE": "Domain hinzufügen", - "DESCRIPTION": "Du bist im Begriff, Deiner Organisation eine Domain hinzuzufügen. Deine Benutzer können diese nach der erfolgreichen Ausführung für den Login nutzen." + "DESCRIPTION": "Du bist im Begriff, Deiner Organisation eine Domain hinzuzufügen. Die Domain kann für Domain discovery genutzt werden und als Suffix für deine Benutzernamen." } }, "STATE": { @@ -1022,12 +1022,13 @@ "NOTIFICATIONS_DESC": "SMTP und SMS Einstellungen", "MESSAGETEXTS": "Benachrichtigungstexte", "IDP": "Identitätsanbieter", + "VERIFIED_DOMAINS": "Verifizierte Domains", "DOMAIN": "Domain Einstellungen", "LOGINTEXTS": "Login Interface Texte", "BRANDING": "Branding", "PRIVACYPOLICY": "Datenschutzrichtlinie", "OIDC": "OIDC Token Lifetime und Expiration", - "SECRETS": "Secret Erscheinungsbild", + "SECRETS": "Secret Generator", "SECURITY": "Sicherheitseinstellungen" }, "GROUPS": { @@ -1104,7 +1105,7 @@ "INDAYS": "Tage" }, "SECRETS": { - "TITLE": "Secret Erscheinungsbild", + "TITLE": "Passwort Generator", "TYPES": "Schlüsseltypen", "TYPE": { "1": "Email Initialisierungscode", @@ -1116,7 +1117,7 @@ "7": "One Time Password (OTP) - SMS", "8": "One Time Password (OTP) - Email" }, - "ADDGENERATOR": "Secret Erscheinungsbild definieren", + "ADDGENERATOR": "Passwort Generator definieren", "GENERATORTYPE": "Typ", "EXPIRY": "Ablauf (in Stunden)", "INCLUDEDIGITS": "Enthält Zahlen", @@ -1350,7 +1351,7 @@ "MAXAGEDAYS": "Maximale Gültigkeit in Tagen", "USERLOGINMUSTBEDOMAIN": "Organisationsdomain dem Loginname hinzufügen", "USERLOGINMUSTBEDOMAIN_DESCRIPTION": "If you enable this setting, all loginnames will be suffixed with the organization domain. If this settings is disabled, you have to ensure that usernames are unique over all organizations.", - "VALIDATEORGDOMAINS": "Org Domains validieren", + "VALIDATEORGDOMAINS": "Verifizierung des Organisations Domain erforderlich (DNS- oder HTTP-Herausforderung)", "SMTPSENDERADDRESSMATCHESINSTANCEDOMAIN": "SMTP Sender Adresse entspricht Instanzdomain", "ALLOWUSERNAMEPASSWORD": "Benutzername Passwort erlaubt", "ALLOWEXTERNALIDP": "Externer IDP erlaubt", diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index 389461c832..ac804fee34 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -95,7 +95,6 @@ "EVENTS": "Events", "FAILEDEVENTS": "Failed Events", "ORGANIZATION": "Organization", - "DOMAINS": "Domains", "PROJECT": "Projects", "PROJECTOVERVIEW": "Overview", "PROJECTGRANTS": "Grants", @@ -496,6 +495,7 @@ "LASTNAME": "Family Name", "NICKNAME": "Nickname", "DISPLAYNAME": "Display Name", + "PREFERREDLOGINNAME": "Preferred login name", "PREFERRED_LANGUAGE": "Language", "GENDER": "Gender", "PASSWORD": "Password", @@ -796,7 +796,7 @@ }, "PAGES": { "STATE": "Status", - "DOMAINLIST": "Domains" + "DOMAINLIST": "Custom Domains" }, "STATE": { "0": "Unspecified", @@ -953,16 +953,16 @@ }, "DOMAINS": { "NEW": "Add Domain", - "TITLE": "Domains", - "DESCRIPTION": "Configure your domains. This domain can be used to log in with your users.", + "TITLE": "Verified domains", + "DESCRIPTION": "Configure your organization domains. This domain can be used for domain discovery and username suffixing.", "SETPRIMARY": "Set as Primary", "DELETE": { "TITLE": "Delete Domain", - "DESCRIPTION": "You are about to delete one of your domains. Note that your users can no longer use this domain for their login." + "DESCRIPTION": "You are about to delete one of your domains." }, "ADD": { "TITLE": "Add Domain", - "DESCRIPTION": "You are about to add a domain for your organization. After successful process, you users will be able to use the domain for their login." + "DESCRIPTION": "You are about to add a domain for your organization. After successful process, the domain can be used for domain discovery and as suffix for your users." } }, "STATE": { @@ -1023,12 +1023,13 @@ "NOTIFICATIONS_DESC": "SMTP and SMS Settings", "MESSAGETEXTS": "Message Texts", "IDP": "Identity Providers", + "VERIFIED_DOMAINS": "Verified domains", "DOMAIN": "Domain settings", "LOGINTEXTS": "Login Interface Texts", "BRANDING": "Branding", "PRIVACYPOLICY": "Privacy Policy", "OIDC": "OIDC Token lifetime and expiration", - "SECRETS": "Secret Appearance", + "SECRETS": "Secret Generator", "SECURITY": "Security settings" }, "GROUPS": { @@ -1105,7 +1106,7 @@ "INDAYS": "Days" }, "SECRETS": { - "TITLE": "Secret Appearance", + "TITLE": "Secret Generator", "TYPES": "Secret Types", "TYPE": { "1": "Initialization Mail", @@ -1117,7 +1118,7 @@ "7": "One Time Password (OTP) - SMS", "8": "One Time Password (OTP) - Email" }, - "ADDGENERATOR": "Define Secret Appearance", + "ADDGENERATOR": "Define Secret Generator", "GENERATORTYPE": "Type", "EXPIRY": "Expiration (in hours)", "INCLUDEDIGITS": "Include Numbers", @@ -1351,7 +1352,7 @@ "MAXAGEDAYS": "Max Age in days", "USERLOGINMUSTBEDOMAIN": "Add organization domain as suffix to loginnames", "USERLOGINMUSTBEDOMAIN_DESCRIPTION": "If you enable this setting, all loginnames will be suffixed with the organization domain. If this settings is disabled, you have to ensure that usernames are unique over all organizations.", - "VALIDATEORGDOMAINS": "Validate Org domains", + "VALIDATEORGDOMAINS": "Organization domain verification required (DNS or HTTP challenge)", "SMTPSENDERADDRESSMATCHESINSTANCEDOMAIN": "SMTP Sender Address matches Instance Domain", "ALLOWUSERNAMEPASSWORD": "Username Password allowed", "ALLOWEXTERNALIDP": "External IDP allowed", diff --git a/console/src/assets/i18n/es.json b/console/src/assets/i18n/es.json index 1aaba364c8..bbb14c0faf 100644 --- a/console/src/assets/i18n/es.json +++ b/console/src/assets/i18n/es.json @@ -95,7 +95,6 @@ "EVENTS": "Eventos", "FAILEDEVENTS": "Eventos fallidos", "ORGANIZATION": "Organización", - "DOMAINS": "Dominios", "PROJECT": "Proyectos", "PROJECTOVERVIEW": "Resumen", "PROJECTGRANTS": "Concesiones", @@ -496,6 +495,7 @@ "LASTNAME": "Apellidos", "NICKNAME": "Apodo", "DISPLAYNAME": "Nombre mostrado", + "PREFERREDLOGINNAME": "Nombre de inicio de sesión preferido", "PREFERRED_LANGUAGE": "Idioma", "GENDER": "Género", "PASSWORD": "Contraseña", @@ -796,7 +796,7 @@ }, "PAGES": { "STATE": "Estado", - "DOMAINLIST": "Dominios" + "DOMAINLIST": "Dominios personalizados" }, "STATE": { "0": "No especificado", @@ -953,7 +953,7 @@ }, "DOMAINS": { "NEW": "Añadir dominio", - "TITLE": "Dominios", + "TITLE": "Dominios verificados", "DESCRIPTION": "Configura tus dominios. Este dominio puede usarse para iniciar sesión con tus usuarios.", "SETPRIMARY": "Establecer como primario", "DELETE": { @@ -1023,6 +1023,7 @@ "NOTIFICATIONS_DESC": "Ajustes SMTP y SMS", "MESSAGETEXTS": "Mensajes de texto", "IDP": "Proveedores de identidad", + "VERIFIED_DOMAINS": "Dominios verificados", "DOMAIN": "Ajustes de dominio", "LOGINTEXTS": "Textos de interfaz de inicio de sesión", "BRANDING": "Imagen de marca", @@ -1105,7 +1106,7 @@ "INDAYS": "días" }, "SECRETS": { - "TITLE": "Apariencia del secreto", + "TITLE": "Generador del secreto", "TYPES": "Tipos de secreto", "TYPE": { "1": "Correo de inicialización", @@ -1117,7 +1118,7 @@ "7": "One Time Password (OTP) - SMS", "8": "One Time Password (OTP) - email" }, - "ADDGENERATOR": "Definir apariencia del secreto", + "ADDGENERATOR": "Configurar generador del secreto", "GENERATORTYPE": "Tipo", "EXPIRY": "Caducidad (en horas)", "INCLUDEDIGITS": "Incluir números", @@ -1351,7 +1352,7 @@ "MAXAGEDAYS": "Antigüedad máxima en días", "USERLOGINMUSTBEDOMAIN": "Añadir el dominio de la organización como sufijo de los nombres de inicio de sesión", "USERLOGINMUSTBEDOMAIN_DESCRIPTION": "Si activas esta opción, todos los nombres de inicio de sesión tendrán como sufijo el dominio de esta organización. Si esta opción está desactivada, tendrás que asegurarte de que los nombres de usuario son únicos para todas las organizaciones.", - "VALIDATEORGDOMAINS": "Validar los dominios de la organización", + "VALIDATEORGDOMAINS": "Verificación de dominio de la organización requerida (desafío DNS o HTTP)", "SMTPSENDERADDRESSMATCHESINSTANCEDOMAIN": "La dirección del remitente SMTP coincide con el dominio de la instancia", "ALLOWUSERNAMEPASSWORD": "Nombre de usuario y contraseña permitido", "ALLOWEXTERNALIDP": "Permitido IDP externo", diff --git a/console/src/assets/i18n/fr.json b/console/src/assets/i18n/fr.json index 469cd3ca69..39aae0e9d4 100644 --- a/console/src/assets/i18n/fr.json +++ b/console/src/assets/i18n/fr.json @@ -95,7 +95,6 @@ "EVENTS": "Événements", "FAILEDEVENTS": "Événements échoués", "ORGANIZATION": "Organisation", - "DOMAINS": "Domaines", "PROJECT": "Projets", "PROJECTOVERVIEW": "Vue d'ensemble", "PROJECTGRANTS": "Subventions", @@ -495,6 +494,7 @@ "LASTNAME": "Nom de famille", "NICKNAME": "Surnom", "DISPLAYNAME": "Nom d'affichage", + "PREFERREDLOGINNAME": "Nom de connexion préféré", "PREFERRED_LANGUAGE": "Langue", "GENDER": "Sexe", "PASSWORD": "Mot de passe", @@ -795,7 +795,7 @@ }, "PAGES": { "STATE": "Statut", - "DOMAINLIST": "Domaines" + "DOMAINLIST": "Domaines personnalisés" }, "STATE": { "0": "Inconnu", @@ -952,7 +952,7 @@ }, "DOMAINS": { "NEW": "Ajouter un domaine", - "TITLE": "Domaines", + "TITLE": "Domaines vérifiés", "DESCRIPTION": "Configurez vos domaines. Ce domaine peut être utilisé pour se connecter avec vos utilisateurs.", "SETPRIMARY": "Définir comme primaire", "DELETE": { @@ -1022,6 +1022,7 @@ "NOTIFICATIONS_DESC": "Paramètres SMTP et SMS", "MESSAGETEXTS": "Textes des messages", "IDP": "Fournisseurs d'identité", + "VERIFIED_DAMAINS": "Domaines vérifiés", "DOMAIN": "Paramètres du domaine", "LOGINTEXTS": "Textes de l'interface de connexion", "BRANDING": "Image de marque", @@ -1104,7 +1105,7 @@ "INDAYS": "Jours" }, "SECRETS": { - "TITLE": "Apparence du secret", + "TITLE": "Générateur du secret", "TYPES": "Types de secret", "TYPE": { "1": "Courrier d'initialisation", @@ -1116,7 +1117,7 @@ "7": "Mot de passe à usage unique (OTP) - SMS", "8": "Mot de passe à usage unique (OTP) - e-mail" }, - "ADDGENERATOR": "Définir l'apparence du secret", + "ADDGENERATOR": "Configurer générateur de mot de passe", "GENERATORTYPE": "Type", "EXPIRY": "Expiration (en heures)", "INCLUDEDIGITS": "Inclure les chiffres", @@ -1350,7 +1351,7 @@ "MAXAGEDAYS": "Âge maximum en jours", "USERLOGINMUSTBEDOMAIN": "Le nom de connexion de l'utilisateur doit contenir le nom de domaine de l'organisation", "USERLOGINMUSTBEDOMAIN_DESCRIPTION": "Si vous activez ce paramètre, tous les noms de connexion seront suffixés avec le domaine de l'organisation. Si ce paramètre est désactivé, vous devez vous assurer que les noms d'utilisateur sont uniques pour toutes les organisations.", - "VALIDATEORGDOMAINS": "Valider les domaines d'Org", + "VALIDATEORGDOMAINS": "Vérification du domaine de l'organisation requise (challenge DNS ou HTTP)", "SMTPSENDERADDRESSMATCHESINSTANCEDOMAIN": "L'adresse de l'expéditeur SMTP correspond au domaine de l'instance", "ALLOWUSERNAMEPASSWORD": "Nom d'utilisateur Mot de passe autorisé", "ALLOWEXTERNALIDP": "IDP externe autorisé", diff --git a/console/src/assets/i18n/it.json b/console/src/assets/i18n/it.json index 6605a8d91a..b06fc82e68 100644 --- a/console/src/assets/i18n/it.json +++ b/console/src/assets/i18n/it.json @@ -95,7 +95,6 @@ "EVENTS": "Eventi", "FAILEDEVENTS": "Eventi falliti", "ORGANIZATION": "Organizzazione", - "DOMAINS": "Domini", "PROJECT": "Progetti", "PROJECTOVERVIEW": "Progetto", "PROJECTGRANTS": "Organizzazioni ammissibili", @@ -494,6 +493,7 @@ "LASTNAME": "Cognome", "NICKNAME": "Soprannome", "DISPLAYNAME": "DisplayName", + "PREFERREDLOGINNAME": "Nome di accesso preferito", "PREFERRED_LANGUAGE": "Lingua", "GENDER": "Genere", "PASSWORD": "Password", @@ -794,7 +794,7 @@ }, "PAGES": { "STATE": "Stato", - "DOMAINLIST": "Domini" + "DOMAINLIST": "Domini personalizzati" }, "STATE": { "0": "Non specifico", @@ -952,7 +952,7 @@ }, "DOMAINS": { "NEW": "Aggiungi dominio", - "TITLE": "Domini", + "TITLE": "Domini verificati", "DESCRIPTION": "Configura i tuoi domini. Questo dominio pu\u00f2 essere utilizzato per accedere con i tuoi utenti.", "SETPRIMARY": "Impostato come primario", "DELETE": { @@ -1022,6 +1022,7 @@ "NOTIFICATIONS_DESC": "Impostazioni SMTP e SMS", "MESSAGETEXTS": "Testi di notifica", "IDP": "Fornitori di identità", + "VERIFIED_DAMAINS": "Domini verificati", "DOMAIN": "Impostazioni del dominio", "LOGINTEXTS": "Testi dell'interfaccia login", "BRANDING": "Branding", @@ -1104,7 +1105,7 @@ "INDAYS": "giorni" }, "SECRETS": { - "TITLE": "Aspetto dei segreti", + "TITLE": "Generatore di password", "TYPES": "Tipi di segreti", "TYPE": { "1": "Initializzazione email", @@ -1116,7 +1117,7 @@ "7": "One Time Password (OTP) - SMS", "8": "One Time Password (OTP) - email" }, - "ADDGENERATOR": "Definisci aspetto", + "ADDGENERATOR": "Imposta il generatore di password", "GENERATORTYPE": "Tipo", "EXPIRY": "Scadenza (in ore)", "INCLUDEDIGITS": "Contiene numeri", @@ -1350,7 +1351,7 @@ "MAXAGEDAYS": "Lunghezza massima in giorni", "USERLOGINMUSTBEDOMAIN": "Nome utente deve contenere il dominio dell' organizzazione", "USERLOGINMUSTBEDOMAIN_DESCRIPTION": "Se abiliti questa impostazione, a tutti i nomi di accesso verrà aggiunto il suffisso del dominio dell'organizzazione. Se questa impostazione è disabilitata, devi assicurarti che i nomi utente siano univoci per tutte le organizzazioni.", - "VALIDATEORGDOMAINS": "Verifica domini dell' organizzazione", + "VALIDATEORGDOMAINS": "Verifica del dominio dell'organizzazione richiesta (challenge DNS o HTTP)", "SMTPSENDERADDRESSMATCHESINSTANCEDOMAIN": "L'indirizzo mittente SMTP corrisponde al dominio dell'istanza", "ALLOWUSERNAMEPASSWORD": "Autenticazione classica con password consentita", "ALLOWEXTERNALIDP": "IDP esterno consentito", diff --git a/console/src/assets/i18n/ja.json b/console/src/assets/i18n/ja.json index 623f802469..7f90040d4f 100644 --- a/console/src/assets/i18n/ja.json +++ b/console/src/assets/i18n/ja.json @@ -95,7 +95,6 @@ "EVENTS": "イベント", "FAILEDEVENTS": "失敗したイベント", "ORGANIZATION": "組織", - "DOMAINS": "ドメイン", "PROJECT": "プロジェクト", "PROJECTOVERVIEW": "概要", "PROJECTGRANTS": "グラント", @@ -496,6 +495,7 @@ "LASTNAME": "姓", "NICKNAME": "ニックネーム", "DISPLAYNAME": "表示名", + "PREFERREDLOGINNAME": "優先ログイン名", "PREFERRED_LANGUAGE": "言語", "GENDER": "性別", "PASSWORD": "パスワード", @@ -796,7 +796,7 @@ }, "PAGES": { "STATE": "ステータス", - "DOMAINLIST": "ドメイン" + "DOMAINLIST": "カスタムドメイン" }, "STATE": { "0": "未定義", @@ -953,7 +953,7 @@ }, "DOMAINS": { "NEW": "ドメインを追加する", - "TITLE": "ドメイン", + "TITLE": "検証済みドメイン", "DESCRIPTION": "ドメインを設定します。このドメインは、ユーザーのログインで使用できます。", "SETPRIMARY": "プライマリとして設定する", "DELETE": { @@ -1023,6 +1023,7 @@ "NOTIFICATIONS_DESC": "SMTPおよびSMS設定", "MESSAGETEXTS": "メッセージテキスト", "IDP": "IDプロバイダー", + "VERIFIED_DAMAINS": "検証済みドメイン", "DOMAIN": "ドメイン設定", "LOGINTEXTS": "ログイン画面のテキスト", "BRANDING": "ブランディング", @@ -1346,7 +1347,7 @@ "MAXAGEDAYS": "最大有効期限", "USERLOGINMUSTBEDOMAIN": "ログイン名の接尾辞として組織ドメインを追加する", "USERLOGINMUSTBEDOMAIN_DESCRIPTION": "この設定を有効にすると、すべてのログイン名が組織ドメインで接尾辞が付けられます。この設定が無効になっている場合、ユーザー名がすべての組織で一意であることを確認する必要があります。", - "VALIDATEORGDOMAINS": "組織ドメインを認証する", + "VALIDATEORGDOMAINS": "組織のドメイン検証が必要です (DNSまたはHTTPチャレンジ)", "SMTPSENDERADDRESSMATCHESINSTANCEDOMAIN": "SMTP送信者アドレスはインスタンスドメインに一致しています", "ALLOWUSERNAMEPASSWORD": "ユーザー名とパスワードを許可", "ALLOWEXTERNALIDP": "外部IDPを許可", diff --git a/console/src/assets/i18n/mk.json b/console/src/assets/i18n/mk.json index 65af79503c..e21458534b 100644 --- a/console/src/assets/i18n/mk.json +++ b/console/src/assets/i18n/mk.json @@ -95,7 +95,6 @@ "EVENTS": "Настани", "FAILEDEVENTS": "Неуспешни настани", "ORGANIZATION": "Организација", - "DOMAINS": "Домени", "PROJECT": "Проекти", "PROJECTOVERVIEW": "Преглед", "PROJECTGRANTS": "Овластувања", @@ -496,6 +495,7 @@ "LASTNAME": "Презиме", "NICKNAME": "Прекар", "DISPLAYNAME": "Име за приказ", + "PREFERREDLOGINNAME": "Претпочитано име за најава", "PREFERRED_LANGUAGE": "Јазик", "GENDER": "Пол", "PASSWORD": "Лозинка", @@ -796,7 +796,7 @@ }, "PAGES": { "STATE": "Статус", - "DOMAINLIST": "Домени" + "DOMAINLIST": "Прилагодени домени" }, "STATE": { "0": "Ненаведено", @@ -953,7 +953,7 @@ }, "DOMAINS": { "NEW": "Додади домен", - "TITLE": "Домени", + "TITLE": "Потврдени домени", "DESCRIPTION": "Конфигурирајте ги вашите домени. Овој домен може да се користи за најава на вашите корисници.", "SETPRIMARY": "Постави како основен", "DELETE": { @@ -1024,6 +1024,7 @@ "NOTIFICATIONS_DESC": "Подесувања за SMTP и SMS", "MESSAGETEXTS": "Текстови на пораки", "IDP": "Identity Providers", + "VERIFIED_DAMAINS": "Потврдени домени", "DOMAIN": "Подесувања за домен", "LOGINTEXTS": "Текстови на интерфејс за најава", "BRANDING": "Брендирање", @@ -1352,7 +1353,7 @@ "MAXAGEDAYS": "Максимална возраст во денови", "USERLOGINMUSTBEDOMAIN": "Додади организациски домен како суфикс на корисничките имиња", "USERLOGINMUSTBEDOMAIN_DESCRIPTION": "Ако го овозможите ова подесување, сите кориснички имиња ќе имаат суфикс на организацискиот домен. Доколку ова подесување е оневозможено, морате да се осигурате дека корисничките имиња се уникатни низ сите организации.", - "VALIDATEORGDOMAINS": "Валидирај организациски домени", + "VALIDATEORGDOMAINS": "Потврда на доменот на организацијата е неопходна (DNS или HTTP предизвик)", "SMTPSENDERADDRESSMATCHESINSTANCEDOMAIN": "SMTP адресата на испраќачот се поклопува со доменот на инстанцата", "ALLOWUSERNAMEPASSWORD": "Дозволено корисничко име и лозинка", "ALLOWEXTERNALIDP": "Дозволен надворешен IDP", diff --git a/console/src/assets/i18n/pl.json b/console/src/assets/i18n/pl.json index f594d9993a..244de0b78a 100644 --- a/console/src/assets/i18n/pl.json +++ b/console/src/assets/i18n/pl.json @@ -95,7 +95,6 @@ "EVENTS": "Zdarzenia", "FAILEDEVENTS": "Nieudane Zdarzenia", "ORGANIZATION": "Organizacja", - "DOMAINS": "Domeny", "PROJECT": "Projekty", "PROJECTOVERVIEW": "Przegląd", "PROJECTGRANTS": "Uprawnienia", @@ -495,6 +494,7 @@ "LASTNAME": "Nazwisko", "NICKNAME": "Przezwisko", "DISPLAYNAME": "Nazwa wyświetlana", + "PREFERREDLOGINNAME": "Preferowana nazwa logowania", "PREFERRED_LANGUAGE": "Język", "GENDER": "Płeć", "PASSWORD": "Hasło", @@ -795,7 +795,7 @@ }, "PAGES": { "STATE": "Status", - "DOMAINLIST": "Domeny" + "DOMAINLIST": "Własne domeny" }, "STATE": { "0": "Nieokreślony", @@ -952,7 +952,7 @@ }, "DOMAINS": { "NEW": "Dodaj domenę", - "TITLE": "Domeny", + "TITLE": "Zweryfikowane domeny", "DESCRIPTION": "Skonfiguruj swoje domeny. Ta domena może być używana do logowania się z Twoimi użytkownikami.", "SETPRIMARY": "Ustaw jako główną", "DELETE": { @@ -1022,6 +1022,7 @@ "NOTIFICATIONS_DESC": "Ustawienia SMTP i SMS", "MESSAGETEXTS": "Teksty wiadomości", "IDP": "Dostawcy tożsamości", + "VERIFIED_DAMAINS": "Zweryfikowane domeny", "DOMAIN": "Ustawienia domeny", "LOGINTEXTS": "Teksty interfejsu logowania", "BRANDING": "Marka", @@ -1350,7 +1351,7 @@ "MAXAGEDAYS": "Maksymalny wiek w dniach", "USERLOGINMUSTBEDOMAIN": "Dodaj domenę organizacji jako przyrostek do nazw logowania", "USERLOGINMUSTBEDOMAIN_DESCRIPTION": "Jeśli włączysz to ustawienie, wszystkie nazwy logowania będą miały przyrostek z domeną organizacji. Jeśli to ustawienie jest wyłączone, musisz zapewnić unikalność nazw użytkowników we wszystkich organizacjach.", - "VALIDATEORGDOMAINS": "Sprawdzanie ważności domen organizacji", + "VALIDATEORGDOMAINS": "Weryfikacja domeny organizacji jest wymagana (wyzwanie DNS lub HTTP)", "SMTPSENDERADDRESSMATCHESINSTANCEDOMAIN": "Adres nadawcy SMTP pasuje do domeny instancji", "ALLOWUSERNAMEPASSWORD": "Zezwól na użycie nazwy użytkownika i hasła", "ALLOWEXTERNALIDP": "Zezwól na zewnętrznego dostawcę tożsamości", diff --git a/console/src/assets/i18n/pt.json b/console/src/assets/i18n/pt.json index 29e9e34f61..ecbabcc56a 100644 --- a/console/src/assets/i18n/pt.json +++ b/console/src/assets/i18n/pt.json @@ -95,7 +95,6 @@ "EVENTS": "Eventos", "FAILEDEVENTS": "Eventos com Falha", "ORGANIZATION": "Organização", - "DOMAINS": "Domínios", "PROJECT": "Projetos", "PROJECTOVERVIEW": "Visão Geral", "PROJECTGRANTS": "Autorizações", @@ -496,6 +495,7 @@ "LASTNAME": "Sobrenome", "NICKNAME": "Apelido", "DISPLAYNAME": "Nome de Exibição", + "PREFERREDLOGINNAME": "Nome de login preferido", "PREFERRED_LANGUAGE": "Idioma", "GENDER": "Gênero", "SENHA": "Senha", @@ -796,7 +796,7 @@ }, "PAGES": { "STATE": "Status", - "DOMAINLIST": "Domínios" + "DOMAINLIST": "Domínios personalizados" }, "STATE": { "0": "Não especificado", @@ -953,7 +953,7 @@ }, "DOMAINS": { "NEW": "Adicionar Domínio", - "TITLE": "Domínios", + "TITLE": "Domínios verificados", "DESCRIPTION": "Configure seus domínios. Este domínio pode ser usado para o login dos seus usuários.", "SETPRIMARY": "Definir como Principal", "DELETE": { @@ -1024,6 +1024,7 @@ "NOTIFICATIONS_DESC": "Configurações de SMTP e SMS", "MESSAGETEXTS": "Textos de Mensagem", "IDP": "Provedores de Identidade", + "VERIFIED_DAMAINS": "Domínios verificados", "DOMAIN": "Configurações de Domínio", "LOGINTEXTS": "Textos da Interface de Login", "BRANDING": "Marca", @@ -1352,7 +1353,7 @@ "MAXAGEDAYS": "Idade máxima em dias", "USERLOGINMUSTBEDOMAIN": "Adicionar domínio da organização como sufixo aos nomes de login", "USERLOGINMUSTBEDOMAIN_DESCRIPTION": "Se você habilitar essa configuração, todos os nomes de login serão sufixados com o domínio da organização. Se essa configuração estiver desabilitada, você deve garantir que os nomes de usuário sejam exclusivos em todas as organizações.", - "VALIDATEORGDOMAINS": "Validar domínios da organização", + "VALIDATEORGDOMAINS": "Verificação de domínio da organização necessária (desafio DNS ou HTTP)", "SMTPSENDERADDRESSMATCHESINSTANCEDOMAIN": "O endereço do remetente do SMTP corresponde ao domínio da Instância", "ALLOWUSERNAMEPASSWORD": "Permitir usuário e senha", "ALLOWEXTERNALIDP": "Permitir provedor de ID externo", diff --git a/console/src/assets/i18n/zh.json b/console/src/assets/i18n/zh.json index 23cacbc444..9b8606d901 100644 --- a/console/src/assets/i18n/zh.json +++ b/console/src/assets/i18n/zh.json @@ -95,7 +95,6 @@ "EVENTS": "活动", "FAILEDEVENTS": "失败事件", "ORGANIZATION": "组织", - "DOMAINS": "域名", "PROJECT": "项目", "PROJECTOVERVIEW": "概览", "PROJECTGRANTS": "授予", @@ -495,6 +494,7 @@ "LASTNAME": "姓", "NICKNAME": "昵称", "DISPLAYNAME": "展示名称", + "PREFERREDLOGINNAME": "首选登录名", "PREFERRED_LANGUAGE": "语言", "GENDER": "性别", "PASSWORD": "密码", @@ -795,7 +795,7 @@ }, "PAGES": { "STATE": "状态", - "DOMAINLIST": "域名" + "DOMAINLIST": "自定义域名" }, "STATE": { "0": "未指定", @@ -952,7 +952,7 @@ }, "DOMAINS": { "NEW": "添加域名", - "TITLE": "域名", + "TITLE": "已验证的域名", "DESCRIPTION": "配置您的域名。此域名可用于您的用户登录。", "SETPRIMARY": "设置为主域名", "DELETE": { @@ -1022,6 +1022,7 @@ "NOTIFICATIONS_DESC": "SMTP 和 SMS 设置", "MESSAGETEXTS": "消息文本", "IDP": "身份提供者", + "VERIFIED_DAMAINS": "已验证的域名", "DOMAIN": "域名设置", "LOGINTEXTS": "登录界面文本", "BRANDING": "品牌标识", @@ -1349,7 +1350,7 @@ "MAXAGEDAYS": "Max Age in days", "USERLOGINMUSTBEDOMAIN": "用户名必须包含组织域名", "USERLOGINMUSTBEDOMAIN_DESCRIPTION": "如果启用此设置,所有登录名都将以组织域为后缀。如果禁用此设置,您必须确保用户名在所有组织中都是唯一的。", - "VALIDATEORGDOMAINS": "验证组织域名", + "VALIDATEORGDOMAINS": "组织域名验证需要 (DNS 或 HTTP 挑战)", "SMTPSENDERADDRESSMATCHESINSTANCEDOMAIN": "SMTP 发件人地址与实例域名匹配", "ALLOWUSERNAMEPASSWORD": "允许用户名密码", "ALLOWEXTERNALIDP": "允许外部身份提供者", diff --git a/console/src/assets/icons/line-awesome/css/line-awesome.css b/console/src/assets/icons/line-awesome/css/line-awesome.css index 9af89dcc2d..8796d90318 100644 --- a/console/src/assets/icons/line-awesome/css/line-awesome.css +++ b/console/src/assets/icons/line-awesome/css/line-awesome.css @@ -5831,9 +5831,12 @@ readers do not read off random characters that represent icons */ font-weight: normal; font-display: auto; src: url('../fonts/la-brands-400.eot'); - src: url('../fonts/la-brands-400.eot?#iefix') format('embedded-opentype'), - url('../fonts/la-brands-400.woff2') format('woff2'), url('../fonts/la-brands-400.woff') format('woff'), - url('../fonts/la-brands-400.ttf') format('truetype'), url('../fonts/la-brands-400.svg#lineawesome') format('svg'); + src: + url('../fonts/la-brands-400.eot?#iefix') format('embedded-opentype'), + url('../fonts/la-brands-400.woff2') format('woff2'), + url('../fonts/la-brands-400.woff') format('woff'), + url('../fonts/la-brands-400.ttf') format('truetype'), + url('../fonts/la-brands-400.svg#lineawesome') format('svg'); } .lab { @@ -5845,9 +5848,12 @@ readers do not read off random characters that represent icons */ font-weight: 400; font-display: auto; src: url('../fonts/la-regular-400.eot'); - src: url('../fonts/la-regular-400.eot?#iefix') format('embedded-opentype'), - url('../fonts/la-regular-400.woff2') format('woff2'), url('../fonts/la-regular-400.woff') format('woff'), - url('../fonts/la-regular-400.ttf') format('truetype'), url('../fonts/la-regular-400.svg#lineawesome') format('svg'); + src: + url('../fonts/la-regular-400.eot?#iefix') format('embedded-opentype'), + url('../fonts/la-regular-400.woff2') format('woff2'), + url('../fonts/la-regular-400.woff') format('woff'), + url('../fonts/la-regular-400.ttf') format('truetype'), + url('../fonts/la-regular-400.svg#lineawesome') format('svg'); } .lar { @@ -5860,9 +5866,12 @@ readers do not read off random characters that represent icons */ font-weight: 900; font-display: auto; src: url('../fonts/la-solid-900.eot'); - src: url('../fonts/la-solid-900.eot?#iefix') format('embedded-opentype'), - url('../fonts/la-solid-900.woff2') format('woff2'), url('../fonts/la-solid-900.woff') format('woff'), - url('../fonts/la-solid-900.ttf') format('truetype'), url('../fonts/la-solid-900.svg#lineawesome') format('svg'); + src: + url('../fonts/la-solid-900.eot?#iefix') format('embedded-opentype'), + url('../fonts/la-solid-900.woff2') format('woff2'), + url('../fonts/la-solid-900.woff') format('woff'), + url('../fonts/la-solid-900.ttf') format('truetype'), + url('../fonts/la-solid-900.svg#lineawesome') format('svg'); } .la, diff --git a/console/src/assets/icons/line-awesome/css/line-awesome.min.css b/console/src/assets/icons/line-awesome/css/line-awesome.min.css index 35252858e1..0fb6dfe0b7 100644 --- a/console/src/assets/icons/line-awesome/css/line-awesome.min.css +++ b/console/src/assets/icons/line-awesome/css/line-awesome.min.css @@ -4390,8 +4390,11 @@ font-weight: 400; font-display: auto; src: url(../fonts/la-brands-400.eot); - src: url(../fonts/la-brands-400.eot?#iefix) format('embedded-opentype'), url(../fonts/la-brands-400.woff2) format('woff2'), - url(../fonts/la-brands-400.woff) format('woff'), url(../fonts/la-brands-400.ttf) format('truetype'), + src: + url(../fonts/la-brands-400.eot?#iefix) format('embedded-opentype'), + url(../fonts/la-brands-400.woff2) format('woff2'), + url(../fonts/la-brands-400.woff) format('woff'), + url(../fonts/la-brands-400.ttf) format('truetype'), url(../fonts/la-brands-400.svg#lineawesome) format('svg'); } .lab { @@ -4403,9 +4406,12 @@ font-weight: 400; font-display: auto; src: url(../fonts/la-regular-400.eot); - src: url(../fonts/la-regular-400.eot?#iefix) format('embedded-opentype'), - url(../fonts/la-regular-400.woff2) format('woff2'), url(../fonts/la-regular-400.woff) format('woff'), - url(../fonts/la-regular-400.ttf) format('truetype'), url(../fonts/la-regular-400.svg#lineawesome) format('svg'); + src: + url(../fonts/la-regular-400.eot?#iefix) format('embedded-opentype'), + url(../fonts/la-regular-400.woff2) format('woff2'), + url(../fonts/la-regular-400.woff) format('woff'), + url(../fonts/la-regular-400.ttf) format('truetype'), + url(../fonts/la-regular-400.svg#lineawesome) format('svg'); } .lar { font-family: 'Line Awesome Free'; @@ -4417,8 +4423,11 @@ font-weight: 900; font-display: auto; src: url(../fonts/la-solid-900.eot); - src: url(../fonts/la-solid-900.eot?#iefix) format('embedded-opentype'), url(../fonts/la-solid-900.woff2) format('woff2'), - url(../fonts/la-solid-900.woff) format('woff'), url(../fonts/la-solid-900.ttf) format('truetype'), + src: + url(../fonts/la-solid-900.eot?#iefix) format('embedded-opentype'), + url(../fonts/la-solid-900.woff2) format('woff2'), + url(../fonts/la-solid-900.woff) format('woff'), + url(../fonts/la-solid-900.ttf) format('truetype'), url(../fonts/la-solid-900.svg#lineawesome) format('svg'); } .la, diff --git a/console/src/index.html b/console/src/index.html index fe500d8d28..193bf704c1 100644 --- a/console/src/index.html +++ b/console/src/index.html @@ -1,4 +1,4 @@ - + diff --git a/console/src/styles.scss b/console/src/styles.scss index 40a0d614fe..4953a44db1 100644 --- a/console/src/styles.scss +++ b/console/src/styles.scss @@ -569,8 +569,19 @@ body { body { margin: 0; - font-family: 'Lato', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', - 'Droid Sans', 'Helvetica Neue', sans-serif; + font-family: + 'Lato', + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + 'Roboto', + 'Oxygen', + 'Ubuntu', + 'Cantarell', + 'Fira Sans', + 'Droid Sans', + 'Helvetica Neue', + sans-serif; } h1 { diff --git a/console/src/styles/input.scss b/console/src/styles/input.scss index 16c29c5d79..190b8be9a6 100644 --- a/console/src/styles/input.scss +++ b/console/src/styles/input.scss @@ -25,7 +25,9 @@ border-radius: 4px; height: 40px; padding: 10px; - transition: border-color 0.15s ease-in-out, background-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), + transition: + border-color 0.15s ease-in-out, + background-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); width: 100%; color: mat.get-color-from-palette($foreground, text); diff --git a/console/yarn.lock b/console/yarn.lock index f1816b4321..2a959aec9a 100644 --- a/console/yarn.lock +++ b/console/yarn.lock @@ -2026,10 +2026,10 @@ resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz" integrity sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw== -"@eslint/eslintrc@^2.1.1": - version "2.1.1" - resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.1.tgz" - integrity sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA== +"@eslint/eslintrc@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.2.tgz#c6936b4b328c64496692f76944e755738be62396" + integrity sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g== dependencies: ajv "^6.12.4" debug "^4.3.2" @@ -2041,39 +2041,38 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@^8.46.0": - version "8.46.0" - resolved "https://registry.npmjs.org/@eslint/js/-/js-8.46.0.tgz" - integrity sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA== +"@eslint/js@8.50.0": + version "8.50.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.50.0.tgz#9e93b850f0f3fa35f5fa59adfd03adae8488e484" + integrity sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ== "@gar/promisify@^1.1.3": version "1.1.3" resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== -"@grpc/grpc-js@^1.8.14": - version "1.8.14" - resolved "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.14.tgz" - integrity sha512-w84maJ6CKl5aApCMzFll0hxtFNT6or9WwMslobKaqWUEf1K+zhlL43bSQhFreyYWIWR+Z0xnVFC1KtLm4ZpM/A== +"@grpc/grpc-js@^1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.9.3.tgz#811cc49966ab7ed96efa31d213e80d671fd13839" + integrity sha512-b8iWtdrYIeT5fdZdS4Br/6h/kuk0PW5EVBUGk1amSbrpL8DlktJD43CdcCWwRdd6+jgwHhADSbL9CsNnm6EUPA== dependencies: - "@grpc/proto-loader" "^0.7.0" + "@grpc/proto-loader" "^0.7.8" "@types/node" ">=12.12.47" -"@grpc/proto-loader@^0.7.0": - version "0.7.7" - resolved "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.7.tgz" - integrity sha512-1TIeXOi8TuSCQprPItwoMymZXxWT0CPxUhkrkeCUH+D8U7QDwQ6b7SUz2MaLuWM2llT+J/TVFLmQI5KtML3BhQ== +"@grpc/proto-loader@^0.7.8": + version "0.7.10" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.10.tgz#6bf26742b1b54d0a473067743da5d3189d06d720" + integrity sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ== dependencies: - "@types/long" "^4.0.1" lodash.camelcase "^4.3.0" - long "^4.0.0" - protobufjs "^7.0.0" + long "^5.0.0" + protobufjs "^7.2.4" yargs "^17.7.2" -"@humanwhocodes/config-array@^0.11.10": - version "0.11.10" - resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz" - integrity sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ== +"@humanwhocodes/config-array@^0.11.11": + version "0.11.11" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844" + integrity sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA== dependencies: "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" @@ -3274,15 +3273,10 @@ dependencies: "@types/node" "*" -"@types/jasmine@*": - version "4.3.1" - resolved "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.3.1.tgz" - integrity sha512-Vu8l+UGcshYmV1VWwULgnV/2RDbBaO6i2Ptx7nd//oJPIZGhoI1YLST4VKagD2Pq/Bc2/7zvtvhM7F3p4SN7kQ== - -"@types/jasmine@~4.3.3": - version "4.3.5" - resolved "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.3.5.tgz" - integrity sha512-9YHUdvuNDDRJYXZwHqSsO72Ok0vmqoJbNn73ttyITQp/VA60SarnZ+MPLD37rJAhVoKp+9BWOvJP5tHIRfZylQ== +"@types/jasmine@*", "@types/jasmine@~4.3.6": + version "4.3.6" + resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-4.3.6.tgz#d9855fa9f808138488784610f888046bb9a59aff" + integrity sha512-3N0FpQTeiWjm+Oo1WUYWguUS7E6JLceiGTriFrG8k5PU7zRLJCzLcWURU3wjMbZGS//a2/LgjsnO3QxIlwxt9g== "@types/jasminewd2@~2.0.10": version "2.0.10" @@ -3303,11 +3297,6 @@ dependencies: "@types/node" "*" -"@types/long@^4.0.1": - version "4.0.2" - resolved "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz" - integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== - "@types/mime@*": version "3.0.1" resolved "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz" @@ -3319,14 +3308,14 @@ integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== "@types/node@*", "@types/node@>=10.0.0", "@types/node@>=12.12.47", "@types/node@>=13.7.0": - version "20.1.5" - resolved "https://registry.npmjs.org/@types/node/-/node-20.1.5.tgz" - integrity sha512-IvGD1CD/nego63ySR7vrAKEX3AJTcmrAN2kn+/sDNLi1Ff5kBzDeEdqWDplK+0HAEoLYej137Sk0cUU8OLOlMg== + version "20.6.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.6.5.tgz#4c6a79adf59a8e8193ac87a0e522605b16587258" + integrity sha512-2qGq5LAOTh9izcc0+F+dToFigBWiK1phKPt7rNhOqJSr35y8rlIBjDwGtFSgAI6MGIhjwOVNSQZVdJsZJ2uR1w== -"@types/node@^18.15.11": - version "18.16.10" - resolved "https://registry.npmjs.org/@types/node/-/node-18.16.10.tgz" - integrity sha512-sMo3EngB6QkMBlB9rBe1lFdKSLqljyWPPWv6/FzSxh/IDlyVWSzE9RiF4eAuerQHybrWdqBgAGb03PM89qOasA== +"@types/node@^20.7.0": + version "20.7.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.7.0.tgz#c03de4572f114a940bc2ca909a33ddb2b925e470" + integrity sha512-zI22/pJW2wUZOVyguFaUL1HABdmSVxpXrzIqkjsHmyUjNhPoWM1CKfvVuXfetHhIok4RY573cqS0mZ1SJEnoTg== "@types/opentype.js@^1.3.4": version "1.3.4" @@ -3338,13 +3327,20 @@ resolved "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz" integrity sha512-qYi3YV9inU/REEfxwVcGZzbS3KG/Xs90lv0Pr+lDtuVjBPGd1A+eciXzVSaRvLify132BfcvhvEjeVahrUl0Ug== -"@types/qrcode@1.5.0", "@types/qrcode@^1.5.0": +"@types/qrcode@1.5.0": version "1.5.0" resolved "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.0.tgz" integrity sha512-x5ilHXRxUPIMfjtM+1vf/GPTRWZ81nqscursm5gMznJeK9M0YnZ1c3bEvRLQ0zSSgedLx1J6MGL231ObQGGhaA== dependencies: "@types/node" "*" +"@types/qrcode@^1.5.2": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.5.2.tgz#27633439b7fbe88cc3043b29c8e7612a8a789e15" + integrity sha512-W4KDz75m7rJjFbyCctzCtRzZUj+PrUHV+YjqDp50sSRezTbrtEAIq2iTzC6lISARl3qw+8IlcCyljdcVJE0Wug== + dependencies: + "@types/node" "*" + "@types/qs@*": version "6.9.7" resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz" @@ -5384,15 +5380,7 @@ eslint-scope@5.1.1, eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^7.0.0: - version "7.2.0" - resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz" - integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-scope@^7.2.2: +eslint-scope@^7.0.0, eslint-scope@^7.2.2: version "7.2.2" resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz" integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== @@ -5400,21 +5388,21 @@ eslint-scope@^7.2.2: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.2: - version "3.4.2" - resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz" - integrity sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw== +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@^8.44.0: - version "8.46.0" - resolved "https://registry.npmjs.org/eslint/-/eslint-8.46.0.tgz" - integrity sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg== +eslint@^8.50.0: + version "8.50.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.50.0.tgz#2ae6015fee0240fcd3f83e1e25df0287f487d6b2" + integrity sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.1" - "@eslint/js" "^8.46.0" - "@humanwhocodes/config-array" "^0.11.10" + "@eslint/eslintrc" "^2.1.2" + "@eslint/js" "8.50.0" + "@humanwhocodes/config-array" "^0.11.11" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" ajv "^6.12.4" @@ -5424,7 +5412,7 @@ eslint@^8.44.0: doctrine "^3.0.0" escape-string-regexp "^4.0.0" eslint-scope "^7.2.2" - eslint-visitor-keys "^3.4.2" + eslint-visitor-keys "^3.4.3" espree "^9.6.1" esquery "^1.4.2" esutils "^2.0.2" @@ -7266,11 +7254,6 @@ log4js@^6.4.1: rfdc "^1.3.0" streamroller "^3.1.5" -long@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/long/-/long-4.0.0.tgz" - integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== - long@^5.0.0: version "5.2.3" resolved "https://registry.npmjs.org/long/-/long-5.2.3.tgz" @@ -8349,10 +8332,10 @@ prettier-plugin-organize-imports@^3.2.2: resolved "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.2.tgz" integrity sha512-e97lE6odGSiHonHJMTYC0q0iLXQyw0u5z/PJpvP/3vRy6/Zi9kLBwFAbEGjDzIowpjQv8b+J04PDamoUSQbzGA== -prettier@^2.8.7: - version "2.8.8" - resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz" - integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== +prettier@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643" + integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg== pretty-bytes@^5.3.0: version "5.6.0" @@ -8382,10 +8365,10 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" -protobufjs@^7.0.0: - version "7.2.3" - resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.3.tgz" - integrity sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg== +protobufjs@^7.0.0, protobufjs@^7.2.4: + version "7.2.5" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.5.tgz#45d5c57387a6d29a17aab6846dcc283f9b8e7f2d" + integrity sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -8907,7 +8890,7 @@ semver@7.5.3: dependencies: lru-cache "^6.0.0" -semver@7.5.4: +semver@7.5.4, semver@^7.0.0, semver@^7.1.1, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -8915,27 +8898,15 @@ semver@7.5.4: lru-cache "^6.0.0" semver@^5.3.0, semver@^5.6.0: - version "5.7.1" - resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.0.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^6.3.1: +semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.0.0, semver@^7.1.1, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: - version "7.5.1" - resolved "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz" - integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw== - dependencies: - lru-cache "^6.0.0" - send@0.18.0: version "0.18.0" resolved "https://registry.npmjs.org/send/-/send-0.18.0.tgz" @@ -9080,9 +9051,9 @@ socket.io-adapter@~2.5.2: ws "~8.11.0" socket.io-parser@~4.2.1: - version "4.2.2" - resolved "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz" - integrity sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw== + version "4.2.4" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" + integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== dependencies: "@socket.io/component-emitter" "~3.1.0" debug "~4.3.1" diff --git a/docs/docs/apis/introduction.mdx b/docs/docs/apis/introduction.mdx index 5f56fd811e..2569edea7e 100644 --- a/docs/docs/apis/introduction.mdx +++ b/docs/docs/apis/introduction.mdx @@ -1,5 +1,5 @@ --- -title: API Reference Overview +title: ZITADEL API Reference Overview sidebar_label: Overview --- diff --git a/docs/docs/apis/observability/health.md b/docs/docs/apis/observability/health.md index 0b562b35d5..30a63f7999 100644 --- a/docs/docs/apis/observability/health.md +++ b/docs/docs/apis/observability/health.md @@ -1,5 +1,6 @@ --- -title: Ready / Healthy +title: ZITADEL Ready and Health Enpoints +sidebar_label: Ready and Health Enpoints --- ZITADEL exposes a `Ready`- and `Healthy` endpoint to allow external systems like load balancers, orchestration systems, uptime probes and others to check the status. diff --git a/docs/docs/apis/observability/metrics.md b/docs/docs/apis/observability/metrics.md index ef83395a4a..d3343526f5 100644 --- a/docs/docs/apis/observability/metrics.md +++ b/docs/docs/apis/observability/metrics.md @@ -1,5 +1,6 @@ --- -title: Metrics +title: ZITADEL Metrics +sidebar_label: Metrics --- ZITADEL provides a `metrics` endpoint with the help of the [opentelemetry-go](https://github.com/open-telemetry/opentelemetry-go) package. diff --git a/docs/docs/apis/openidoauth/authn-methods.md b/docs/docs/apis/openidoauth/authn-methods.md index b432d8984f..7e9566fd0e 100644 --- a/docs/docs/apis/openidoauth/authn-methods.md +++ b/docs/docs/apis/openidoauth/authn-methods.md @@ -1,5 +1,6 @@ --- -title: Authentication Methods +title: Authentication Methods in ZITADEL +sidebar_label: Authentication Methods --- ## Client Secret Basic @@ -45,7 +46,7 @@ JWT | Claim | Example | Description | |:------|:---------------------------|:----------------------------------------------------------------------------------------------------------------| -| aud | `"https://{your_domain}"` | String or Array of intended audiences MUST include ZITADEL's issuing domain | +| aud | `"https://$CUSTOM-DOMAIN"` | String or Array of intended audiences MUST include ZITADEL's issuing domain | | exp | `1605183582` | Unix timestamp of the expiry | | iat | `1605179982` | Unix timestamp of the creation singing time of the JWT, MUST NOT be older than 1h | | iss | `"78366401571920522@acme"` | String which represents the requesting party (owner of the key), normally the `clientID` from the json key file | @@ -55,7 +56,7 @@ JWT { "iss": "78366401571920522@acme", "sub": "78366401571920522@acme", - "aud": "https://{your_domain}", + "aud": "https://$CUSTOM-DOMAIN", "exp": 1605183582, "iat": 1605179982 } diff --git a/docs/docs/apis/openidoauth/authrequest.mdx b/docs/docs/apis/openidoauth/authrequest.mdx index d352a09b8a..1ec82c775d 100644 --- a/docs/docs/apis/openidoauth/authrequest.mdx +++ b/docs/docs/apis/openidoauth/authrequest.mdx @@ -1,5 +1,5 @@ --- -title: OIDC Authentication Request Playground +title: ZITADEL OIDC Authentication Request Playground sidebar_label: OIDC Playground --- diff --git a/docs/docs/apis/openidoauth/claims.md b/docs/docs/apis/openidoauth/claims.md index 020a108f63..fb006765e2 100644 --- a/docs/docs/apis/openidoauth/claims.md +++ b/docs/docs/apis/openidoauth/claims.md @@ -1,5 +1,6 @@ --- -title: Claims +title: Claims in ZITADEL +sidebar_label: Claims --- ZITADEL asserts claims on different places according to the corresponding specifications or project and clients settings. @@ -42,7 +43,7 @@ Please check below the matrix for an overview where which scope is asserted. | Claims | Example | Description | |:-------------------|:-----------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------| | acr | TBA | TBA | -| address | `Lerchenfeldstrasse 3, 9014 St. Gallen` | TBA | +| address | `Lerchenfeldstrasse 3, 9014 St. Gallen` | TBA | | amr | `pwd mfa` | Authentication Method References as defined in [RFC8176](https://tools.ietf.org/html/rfc8176)
    `password` value is deprecated, please check `pwd` | | aud | `69234237810729019` | The audience of the token, by default all client id's and the project id are included | | auth_time | `1311280969` | Unix time of the authentication | @@ -54,7 +55,7 @@ Please check below the matrix for an overview where which scope is asserted. | gender | `other` | Gender of the subject | | given_name | `Road` | Given name of the subject | | iat | `1311280970` | Time of the token was issued at (as unix time) | -| iss | `{your_domain}` | Issuing domain of a token | +| iss | `$CUSTOM-DOMAIN` | Issuing domain of a token | | jti | `69234237813329048` | Unique id of the token | | locale | `en` | Language from the subject | | name | `Road Runner` | The subjects full name | diff --git a/docs/docs/apis/openidoauth/endpoints.mdx b/docs/docs/apis/openidoauth/endpoints.mdx index a3b4875cdd..a9db7ffacc 100644 --- a/docs/docs/apis/openidoauth/endpoints.mdx +++ b/docs/docs/apis/openidoauth/endpoints.mdx @@ -1,5 +1,6 @@ --- -title: OpenID Connect Endpoints +title: OpenID Connect Endpoints in ZITADEL +sidebar_label: OpenID Connect Endpoints --- import Tabs from "@theme/Tabs"; diff --git a/docs/docs/apis/openidoauth/grant-types.md b/docs/docs/apis/openidoauth/grant-types.md index e6562941cc..be4ce95545 100644 --- a/docs/docs/apis/openidoauth/grant-types.md +++ b/docs/docs/apis/openidoauth/grant-types.md @@ -1,5 +1,6 @@ --- -title: Grant Types +title: Grant Types in ZITADEL +sidebar_label: Grant Types --- For a list of supported or unsupported `Grant Types` please have a look at the table below. @@ -75,19 +76,19 @@ Key JSON JWT -| Claim | Example | Description | -|:------|:--------------------------|:--------------------------------------------------------------------------------------------------------------| -| aud | `"https://{your_domain}"` | String or Array of intended audiences MUST include ZITADEL's issuing domain | -| exp | `1605183582` | Unix timestamp of the expiry | -| iat | `1605179982` | Unix timestamp of the creation singing time of the JWT, MUST NOT be older than 1h | -| iss | `"77479219772321307"` | String which represents the requesting party (owner of the key), normally the `userId` from the json key file | -| sub | `"77479219772321307"` | The subject ID of the service user, normally the `userId` from the json key file | +| Claim | Example | Description | +|:------|:-------------------------|:--------------------------------------------------------------------------------------------------------------| +| aud | `"https://$CUSTOM-DOMAIN"` | String or Array of intended audiences MUST include ZITADEL's issuing domain | +| exp | `1605183582` | Unix timestamp of the expiry | +| iat | `1605179982` | Unix timestamp of the creation singing time of the JWT, MUST NOT be older than 1h | +| iss | `"77479219772321307"` | String which represents the requesting party (owner of the key), normally the `userId` from the json key file | +| sub | `"77479219772321307"` | The subject ID of the service user, normally the `userId` from the json key file | ```JSON { "iss": "77479219772321307", "sub": "77479219772321307", - "aud": "https://{your_domain}", + "aud": "https://$CUSTOM-DOMAIN", "exp": 1605183582, "iat": 1605179982 } diff --git a/docs/docs/apis/openidoauth/scopes.md b/docs/docs/apis/openidoauth/scopes.md index 387115d77d..e948e84fcc 100644 --- a/docs/docs/apis/openidoauth/scopes.md +++ b/docs/docs/apis/openidoauth/scopes.md @@ -1,5 +1,6 @@ --- -title: Scopes +title: Scopes in ZITADEL +sidebar_label: Scopes --- ZITADEL supports the usage of scopes as way of requesting information from the IAM and also instruct ZITADEL to do certain operations. diff --git a/docs/docs/apis/saml/endpoints.md b/docs/docs/apis/saml/endpoints.md index eeae326d9a..bb0e765da0 100644 --- a/docs/docs/apis/saml/endpoints.md +++ b/docs/docs/apis/saml/endpoints.md @@ -1,10 +1,10 @@ --- -title: SAML endpoints +title: SAML Endpoints in ZITADEL --- ## SAML 2.0 metadata -The SAML Metadata is located within the issuer domain. This would give us {your_domain}/saml/v2/metadata. +The SAML Metadata is located within the issuer domain. This would give us $CUSTOM-DOMAIN/saml/v2/metadata. This metadata contains all the information defined in the spec. @@ -13,14 +13,14 @@ spec.** [Metadata for the OASIS Security Assertion Markup Language (SAML) V2.0 ## Certificate endpoint -{your_domain}/saml/v2/certificate +$CUSTOM-DOMAIN/saml/v2/certificate The certificate endpoint provides the certificate which is used to sign the responses for download, for easier use with different service providers which want the certificate separately instead of inside the metadata. ## SSO endpoint -{your_domain}/saml/v2/SSO +$CUSTOM-DOMAIN/saml/v2/SSO The SSO endpoint is the starting point for all initial user authentications. The user agent (browser) will be redirected to this endpoint to authenticate the user. @@ -35,10 +35,10 @@ spec.** [Bindings for the OASIS Security Assertion Markup Language (SAML) V2.0 | Parameter | Description | |---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| RelayState | ID to associate the exchange with the original request. | +| RelayState | (Optional) ID to associate the exchange with the original request. | | SAMLRequest | The request made to the SAML IDP. (base64 encoded) | | SigAlg | Algorithm used to sign the request, only if binding is 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' as signature has to be provided es separate parameter. (base64 encoded) | -| Signature | Signature of the request as parameter with 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' binding. (base64 encoded) | +| Signature | Signature of the request as parameter with 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' binding. (base64 encoded) | ### Successful response diff --git a/docs/docs/apis/statuscodes.mdx b/docs/docs/apis/statuscodes.mdx index d4f5880026..f23454265b 100644 --- a/docs/docs/apis/statuscodes.mdx +++ b/docs/docs/apis/statuscodes.mdx @@ -1,5 +1,6 @@ --- -title: GRPC Status Codes +title: GRPC Status Codes in ZITADEL +sidebar_label: GRPC Status Codes --- | GRPC Number | GRPC Code | HTTP Status Code | HTTP Status Text |Description | diff --git a/docs/docs/concepts/architecture/secrets.md b/docs/docs/concepts/architecture/secrets.md index f662c77264..47b1567f65 100644 --- a/docs/docs/concepts/architecture/secrets.md +++ b/docs/docs/concepts/architecture/secrets.md @@ -1,5 +1,6 @@ --- -title: Secrets +title: How ZITADEL Processes and Stores Secrets +sidebar_label: Secrets --- In this chapter you can find information of how ZITADEL processes and stores secrets and credentials in a secure fashion. diff --git a/docs/docs/concepts/architecture/software.md b/docs/docs/concepts/architecture/software.md index d820e2c897..110ddfbce2 100644 --- a/docs/docs/concepts/architecture/software.md +++ b/docs/docs/concepts/architecture/software.md @@ -1,5 +1,6 @@ --- -title: Software +title: ZITADEL's Software Architecture +sidebar_label: Software Architecture --- ZITADEL is built with two essential patterns. Event Sourcing (ES) and Command and Query Responsibility Segregation (CQRS). @@ -14,7 +15,7 @@ Each ZITADEL binary contains all components necessary to serve traffic From serving the API, rendering GUI's, background processing of events and task. This All in One (AiO) approach makes operating ZITADEL simple. -## Software Structure +## The Architecture ZITADELs software architecture is built around multiple components at different levels. This chapter should give you an idea of the components as well as the different layers. diff --git a/docs/docs/concepts/architecture/solution.md b/docs/docs/concepts/architecture/solution.md index 826bb976c4..710e7ae306 100644 --- a/docs/docs/concepts/architecture/solution.md +++ b/docs/docs/concepts/architecture/solution.md @@ -1,5 +1,6 @@ --- -title: Deployment +title: ZITADEL's Deployment Architecture +sidebar_label: Deployment Architecture --- ## High Availability diff --git a/docs/docs/concepts/eventstore/implementation.md b/docs/docs/concepts/eventstore/implementation.md index 54e9c713fe..1b92ab4d9f 100644 --- a/docs/docs/concepts/eventstore/implementation.md +++ b/docs/docs/concepts/eventstore/implementation.md @@ -1,5 +1,6 @@ --- -title: Implementation +title: ZITADEL Database Structure +sidebar_label: Database Structure --- This documentation gives you an insight into the structure of the ZITADEL database. diff --git a/docs/docs/concepts/eventstore/overview.md b/docs/docs/concepts/eventstore/overview.md index fd564ce96b..0053f918a6 100644 --- a/docs/docs/concepts/eventstore/overview.md +++ b/docs/docs/concepts/eventstore/overview.md @@ -1,5 +1,5 @@ --- -title: Eventstore +title: ZITADEL Event Store sidebar_label: Overview --- diff --git a/docs/docs/concepts/features/actions.md b/docs/docs/concepts/features/actions.md index a8c5d97e43..a91001fce5 100644 --- a/docs/docs/concepts/features/actions.md +++ b/docs/docs/concepts/features/actions.md @@ -1,5 +1,6 @@ --- -title: Actions +title: ZITADEL Actions +sidebar_label: Actions --- By using ZITADEL actions, you can manipulate ZITADELs behavior on specific Events. diff --git a/docs/docs/concepts/features/audit-trail.md b/docs/docs/concepts/features/audit-trail.md index 12cd77606e..bad91b01d0 100644 --- a/docs/docs/concepts/features/audit-trail.md +++ b/docs/docs/concepts/features/audit-trail.md @@ -1,5 +1,6 @@ --- -title: Audit Trail +title: ZITADEL's In-built Audit Trail +sidebar_label: Audit Trail --- ZITADEL provides you with an built-in audit trail to track all changes and events over an unlimited period of time. @@ -26,7 +27,7 @@ The same view is available on several other objects such as organization or proj ### Event View Administrators can see all events across an instance and filter them directly in [Console](/docs/guides/manage/console/overview). -Go to your instance settings and then click on the Tab **Events** to open the Event Viewer or browse to $YOUR_DOMAIN/ui/console/events +Go to your instance settings and then click on the Tab **Events** to open the Event Viewer or browse to $CUSTOM-DOMAIN/ui/console/events ![Event viewer](/img/concepts/audit-trail/event-viewer.png) diff --git a/docs/docs/concepts/features/identity-brokering.md b/docs/docs/concepts/features/identity-brokering.md index 9b6c98f583..bb4b01d344 100644 --- a/docs/docs/concepts/features/identity-brokering.md +++ b/docs/docs/concepts/features/identity-brokering.md @@ -1,5 +1,6 @@ --- -title: Identity Brokering +title: Identity Brokering in ZITADEL +sidebar_label: Identity Brokering --- ## What are Identity Brokering and Federated Identities? diff --git a/docs/docs/concepts/features/selfservice.md b/docs/docs/concepts/features/selfservice.md index a39e99ba49..1376699928 100644 --- a/docs/docs/concepts/features/selfservice.md +++ b/docs/docs/concepts/features/selfservice.md @@ -1,5 +1,6 @@ --- -title: Self-Service +title: Self Service in ZITADEL +sidebar_label: Self Service --- ZITADEL allows users to perform many tasks themselves. @@ -138,7 +139,7 @@ A client can also implement this, by calling the [specific endpoint](/apis/openi ## Profile These actions are available for authenticated users only. -ZITADEL provides a self-service UI for the user profile out-of-the box under the path _{your_domain}/ui/console/users/me_. +ZITADEL provides a self-service UI for the user profile out-of-the box under the path _$CUSTOM-DOMAIN/ui/console/users/me_. You can also implement your own version in your application by using our APIs. ### Change password diff --git a/docs/docs/concepts/structure/applications.md b/docs/docs/concepts/structure/applications.md index 4035d088f2..1a3343b620 100644 --- a/docs/docs/concepts/structure/applications.md +++ b/docs/docs/concepts/structure/applications.md @@ -1,5 +1,6 @@ --- -title: Applications +title: ZITADEL Applications +sidebar_label: Applications --- # Applications diff --git a/docs/docs/concepts/structure/granted_projects.md b/docs/docs/concepts/structure/granted_projects.md index 83fec03300..5ab2e84603 100644 --- a/docs/docs/concepts/structure/granted_projects.md +++ b/docs/docs/concepts/structure/granted_projects.md @@ -1,5 +1,6 @@ --- -title: Granted Projects +title: ZITADEL's Granted Projects +sidebar_label: Granted Projects --- # Granted Project diff --git a/docs/docs/concepts/structure/instance.mdx b/docs/docs/concepts/structure/instance.mdx index d407ab679d..2601be62ee 100644 --- a/docs/docs/concepts/structure/instance.mdx +++ b/docs/docs/concepts/structure/instance.mdx @@ -1,5 +1,6 @@ --- -title: Instance +title: ZITADEL Instances +sidebar_label: Instances --- ## Instance Structure diff --git a/docs/docs/concepts/structure/managers.mdx b/docs/docs/concepts/structure/managers.mdx index d621f2f67e..2a709fb34e 100644 --- a/docs/docs/concepts/structure/managers.mdx +++ b/docs/docs/concepts/structure/managers.mdx @@ -1,5 +1,6 @@ --- -title: Managers +title: ZITADEL Managers +sidebar_label: Managers --- import ManagerDescription from "./_manager_description.mdx"; diff --git a/docs/docs/concepts/structure/organizations.md b/docs/docs/concepts/structure/organizations.md index 0ea94329e1..c3366bab12 100644 --- a/docs/docs/concepts/structure/organizations.md +++ b/docs/docs/concepts/structure/organizations.md @@ -1,5 +1,6 @@ --- -title: Organizations +title: ZITADEL Organizations +sidebar_label: Organizations --- import OrgDescription from './_org_description.mdx'; diff --git a/docs/docs/concepts/structure/policies.md b/docs/docs/concepts/structure/policies.md index 6b4453dc7f..6db4e365fd 100644 --- a/docs/docs/concepts/structure/policies.md +++ b/docs/docs/concepts/structure/policies.md @@ -1,8 +1,9 @@ --- -title: Settings/Policies +title: ZITADEL Settings and Policies +sidebar_label: Setting and Policies --- -Settings and policies are configurations of all the different parts of the Instance or an organization. For all parts we have a suitable default in the Instance. +Settings and policies are configurations of all the different parts of the instance or an organization. For all parts we have a suitable default in the instance. The default configuration can be overridden for each organization, some policies are currently only available on the instance level. Learn more about our different policies [here](/guides/manage/console/instance-settings.mdx). API wise, settings are often called policies. You can read the proto and swagger definitions [here](../../apis/introduction.mdx). diff --git a/docs/docs/concepts/structure/projects.md b/docs/docs/concepts/structure/projects.md index 63d29c5dec..16b6bf7e5b 100644 --- a/docs/docs/concepts/structure/projects.md +++ b/docs/docs/concepts/structure/projects.md @@ -1,5 +1,6 @@ --- -title: Projects +title: ZITADEL Projects +sidebar_label: Projects --- # Project diff --git a/docs/docs/concepts/structure/users.md b/docs/docs/concepts/structure/users.md index a765e36969..5ece5af7bd 100644 --- a/docs/docs/concepts/structure/users.md +++ b/docs/docs/concepts/structure/users.md @@ -1,5 +1,6 @@ --- -title: Users +title: ZITADEL Users +sidebar_label: Users --- ## Types of users diff --git a/docs/docs/examples/call-zitadel-api/dot-net.md b/docs/docs/examples/call-zitadel-api/dot-net.md index 31e7ce7cfc..05bc6a1916 100644 --- a/docs/docs/examples/call-zitadel-api/dot-net.md +++ b/docs/docs/examples/call-zitadel-api/dot-net.md @@ -1,5 +1,6 @@ --- -title: .NET +title: Integrate ZITADEL into a .NET Application +sidebar_label: .NET --- This integration guide shows you how to integrate **ZITADEL** into your .NET application. diff --git a/docs/docs/examples/call-zitadel-api/go.md b/docs/docs/examples/call-zitadel-api/go.md index 52435240df..2ca6a4330f 100644 --- a/docs/docs/examples/call-zitadel-api/go.md +++ b/docs/docs/examples/call-zitadel-api/go.md @@ -1,5 +1,6 @@ --- -title: Go +title: Integrate ZITADEL into a Go Application +sidebar_label: Go --- This integration guide shows you how to integrate **ZITADEL** into your Go application. diff --git a/docs/docs/examples/identity-proxy/oauth2-proxy.md b/docs/docs/examples/identity-proxy/oauth2-proxy.md index b1a470df5c..e63f91230e 100644 --- a/docs/docs/examples/identity-proxy/oauth2-proxy.md +++ b/docs/docs/examples/identity-proxy/oauth2-proxy.md @@ -43,7 +43,7 @@ provider = "oidc" user_id_claim = "sub" #uses the subject as ID instead of the email provider_display_name = "ZITADEL" redirect_url = "http://127.0.0.1:4180/oauth2/callback" -oidc_issuer_url = "https://{your_domain}.zitadel.cloud" +oidc_issuer_url = "https://$CUSTOM-DOMAIN" upstreams = [ "https://example.corp.com" ] diff --git a/docs/docs/examples/introduction.mdx b/docs/docs/examples/introduction.mdx index b29c5bcb2d..f88ce3afd9 100644 --- a/docs/docs/examples/introduction.mdx +++ b/docs/docs/examples/introduction.mdx @@ -1,5 +1,5 @@ --- -title: Overview of examples, quickstarts, and SDKs +title: Overview of ZITADEL Examples, Quickstarts, and SDKs sidebar_label: Overview --- diff --git a/docs/docs/examples/login/angular.md b/docs/docs/examples/login/angular.md index 3eaa9ca2c6..9d13988a9e 100644 --- a/docs/docs/examples/login/angular.md +++ b/docs/docs/examples/login/angular.md @@ -1,5 +1,6 @@ --- -title: Angular +title: ZITADEL with Angular +sidebar_label: Angular --- This integration guide demonstrates the recommended way to incorporate ZITADEL into your Angular application. diff --git a/docs/docs/examples/login/flutter.md b/docs/docs/examples/login/flutter.md index e0e58c2065..c144ccd0c8 100644 --- a/docs/docs/examples/login/flutter.md +++ b/docs/docs/examples/login/flutter.md @@ -1,5 +1,7 @@ --- -title: Flutter +title: ZITADEL with Flutter +sidebar_label: Flutter + --- This guide demonstrates how you integrate **ZITADEL** into a Flutter app. It refers to our example on [GitHub](https://github.com/zitadel/zitadel_flutter) diff --git a/docs/docs/examples/login/nextjs-b2b.md b/docs/docs/examples/login/nextjs-b2b.md index 7dbd33f1f2..f1e31fb50b 100644 --- a/docs/docs/examples/login/nextjs-b2b.md +++ b/docs/docs/examples/login/nextjs-b2b.md @@ -1,8 +1,9 @@ --- -title: Next.js B2B Scenario +title: ZITADEL with Next.js - A B2B Scenario +sidebar_label: Next.js - B2B --- -This is our Zitadel [Next.js](https://nextjs.org/) B2B template. It shows how to authenticate as a user with multiple organizations. The application shows your users roles on the selected organizations, other projects your organization is allowed to use and other users having a grant to use the application. +This is our ZITADEL [Next.js](https://nextjs.org/) B2B template. It shows how to authenticate as a user with multiple organizations. The application shows your users roles on the selected organizations, other projects your organization is allowed to use and other users having a grant to use the application. If you need more info on B2B use cases consider reading our guide for the [B2B solution scenario](/guides/solution-scenarios/b2b.mdx). diff --git a/docs/docs/examples/login/nextjs.md b/docs/docs/examples/login/nextjs.md index 5362a33911..76a8770ded 100644 --- a/docs/docs/examples/login/nextjs.md +++ b/docs/docs/examples/login/nextjs.md @@ -1,5 +1,6 @@ --- -title: Next.js +title: ZITADEL with Next.js +sidebar_label: Next.js --- This is our Zitadel [Next.js](https://nextjs.org/) template. It shows how to authenticate as a user and retrieve user information from the OIDC endpoint. diff --git a/docs/docs/examples/login/react.mdx b/docs/docs/examples/login/react.mdx index f346cc55e7..5fad3f644f 100644 --- a/docs/docs/examples/login/react.mdx +++ b/docs/docs/examples/login/react.mdx @@ -1,5 +1,6 @@ --- -title: React +title: ZITADEL with React +sidebar_label: React --- This Integration guide shows you the recommended way to integrate **ZITADEL** into your React Application. diff --git a/docs/docs/examples/sdks.md b/docs/docs/examples/sdks.md index 4c56c5053a..6d58fa5f9a 100644 --- a/docs/docs/examples/sdks.md +++ b/docs/docs/examples/sdks.md @@ -1,5 +1,6 @@ --- -title: SDKs +title: ZITADEL SDKs +sidebar_label: SDKs --- On this page you find our official SDKs, links to supporting frameworks and providers, and resources to help with SDKs. diff --git a/docs/docs/examples/secure-api/dot-net.md b/docs/docs/examples/secure-api/dot-net.md index f1bb673d1b..9473646c87 100644 --- a/docs/docs/examples/secure-api/dot-net.md +++ b/docs/docs/examples/secure-api/dot-net.md @@ -1,4 +1,5 @@ --- -title: .NET +title: ZITADEL with .NET +sidebar_label: .NET --- Coming soon \ No newline at end of file diff --git a/docs/docs/examples/secure-api/go.md b/docs/docs/examples/secure-api/go.md index 5663cdfb6f..60609a0e38 100644 --- a/docs/docs/examples/secure-api/go.md +++ b/docs/docs/examples/secure-api/go.md @@ -1,5 +1,6 @@ --- -title: Go +title: ZITADEL with Go +sidebar_label: Go --- This integration guide shows you how to integrate **ZITADEL** into your Go API. It demonstrates how to secure your API using diff --git a/docs/docs/examples/secure-api/python-flask.mdx b/docs/docs/examples/secure-api/python-flask.mdx index 0c355ffec7..6b83049a16 100644 --- a/docs/docs/examples/secure-api/python-flask.mdx +++ b/docs/docs/examples/secure-api/python-flask.mdx @@ -1,5 +1,6 @@ --- -title: Python +title: ZITADEL with Python +sidebar_label: Python --- This example shows you how to secure a Python3 Flask API with both authentication and authorization using ZITADEL. diff --git a/docs/docs/guides/integrate/access-zitadel-apis.md b/docs/docs/guides/integrate/access-zitadel-apis.md index 2509c07243..545e9d75a1 100644 --- a/docs/docs/guides/integrate/access-zitadel-apis.md +++ b/docs/docs/guides/integrate/access-zitadel-apis.md @@ -44,7 +44,7 @@ Use the scope `urn:zitadel:iam:org:project:id:zitadel:aud` to include the ZITADE ```bash curl --request POST \ - --url {your_domain}/oauth/v2/token \ + --url $CUSTOM-DOMAIN/oauth/v2/token \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \ --data scope='openid profile email urn:zitadel:iam:org:project:id:zitadel:aud' \ diff --git a/docs/docs/guides/integrate/access-zitadel-system-api.md b/docs/docs/guides/integrate/access-zitadel-system-api.md index 598a096dc7..08bef3656c 100644 --- a/docs/docs/guides/integrate/access-zitadel-system-api.md +++ b/docs/docs/guides/integrate/access-zitadel-system-api.md @@ -59,7 +59,7 @@ The JWT payload will need to contain the following claims: { "iss": "", "sub": "", - "aud": "", + "aud": "", "exp": , "iat": } @@ -95,7 +95,7 @@ Now that you configured ZITADEL and created a JWT, you can call the System API a ```bash curl --request POST \ - --url {your_domain}/system/v1/instances/_search \ + --url $CUSTOM-DOMAIN/system/v1/instances/_search \ --header 'Authorization: Bearer {token}' \ --header 'Content-Type: application/json' ``` diff --git a/docs/docs/guides/integrate/authenticated-mongodb-charts.md b/docs/docs/guides/integrate/authenticated-mongodb-charts.md index aee8dfc4a1..ed6a72bca5 100644 --- a/docs/docs/guides/integrate/authenticated-mongodb-charts.md +++ b/docs/docs/guides/integrate/authenticated-mongodb-charts.md @@ -1,5 +1,6 @@ --- -title: Authenticated MongoDB Charts +title: Embed Authenticated MongoDB Charts Using ZITADEL +sidebar_label: Authenticated MongoDB Charts --- This integration guide shows how you can embed authenticated MongoDB Charts in your web application using ZITADEL as authentication provider. @@ -28,7 +29,7 @@ Configure ZITADEL as your _Custom JWT Provider_ following the [MongoDB docs](htt Configure the following values: - Signing Algorithm: RS256 - Signing Key: JWK or JWKS URL -- JWKS: https://{your_domain}.zitadel.cloud/oauth/v2/keys +- JWKS: https://$CUSTOM-DOMAIN/oauth/v2/keys - Audience: Your app's client ID which you copied when you created the ZITADEL app Your configuration should look similar to this: diff --git a/docs/docs/guides/integrate/client-credentials.md b/docs/docs/guides/integrate/client-credentials.md index 584ce82406..bd0ccf85f3 100644 --- a/docs/docs/guides/integrate/client-credentials.md +++ b/docs/docs/guides/integrate/client-credentials.md @@ -41,7 +41,7 @@ You will need to craft a POST request to ZITADEL's token endpoint: ```bash curl --request POST \ - --url https://{your_domain}.zitadel.cloud/oauth/v2/token \ + --url https://$CUSTOM-DOMAIN/oauth/v2/token \ --header 'Content-Type: application/x-www-form-urlencoded' \ --header 'Authorization: Basic ${BASIC_AUTH}' \ --data grant_type=client_credentials \ @@ -72,7 +72,7 @@ In this example we read the organization of the service user. ```bash curl --request GET \ - --url {your-domain}/management/v1/orgs/me \ + --url $CUSTOM-DOMAIN/management/v1/orgs/me \ --header 'Authorization: Bearer ${TOKEN}' ``` diff --git a/docs/docs/guides/integrate/event-api.md b/docs/docs/guides/integrate/event-api.md index bf0f8d43c0..44f9aaef11 100644 --- a/docs/docs/guides/integrate/event-api.md +++ b/docs/docs/guides/integrate/event-api.md @@ -1,5 +1,6 @@ --- -title: Get events from ZITADEL +title: Get Events from ZITADEL +sidebar_label: Events --- ZITADEL leverages the power of eventsourcing, meaning every action and change within the system generates a corresponding event that is stored in the database. @@ -23,7 +24,7 @@ To further restrict your result you can add the following filters: ```bash curl --request POST \ - --url $YOUR-DOMAIN/admin/v1/events/_search \ + --url $CUSTOM-DOMAIN/admin/v1/events/_search \ --header "Authorization: Bearer $TOKEN" ``` @@ -33,7 +34,7 @@ To be able to filter for the different event types ZITADEL knows, you can reques ```bash curl --request POST \ ---url $YOUR-DOMAIN/admin/v1/events/types/_search \ +--url $CUSTOM-DOMAIN/admin/v1/events/types/_search \ --header "Authorization: Bearer $TOKEN" \ --header 'Content-Type: application/json' \ ' @@ -69,7 +70,7 @@ To be able to filter for the different aggregate types (resources) ZITADEL knows ```bash curl --request POST \ - --url $YOUR-DOMAIN/admin/v1/aggregates/types/_search \ + --url $CUSTOM-DOMAIN/admin/v1/aggregates/types/_search \ --header "Authorization: Bearer $TOKEN" \ --header 'Content-Type: application/json' ``` @@ -100,7 +101,7 @@ This example shows you how to get all events from users, filtered with the creat ```bash curl --request POST \ - --url $YOUR-DOMAIN/admin/v1/events/_search \ + --url $CUSTOM-DOMAIN/admin/v1/events/_search \ --header "Authorization: Bearer $TOKEN" \ --header 'Content-Type: application/json' \ --data '{ @@ -120,7 +121,7 @@ Also we include the refresh tokens in this example to know when the user has bec ```bash curl --request POST \ - --url $YOUR-DOMAIN/admin/v1/events/_search \ + --url $CUSTOM-DOMAIN/admin/v1/events/_search \ --header "Authorization: Bearer $TOKEN" \ --header 'Content-Type: application/json' \ --data '{ @@ -146,7 +147,7 @@ In this case this are the following events: ```bash curl --request POST \ - --url $YOUR-DOMAIN/admin/v1/events/_search \ + --url $CUSTOM-DOMAIN/admin/v1/events/_search \ --header "Authorization: Bearer $TOKEN" \ --header 'Content-Type: application/json' \ --data '{ diff --git a/docs/docs/guides/integrate/identity-providers/apple.mdx b/docs/docs/guides/integrate/identity-providers/apple.mdx index cec4a76a70..c5a36c3717 100644 --- a/docs/docs/guides/integrate/identity-providers/apple.mdx +++ b/docs/docs/guides/integrate/identity-providers/apple.mdx @@ -1,5 +1,5 @@ --- -title: Configure Apple as Identity Provider +title: Configure Apple as an Identity Provider in ZITADEL sidebar_label: Apple --- @@ -35,7 +35,6 @@ import TestSetup from './_test_setup.mdx'; 8. Add the redirect uri in the Return URLs - {your-domain}/ui/login/login/externalidp/callback/form - Example redirect url for the domain `https://acme-gzoe4x.zitadel.cloud` would look like this: `https://acme-gzoe4x.zitadel.cloud/ui/login/login/externalidp/callback/form` -9. Save the Client ID and Client secret ![Apple Service](/img/guides/apple_service_create.png) diff --git a/docs/docs/guides/integrate/identity-providers/azure-ad.mdx b/docs/docs/guides/integrate/identity-providers/azure-ad.mdx index b87c08f1bf..23ef4df8bc 100644 --- a/docs/docs/guides/integrate/identity-providers/azure-ad.mdx +++ b/docs/docs/guides/integrate/identity-providers/azure-ad.mdx @@ -1,5 +1,5 @@ --- -title: Configure Azure AD as Identity Provider +title: Configure Azure AD as an Identity Provider in ZITADEL sidebar_label: Azure AD --- diff --git a/docs/docs/guides/integrate/identity-providers/github.mdx b/docs/docs/guides/integrate/identity-providers/github.mdx index 5ab11dd554..e6dcd39cb5 100644 --- a/docs/docs/guides/integrate/identity-providers/github.mdx +++ b/docs/docs/guides/integrate/identity-providers/github.mdx @@ -1,5 +1,5 @@ --- -title: Configure GitHub as Identity Provider +title: Configure GitHub as an Identity Provider in ZITADEL sidebar_label: GitHub --- diff --git a/docs/docs/guides/integrate/identity-providers/gitlab.mdx b/docs/docs/guides/integrate/identity-providers/gitlab.mdx index fe3a89e97a..275a7bc179 100644 --- a/docs/docs/guides/integrate/identity-providers/gitlab.mdx +++ b/docs/docs/guides/integrate/identity-providers/gitlab.mdx @@ -1,5 +1,5 @@ --- -title: Configure GitLab as Identity Provider +title: Configure GitLab as an Identity Provider in ZITADEL sidebar_label: GitLab --- diff --git a/docs/docs/guides/integrate/identity-providers/google.mdx b/docs/docs/guides/integrate/identity-providers/google.mdx index e2ca360018..522dc66153 100644 --- a/docs/docs/guides/integrate/identity-providers/google.mdx +++ b/docs/docs/guides/integrate/identity-providers/google.mdx @@ -1,5 +1,5 @@ --- -title: Configure Google as Identity Provider +title: Configure Google as an Identity Provider in ZITADEL sidebar_label: Google --- diff --git a/docs/docs/guides/integrate/identity-providers/ldap.mdx b/docs/docs/guides/integrate/identity-providers/ldap.mdx index fb573cfcff..bfeb62b265 100644 --- a/docs/docs/guides/integrate/identity-providers/ldap.mdx +++ b/docs/docs/guides/integrate/identity-providers/ldap.mdx @@ -1,5 +1,5 @@ --- -title: Configure LDAP as Identity Provider +title: Configure LDAP as an Identity Provider in ZITADEL sidebar_label: LDAP --- diff --git a/docs/docs/guides/integrate/identity-providers/okta.mdx b/docs/docs/guides/integrate/identity-providers/okta.mdx index 2485cc669b..60eca1f3c8 100644 --- a/docs/docs/guides/integrate/identity-providers/okta.mdx +++ b/docs/docs/guides/integrate/identity-providers/okta.mdx @@ -1,5 +1,5 @@ --- -title: Configure OKTA as Identity Provider +title: Configure OKTA as an Identity Provider in ZITADEL sidebar_label: OKTA generic OIDC id: okta --- diff --git a/docs/docs/guides/integrate/identity-providers/openldap.mdx b/docs/docs/guides/integrate/identity-providers/openldap.mdx index 3b3c045130..aaa4d9e444 100644 --- a/docs/docs/guides/integrate/identity-providers/openldap.mdx +++ b/docs/docs/guides/integrate/identity-providers/openldap.mdx @@ -1,5 +1,5 @@ --- -title: Configure local OpenLDAP as Identity Provider +title: Configure Local OpenLDAP as an Identity Provider in ZITADEL sidebar_label: Local OpenLDAP --- diff --git a/docs/docs/guides/integrate/login-ui/external-login.mdx b/docs/docs/guides/integrate/login-ui/external-login.mdx index 1e1c34e847..da6d0ef9ad 100644 --- a/docs/docs/guides/integrate/login-ui/external-login.mdx +++ b/docs/docs/guides/integrate/login-ui/external-login.mdx @@ -1,5 +1,5 @@ --- -title: Handle External Login +title: Handle External Logins in ZITADEL sidebar_label: External Identity Provider --- diff --git a/docs/docs/guides/integrate/login-ui/logout.mdx b/docs/docs/guides/integrate/login-ui/logout.mdx index 095f3b792e..47a11fd81d 100644 --- a/docs/docs/guides/integrate/login-ui/logout.mdx +++ b/docs/docs/guides/integrate/login-ui/logout.mdx @@ -1,5 +1,6 @@ --- -title: Logout +title: Logging Out via ZITADEL +sidebar_label: Logout --- import Logout from './_logout.mdx'; diff --git a/docs/docs/guides/integrate/login-ui/mfa.mdx b/docs/docs/guides/integrate/login-ui/mfa.mdx index 6e8a2fd37d..3847f3860d 100644 --- a/docs/docs/guides/integrate/login-ui/mfa.mdx +++ b/docs/docs/guides/integrate/login-ui/mfa.mdx @@ -1,6 +1,6 @@ --- -title: Multi-Factor (MFA) -sidebar_label: Multi-Factor (MFA) +title: Multi-Factor Authentication(MFA) in ZITADEL +sidebar_label: Multi-Factor Authentication(MFA) --- import MfaOptions from './_list-mfa-options.mdx'; diff --git a/docs/docs/guides/integrate/login-ui/oidc-standard.mdx b/docs/docs/guides/integrate/login-ui/oidc-standard.mdx index 84da8a46b8..64f8cef1f7 100644 --- a/docs/docs/guides/integrate/login-ui/oidc-standard.mdx +++ b/docs/docs/guides/integrate/login-ui/oidc-standard.mdx @@ -1,5 +1,6 @@ --- -title: OIDC Standard +title: Support for the OpenID Connect(OIDC) Standard in ZITADEL +sidebar_label: OIDC Standard --- To build your own login ui for your own application it is not necessary to have the OIDC standard included or any additional work that has to be done. diff --git a/docs/docs/guides/integrate/login-ui/passkey.mdx b/docs/docs/guides/integrate/login-ui/passkey.mdx index 458da8f233..28f9c7832a 100644 --- a/docs/docs/guides/integrate/login-ui/passkey.mdx +++ b/docs/docs/guides/integrate/login-ui/passkey.mdx @@ -1,5 +1,5 @@ --- -title: Passkeys +title: Passkeys in ZITADEL sidebar_label: Passkeys --- diff --git a/docs/docs/guides/integrate/login-ui/password-reset.mdx b/docs/docs/guides/integrate/login-ui/password-reset.mdx index 184777cb81..afa3c803cb 100644 --- a/docs/docs/guides/integrate/login-ui/password-reset.mdx +++ b/docs/docs/guides/integrate/login-ui/password-reset.mdx @@ -1,5 +1,6 @@ --- -title: Password Reset/Change +title: Password Reset/Change in ZITADEL +sidebar_label: Password Reset/Change --- When your user is on the password screen and has forgotten his password you will probably want him to be able to reset by himself. diff --git a/docs/docs/guides/integrate/login-ui/username-password.mdx b/docs/docs/guides/integrate/login-ui/username-password.mdx index d94186111a..65eb61ba54 100644 --- a/docs/docs/guides/integrate/login-ui/username-password.mdx +++ b/docs/docs/guides/integrate/login-ui/username-password.mdx @@ -1,5 +1,5 @@ --- -title: Register and Login User with Password +title: Register and Login User with Password in ZITADEL sidebar_label: Username and Password --- diff --git a/docs/docs/guides/integrate/login-users.mdx b/docs/docs/guides/integrate/login-users.mdx index b6e7096d53..cf719ed589 100644 --- a/docs/docs/guides/integrate/login-users.mdx +++ b/docs/docs/guides/integrate/login-users.mdx @@ -1,5 +1,6 @@ --- -title: Login Users into your Application +title: Login Users into your Application with ZITADEL +sidebar_label: Login --- import Tabs from "@theme/Tabs"; diff --git a/docs/docs/guides/integrate/logout.md b/docs/docs/guides/integrate/logout.md index 5778fbc772..71c139bb9f 100644 --- a/docs/docs/guides/integrate/logout.md +++ b/docs/docs/guides/integrate/logout.md @@ -1,5 +1,6 @@ --- -title: Logout +title: Log Out Users from an Application with ZITADEL +sidebar_label: Logout --- This guide shows you the different concepts and use cases of the logout process and how to use it in ZITADEL. @@ -36,7 +37,7 @@ If you have specified some post_logout_redirect_uris on your client you have to So ZITADEL is able to read the configured redirect uris. ``` -GET {your_domain}/oidc/v1/end_session +GET $CUSTOM-DOMAIN/oidc/v1/end_session ?id_token_hint={id_token} &post_logout_redirect_uri=https://rp.example.com/logged_out &state=random_string diff --git a/docs/docs/guides/integrate/oauth-recommended-flows.md b/docs/docs/guides/integrate/oauth-recommended-flows.md index 835d9ed8fd..74bb56a461 100644 --- a/docs/docs/guides/integrate/oauth-recommended-flows.md +++ b/docs/docs/guides/integrate/oauth-recommended-flows.md @@ -1,5 +1,5 @@ --- -title: Recommended authorization flows +title: Recommended Authorization Flows --- diff --git a/docs/docs/guides/integrate/pat.md b/docs/docs/guides/integrate/pat.md index 102209fb49..e3be879c26 100644 --- a/docs/docs/guides/integrate/pat.md +++ b/docs/docs/guides/integrate/pat.md @@ -1,5 +1,6 @@ --- -title: PAT (Personal Access Token) +title: ZITADEL's Personal Access Tokens(PATs) +sidebar_label: Personal Access Tokens(PATs) --- @@ -40,6 +41,6 @@ In this example we read the organization of the service user. ```bash curl --request GET \ - --url {your-domain}/management/v1/orgs/me \ + --url $CUSTOM-DOMAIN/management/v1/orgs/me \ --header 'Authorization: Bearer {PAT}' ``` \ No newline at end of file diff --git a/docs/docs/guides/integrate/private-key-jwt.md b/docs/docs/guides/integrate/private-key-jwt.md index 227af7a8e7..54288382e9 100644 --- a/docs/docs/guides/integrate/private-key-jwt.md +++ b/docs/docs/guides/integrate/private-key-jwt.md @@ -1,5 +1,6 @@ --- -title: Private Key JWT +title: Service Users in ZITADEL +sidebar_label: Service Users --- This is a guide on how to create service users in ZITADEL. You can read more about users [here](/concepts/structure/users.md). @@ -67,7 +68,7 @@ Payload { "iss": "100507859606888466", "sub": "100507859606888466", - "aud": "https://{your_domain}.zitadel.cloud", + "aud": "https://$CUSTOM-DOMAIN", "iat": [Current UTC timestamp, e.g. 1605179982, max. 1 hour ago], "exp": [UTC timestamp, e.g. 1605183582] } @@ -89,7 +90,7 @@ With the encoded JWT from the prior step, you will need to craft a POST request ```bash curl --request POST \ - --url https://{your_domain}.zitadel.cloud/oauth/v2/token \ + --url https:/$CUSTOM-DOMAIN/oauth/v2/token \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \ --data scope='openid profile email' \ @@ -121,7 +122,7 @@ For this example let's call the userinfo endpoint to verify that our access toke ```bash curl --request POST \ - --url https://{your_domain}.zitadel.cloud/oidc/v1/userinfo \ + --url $CUSTOM-DOMAIN/oidc/v1/userinfo \ --header 'Content-Type: application/x-www-form-urlencoded' \ --header 'Authorization: Bearer MtjHodGy4zxKylDOhg6kW90WeEQs2q...' ``` @@ -134,7 +135,7 @@ Content-Type: application/json { "name": "MyServiceUser", - "preferred_username": "service_user@{your_domain}.zitadel.cloud", + "preferred_username": "service_user@$CUSTOM-DOMAIN", "updated_at": 1616417938 } ``` diff --git a/docs/docs/guides/integrate/retrieve-user-roles.md b/docs/docs/guides/integrate/retrieve-user-roles.md index 64f5d9ebbc..30f6609ea9 100644 --- a/docs/docs/guides/integrate/retrieve-user-roles.md +++ b/docs/docs/guides/integrate/retrieve-user-roles.md @@ -1,5 +1,6 @@ --- -title: Retrieve user roles +title: Retrieve User Roles in ZITADEL +sidebar_label: Retrieve User Roles --- This guide explains all the possible ways of retrieving user roles across different organizations and projects using ZITADEL's APIs. @@ -82,7 +83,7 @@ Alternatively, you can include the claims `urn:iam:org:project:roles` or/and `ur ### Retrieve roles from the userinfo endpoint -The user info endpoint is **ZITADEL_DOMAIN/oidc/v1/userinfo**. +The user info endpoint is **$CUSTOM-DOMAIN/oidc/v1/userinfo**. This endpoint will return information about the authenticated user. Send the access token of the user as `Bearer Token` in the `Authorization` header: @@ -90,7 +91,7 @@ Send the access token of the user as `Bearer Token` in the `Authorization` heade **cURL Request:** ```bash curl --request GET \ - --url $ZITADEL_DOMAIN/oidc/v1/userinfo + --url $CUSTOM-DOMAIN/oidc/v1/userinfo --header 'Authorization: Bearer ' ``` @@ -205,11 +206,11 @@ Let’s start with a user who has multiple roles in different organizations in a Returns a list of roles for the authenticated user and for the requesting project (based on the token). -**URL: https://$ZITADEL_DOMAIN/auth/v1/permissions/me/_search** +**URL: https://$CUSTOM-DOMAIN/auth/v1/permissions/me/_search** **cURL request:** ```bash -curl -L -X POST 'https://$ZITADEL_DOMAIN/auth/v1/permissions/me/_search' \ +curl -L -X POST 'https://$CUSTOM-DOMAIN/auth/v1/permissions/me/_search' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer ' ``` @@ -230,12 +231,12 @@ Returns a list of permissions the authenticated user has in ZITADEL based on the This request can be used if you are building a management UI. For instance, if the UI is managing users, you can show the management functionality based on the permissions the user has. Here’s an example: if the user has `user.read` and `user.write` permission you can show the edit buttons, if the user only has `user.read` permission, you can hide the edit buttons. -**URL: https://ZITADEL_DOMAIN/auth/v1/permissions/zitadel/me/_search** +**URL: https://$CUSTOM-DOMAIN/auth/v1/permissions/zitadel/me/_search** **cURL Request:** ```bash -curl -L -X POST 'https://$ZITADEL_DOMAIN/auth/v1/permissions/zitadel/me/_search' \ +curl -L -X POST 'https://$CUSTOM-DOMAIN/auth/v1/permissions/zitadel/me/_search' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer ' ``` @@ -276,12 +277,12 @@ curl -L -X POST 'https://$ZITADEL_DOMAIN/auth/v1/permissions/zitadel/me/_search' Returns a list of user grants the authenticated user has. User grants consist of an organization, a project and roles. -**URL: https://$ZITADEL_DOMAIN/auth/v1/usergrants/me/_search** +**URL: https://$CUSTOM-DOMAIN/auth/v1/usergrants/me/_search** **cURL request:** ```bash -curl -L -X POST 'https://$ZITADEL_DOMAIN/auth/v1/usergrants/me/_search' \ +curl -L -X POST 'https://$CUSTOM-DOMAIN/auth/v1/usergrants/me/_search' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer ' \ @@ -378,7 +379,7 @@ curl -L -X POST 'https://$ZITADEL_DOMAIN/auth/v1/usergrants/me/_search' \ ### Retrieve roles using the management API Now we will use the management API to retrieve user roles under an admin user. -The base URL is: **https://$ZITADEL_DOMAIN/management/v1** +The base URL is: **https://$CUSTOM-DOMAIN/management/v1** In [APIs listed under user grants in the management API](/docs/category/apis/resources/mgmt/user-grants), you will see that you can use the management API to retrieve and modify user grants. The two API paths that we are interested in to fetch user roles are given below. @@ -388,12 +389,12 @@ In [APIs listed under user grants in the management API](/docs/category/apis/res Returns a list of user roles that match the search queries. A user with manager permissions will call this API and will also have to reside in the same organization as the user. -**URL: https://$ZITADEL_DOMAIN/management/v1/users/grants/_search** +**URL: https://$CUSTOM-DOMAIN/management/v1/users/grants/_search** **cURL request:** ```bash -curl -L -X POST 'https://$ZITADEL_DOMAIN/management/v1/users/grants/_search' \ +curl -L -X POST 'https://$CUSTOM-DOMAIN/management/v1/users/grants/_search' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer ' \ diff --git a/docs/docs/guides/integrate/services/atlassian-saml.md b/docs/docs/guides/integrate/services/atlassian-saml.md index f6be4726f6..60e210e390 100644 --- a/docs/docs/guides/integrate/services/atlassian-saml.md +++ b/docs/docs/guides/integrate/services/atlassian-saml.md @@ -1,5 +1,5 @@ --- -title: Connect with Atlassian through SAML 2.0 +title: Log in with ZITADEL on Atlassian through SAML 2.0 sidebar_label: Atlassian --- diff --git a/docs/docs/guides/integrate/services/auth0-oidc.mdx b/docs/docs/guides/integrate/services/auth0-oidc.mdx index 628900434e..ec574dd6d9 100644 --- a/docs/docs/guides/integrate/services/auth0-oidc.mdx +++ b/docs/docs/guides/integrate/services/auth0-oidc.mdx @@ -1,5 +1,5 @@ --- -title: Connect with Auth0 through OIDC +title: Log in with ZITADEL on Auth0 through OIDC sidebar_label: Auth0 (OIDC) --- diff --git a/docs/docs/guides/integrate/services/auth0-saml.md b/docs/docs/guides/integrate/services/auth0-saml.md index 3742ccfa43..649d3463e6 100644 --- a/docs/docs/guides/integrate/services/auth0-saml.md +++ b/docs/docs/guides/integrate/services/auth0-saml.md @@ -1,5 +1,5 @@ --- -title: Connect with Auth0 through SAML 2.0 +title: Log in with ZITADEL on Auth0 through SAML 2.0 sidebar_label: Auth0 (SAML) --- diff --git a/docs/docs/guides/integrate/services/aws-saml.md b/docs/docs/guides/integrate/services/aws-saml.md index 26a5c12fcd..86aec98b07 100644 --- a/docs/docs/guides/integrate/services/aws-saml.md +++ b/docs/docs/guides/integrate/services/aws-saml.md @@ -1,5 +1,5 @@ --- -title: Connect with AWS through SAML 2.0 +title: Log in with ZITADEL on AWS through SAML 2.0 sidebar_label: Amazon Web Services --- diff --git a/docs/docs/guides/integrate/services/cloudflare-oidc.mdx b/docs/docs/guides/integrate/services/cloudflare-oidc.mdx index 16fcf638f0..c014bdb4d2 100644 --- a/docs/docs/guides/integrate/services/cloudflare-oidc.mdx +++ b/docs/docs/guides/integrate/services/cloudflare-oidc.mdx @@ -1,5 +1,5 @@ --- -title: Configure as OIDC Identity Provider for Cloudflare Zero Trust +title: Configure ZITADEL as an OIDC Identity Provider on Cloudflare Zero Trust sidebar_label: Cloudflare Zero Trust --- diff --git a/docs/docs/guides/integrate/services/gitlab-saml.md b/docs/docs/guides/integrate/services/gitlab-saml.md index fc9c51ca63..ae6bb04a71 100644 --- a/docs/docs/guides/integrate/services/gitlab-saml.md +++ b/docs/docs/guides/integrate/services/gitlab-saml.md @@ -1,5 +1,5 @@ --- -title: Connect with Gitlab through SAML 2.0 +title: Log in with ZITADEL on Gitlab through SAML 2.0 sidebar_label: Gitlab --- @@ -51,7 +51,7 @@ Check your application, if everything is correct, press "Create". Complete the configuration as follows: -- `Identity provider single sign-on URL`: {your_instance_domain}/saml/v2/SSO +- `Identity provider single sign-on URL`: $CUSTOM-DOMAIN/saml/v2/SSO - `Certificate fingerprint`: You need to download the certificate from {your_instance_domain}/saml/v2/certificate and create a SHA1 fingerprint Save the changes. diff --git a/docs/docs/guides/integrate/services/gitlab-self-hosted.mdx b/docs/docs/guides/integrate/services/gitlab-self-hosted.mdx index 241d577c9d..17e3cc3af6 100644 --- a/docs/docs/guides/integrate/services/gitlab-self-hosted.mdx +++ b/docs/docs/guides/integrate/services/gitlab-self-hosted.mdx @@ -1,5 +1,5 @@ --- -title: Gitlab OmniAuth Provider +title: Log in with ZITADEL on Gitlab OmniAuth Provider --- import CreateApp from "../application/_application.mdx"; diff --git a/docs/docs/guides/integrate/services/google-cloud.mdx b/docs/docs/guides/integrate/services/google-cloud.mdx index eb75bcf974..49cb8ebddb 100644 --- a/docs/docs/guides/integrate/services/google-cloud.mdx +++ b/docs/docs/guides/integrate/services/google-cloud.mdx @@ -1,5 +1,5 @@ --- -title: Google Cloud with Workforce Identity Federation (OIDC) +title: Log in with ZITADEL on Google Cloud with Workforce Identity Federation (OIDC) sidebar_label: Google Cloud --- diff --git a/docs/docs/guides/integrate/services/pingidentity-saml.md b/docs/docs/guides/integrate/services/pingidentity-saml.md index ce827ef1b4..12e0c13a99 100644 --- a/docs/docs/guides/integrate/services/pingidentity-saml.md +++ b/docs/docs/guides/integrate/services/pingidentity-saml.md @@ -1,9 +1,9 @@ --- -title: Connect with Ping Identity through SAML 2.0 +title: Log in with ZITADEL on Ping Identity through SAML 2.0 sidebar_label: Ping Identity --- -This guide shows how to enable login with ZITADEL on Auth0. +This guide shows how to enable login with ZITADEL on Ping Identity. It covers how to: diff --git a/docs/docs/guides/integrate/token-introspection/basic-auth.mdx b/docs/docs/guides/integrate/token-introspection/basic-auth.mdx index 192b08c908..cad0f55c75 100644 --- a/docs/docs/guides/integrate/token-introspection/basic-auth.mdx +++ b/docs/docs/guides/integrate/token-introspection/basic-auth.mdx @@ -1,5 +1,6 @@ --- -title: Basic authentication +title: Basic Authentication in ZITADEL +sidebar_label: Basic Authentication --- import IntrospectionResponse from './_introspection-response.mdx'; diff --git a/docs/docs/guides/integrate/token-introspection/private-key-jwt.mdx b/docs/docs/guides/integrate/token-introspection/private-key-jwt.mdx index 3c83f7cc16..beaa58f3fb 100644 --- a/docs/docs/guides/integrate/token-introspection/private-key-jwt.mdx +++ b/docs/docs/guides/integrate/token-introspection/private-key-jwt.mdx @@ -1,5 +1,6 @@ --- -title: JSON Web Token profile +title: JSON Web Token Profile in ZITADEL +sidebar_label: JSON Web Token(JWT) Profile --- import IntrospectionResponse from './_introspection-response.mdx'; diff --git a/docs/docs/guides/integrate/tools/apache2.mdx b/docs/docs/guides/integrate/tools/apache2.mdx new file mode 100644 index 0000000000..28e9da7e2d --- /dev/null +++ b/docs/docs/guides/integrate/tools/apache2.mdx @@ -0,0 +1,72 @@ +--- +title: Apache 2.0 +--- + +This integration guide shows you the basic OpenID Connect integration with ZITADEL and an Apache 2.0 server. + +## Setup PKCE client in ZITADEL + +- Go to your organization and setup a new application with the type PKCE +- When created go to the "Redirect Settings" and *enable Development Mode* +- Add the Redirect Uri, f.e. `http://localhost:8080/secure/callback` +- Add the Post Logout Uri, f.e. `http://localhost:8080/index.html` +![Configuration](/img/guides/integrate/tools/apache-configuration.png) +![Redirect Settings](/img/guides/integrate/tools/apache-redirect_settings.png) + +You can find the url to your discovery endpoint under "URLs": +![Discovery Endpoint](/img/guides/integrate/tools/apache-urls.png) + +## Configure Apache2 + +### Configure mod_auth_openidc + +We use the module `mod_auth_openidc` in this guide. +You can find a minimal configuration in the [official documentation](https://github.com/OpenIDC/mod_auth_openidc/wiki#16-what-does-a-minimal-apache-configuration-file-look-like). + +The following parameters must be set with the values from ZITADEL. + +```yaml +OIDCProviderMetadataURL https://.zitadel.cloud/.well-known/openid-configuration +OIDCClientID +# OIDCRedirectURI is a vanity URL that must point to a path protected by this module but must NOT point to any content +OIDCRedirectURI +OIDCCryptoPassphrase + +OIDCScope "openid profile" +OIDCPKCEMethod S256 +``` + +With the following parameters + +| Parameter | Description | Example value| +|---|---|---| +| OIDCProviderMetadataURL | Is the url to the discovery endpoint, which is typically located at {your-domain}/.well-known/openid-configuration| https://.zitadel.cloud/.well-known/openid-configuration | +| OIDCClientID | Is the ID of the zitadel application. You can find it on the settings page of the application. | 123456789123@apache_test | +| OIDCRedirectURI | Users will be redirected to this page after successful login. If you are using localhost or any other non-https endpoint, make sure to enable development mode in ZITADEL. | https://mysecureapp.io/secure/callback | +| OIDCCryptoPassphrase | Create a secure passphrase. Consult the module's documentation for more details. | ... | +| OIDCScope | OpenID Connect scopes that should be included. You can find a list of [all scopes](/docs/apis/openidoauth/scopes) in our documentation. | "openid profile" | +| OIDCPKCEMethod | The method should be set to `S256` | S256 | + +### Secure a route + +If you want to secure a route / path then add do so by adding the following Location functionality with the directives: + +```text + + AuthType openid-connect + Require valid-user + +``` + +With the same functionality you can also specify if roles / permissions must be present on the user, or limit access to specific users. +Please consult the official documentation on more information. + +### Handling logout + +Consult the official documentation on how to [logout users](https://github.com/OpenIDC/mod_auth_openidc/wiki#9-how-do-i-logout-users). +Or check out the [example code](#example-code) for a minimal version. + +## Example code + +We provide a [minimum boilerplate example](https://github.com/zitadel/example-apache2) to test the integration of ZITADEL with an Apache server. +Follow the instructions in the readme. diff --git a/docs/docs/guides/manage/cloud/instances.md b/docs/docs/guides/manage/cloud/instances.md index 88c28fc3ff..fd1faf3b98 100644 --- a/docs/docs/guides/manage/cloud/instances.md +++ b/docs/docs/guides/manage/cloud/instances.md @@ -1,8 +1,9 @@ --- -title: Instances +title: ZITADEL Instances +sidebar_label: Instances --- -The ZITADEL customer Portal is used to manage all your different ZITADEL instances. +The ZITADEL Customer Portal is used to manage all your different ZITADEL instances. You can also manage your subscriptions, billing, newsletters and support requests. ## Overview @@ -55,7 +56,7 @@ A free instance can be upgraded to a "pay as you go" instance. By upgrading your ### Add Custom Domain We recommend register a custom domain to access your ZITADEL instance. -The primary domain of your ZITADEL instance will be the issuer of the instance. All other domains can be used to access the instance itself +The primary custom domain of your ZITADEL instance will be the issuer of the instance. All other custom domains can be used to access the instance itself 1. Browse to your instance 2. Click **Add custom domain** @@ -72,7 +73,7 @@ Be aware that it has some impacts if you change the primary domain of your insta ![Add custom domain](/img/manuals/portal/portal_add_domain.png) -#### Verify Domain +#### Verify Custom Domain If you need a custom domain for your ZITADEL instance, you need to verify the domain. diff --git a/docs/docs/guides/manage/cloud/notifications.md b/docs/docs/guides/manage/cloud/notifications.md index 99263924ac..81e65f7860 100644 --- a/docs/docs/guides/manage/cloud/notifications.md +++ b/docs/docs/guides/manage/cloud/notifications.md @@ -1,5 +1,6 @@ --- -title: Notifications +title: ZITADEL Notifications +sidebar_label: Notifications --- You can subscribe to different newsletters and notifications. diff --git a/docs/docs/guides/manage/cloud/start.md b/docs/docs/guides/manage/cloud/start.md index 15eb528dba..abeee7c1bc 100644 --- a/docs/docs/guides/manage/cloud/start.md +++ b/docs/docs/guides/manage/cloud/start.md @@ -1,5 +1,6 @@ --- -title: Getting Started +title: Getting Started with ZITADEL +sidebar_label: Getting Started --- If you are new to ZITADEL your first action is to create your first ZITADEL instance and an account to access the ZITADEL Customer Portal. diff --git a/docs/docs/guides/manage/cloud/support.md b/docs/docs/guides/manage/cloud/support.md index 7ccebbe555..ac69396e26 100644 --- a/docs/docs/guides/manage/cloud/support.md +++ b/docs/docs/guides/manage/cloud/support.md @@ -1,5 +1,6 @@ --- -title: Support +title: ZITADEL Support +sidebar_label: Support --- :::note diff --git a/docs/docs/guides/manage/cloud/users.md b/docs/docs/guides/manage/cloud/users.md index 0cdca5d237..157f6cddb4 100644 --- a/docs/docs/guides/manage/cloud/users.md +++ b/docs/docs/guides/manage/cloud/users.md @@ -1,5 +1,6 @@ --- -title: Users +title: Customer Portal Users in ZITADEL +sidebar_label: Customer Portal Users --- Manage all your users who are allowed to access the Customer Portal. diff --git a/docs/docs/guides/manage/console/actions.mdx b/docs/docs/guides/manage/console/actions.mdx index db31b4fec6..67e16c118d 100644 --- a/docs/docs/guides/manage/console/actions.mdx +++ b/docs/docs/guides/manage/console/actions.mdx @@ -1,5 +1,6 @@ --- -title: Actions +title: ZITADEL Actions +sidebar_label: Actions --- An Identity and Management system is a very interactive place. ZITADEL has built in functionality to react to its events. This functionality is called **Actions** and can be accessed from your organizations top navigation. diff --git a/docs/docs/guides/manage/console/applications.mdx b/docs/docs/guides/manage/console/applications.mdx index 1939644134..71285bf97c 100644 --- a/docs/docs/guides/manage/console/applications.mdx +++ b/docs/docs/guides/manage/console/applications.mdx @@ -1,5 +1,6 @@ --- -title: Applications +title: ZITADEL Applications +sidebar_label: Applications --- import ThemedImage from "@theme/ThemedImage"; @@ -7,7 +8,7 @@ import ThemedImage from "@theme/ThemedImage"; import AuthType from "../../integrate/application/_auth-type.mdx"; import ReviewConfig from "../../integrate/application/_review-config.mdx"; -# What is an application? +## What is an application? Applications are the entry point to your project. Users either login into one of your clients and interact with them directly or use one of your APIs. diff --git a/docs/docs/guides/manage/console/instance-settings.mdx b/docs/docs/guides/manage/console/instance-settings.mdx index 471db69451..68e3e9a963 100644 --- a/docs/docs/guides/manage/console/instance-settings.mdx +++ b/docs/docs/guides/manage/console/instance-settings.mdx @@ -1,5 +1,6 @@ --- -title: Instance Settings +title: ZITADEL Instance Settings +sidebar_label: Instance Settings --- Instance settings work as default or fallback settings for your organizational settings. Most of the time you only have to set instance settings for the cases where you don't need specific behaviour in the organizations themselves or you only have one organization. @@ -26,7 +27,7 @@ When you configure your instance, you can set the following: - [**Login Interface Texts**](#login-interface-texts): Text and internationalization for the login interface - [**Privacy Policy**](#privacy-policy-and-tos): Links to your own Terms of Service and Privacy Policy regulations. Link to Help Page. - [**OIDC Token Lifetimes and Expiration**](#oidc-token-lifetimes-and-expiration): Token lifetime and expiration settings. -- [**Secret Appearance**](#secret-appearance): Appearance of the generated codes and secrets used in mails for verification etc. +- [**Secret Generator**](#secret-generator): Appearance and expiration of the generated codes and secrets used in mails for verification etc. ## Branding @@ -269,7 +270,7 @@ You can set the following times: width="400px" /> -## Secret appearance +## Secret generator ZITADEL has some different codes and secrets, that can be specified. You can configure what kind of characters should be included, how long the secret should be and the expiration. @@ -286,7 +287,7 @@ The following secrets can be configured: Secret appearance diff --git a/docs/docs/guides/manage/console/managers.mdx b/docs/docs/guides/manage/console/managers.mdx index 70073a1d87..f4c2703af6 100644 --- a/docs/docs/guides/manage/console/managers.mdx +++ b/docs/docs/guides/manage/console/managers.mdx @@ -1,5 +1,6 @@ --- -title: Managers +title: ZITADEL Managers +sidebar_label: Managers --- import ManagerDescription from "../../../concepts/structure/_manager_description.mdx"; diff --git a/docs/docs/guides/manage/console/organizations.mdx b/docs/docs/guides/manage/console/organizations.mdx index 32fe246584..15841397f5 100644 --- a/docs/docs/guides/manage/console/organizations.mdx +++ b/docs/docs/guides/manage/console/organizations.mdx @@ -1,5 +1,6 @@ --- -title: Organizations +title: ZITADEL Organizations +sidebar_label: Organizations --- ## What is an organization? @@ -22,7 +23,7 @@ If you choose your logged in user as organization manager, a membership for the alt="Select Organization" /> -If you want to enable your customers to create their organization by themselves, we provide a creation form for a organization. ` + +3. Select “Native” and enter a name for the application, and click “Continue”. + + Device Authorization Flow in ZITADEL + +4. Select “Device Code”. Click “Continue”. + + Device Authorization Flow in ZITADEL + + +5. Check the details and click “Create”. + + Device Authorization Flow in ZITADEL + +6. Copy the “Client ID” and store it for later use. + + Device Authorization Flow in ZITADEL + +## Device Client Example + +The [ZITADEL OpenID Connect client and server library](https://github.com/zitadel/oidc/) written for Go has a device client example, which can behave and authenticate as a device. In order to run this client, you need a recent version of Go (>=1.20) installed on your device. + + +The example requires two environment variables to be set: + + +- `ISSUER`: server address of your instance or domain. You can find the issuer URL in the “URLs” section. In this example, it will be set to [https://test-0o6zvq.zitadel.cloud](https://test-0o6zvq.zitadel.cloud). **Do not use a trailing slash!** + + Device Authorization Flow in ZITADEL + +- `CLIENT_ID`: the Client ID we obtained earlier. + + +Replace `ISSUER` and `CLIENT_ID` values with the ones you obtained and run the example as shown below: + + +``` bash +ISSUER="https://test-0o6zvq.zitadel.cloud" \ +CLIENT_ID="232685602728952637@device_auth" \ +go run github.com/zitadel/oidc/v2/example/client/device@latest +``` + + +You should see some info-level log output with response data and a line with login instructions as given below: + +``` bash +Please browse to https://test-0o6zvq.zitadel.cloud/device and enter code GQWC-FWFK +INFO[0002] start polling +``` + +At this point, the device app starts polling the token endpoint at 5-second intervals until the request is allowed, denied, or times out (currently 5 minutes). + +## Authenticate + +When you browse to the given URL and the device code is entered, the authentication flow for a user is started. If you are already logged in, it skips right ahead to the final screen where you can choose to allow or deny the request. If you are not logged in, you will have to enter your credentials for login. + +Device Authorization Flow in ZITADEL + +Device Authorization Flow in ZITADEL + +Device Authorization Flow in ZITADEL + +Device Authorization Flow in ZITADEL + +Device Authorization Flow in ZITADEL + +When “allow” is clicked, the device (the CLI in this case) will receive a token on the next poll. A log line will be shown as given below: + +``` bash +INFO[0165] successfully obtained token: &{Um2W5Od0yBU0KHfhP0AD0726rXrpxlepOR7yyftMvocgMWr25pVCuca1oiSSLiQjcXQqCEA Bearer 43199 } +``` + +At this point, the program terminates as it doesn’t have any other useful purpose. Regular device apps would of course use the token to consume the actual service. + +## Code URL + +During the start of the authorization flow ,there is a log line printed with the complete response object from ZITADEL that looks like this: + +``` bash +resp&{dOcbPeysDhT26ZatRh9n7Q KPVZ-DJGG https://test-0o6zvq.zitadel.cloud/device https://test-0o6zvq.zitadel.cloud/device?user_code=GQWC-FWFK 300 5} +``` + +From this object, we used the `verification_uri` and `user_code` fields to print the information to the user to go to an address and enter the code. We also return a `verification_uri_complete` field, which already includes the code and allows skipping manual entry of the code. + + +``` bash +https://test-0o6zvq.zitadel.cloud/device?user_code=GQWC-FWFK +``` + +In real-life applications, this could be used to create a QR code on the device screen, and users can use their mobile phones to scan the code to go to the required URL for authentication. In the context of this example, you can restart the device example program and copy/paste the complete link instead of manually entering it. + +## Security considerations + +The user uses a low entropy code to link a device to their account, which is easy to brute-force. Also, the code or QR might be displayed in environments where others could observe and use the code to authenticate the device (hijack the session) to their account. + +The device authorization grant specification was written with the philosophy that hijacking such sessions is not beneficial for the attacker. For example, with an on-demand streaming service, the attacker would be paying for the services the user is benefiting from. Hence, economically, the attack would not make sense. + +See [here](https://datatracker.ietf.org/doc/html/rfc8628#section-5) for more information. + +## Client library + +For Go clients, the ZITADEL OIDC repository already includes device authorization in the `rp` package. + +Use [rp.DeviceAuthorization](https://pkg.go.dev/github.com/zitadel/oidc/v2/pkg/client/rp#DeviceAuthorization) to start the flow and receive the URL and user code. +[rp.DeviceAccessToken](https://pkg.go.dev/github.com/zitadel/oidc/v2/pkg/client/rp#DeviceAccessToken) polls the token endpoint and returns the access token once it succeeds. + +The client implementation is rather simple and can easily be recreated in other languages. Go can be used as an example, or simply follow the [RFC](https://datatracker.ietf.org/doc/html/rfc8628) as a guide. \ No newline at end of file diff --git a/docs/docs/guides/solution-scenarios/domain-discovery.mdx b/docs/docs/guides/solution-scenarios/domain-discovery.mdx index 25bf28c9a8..439a7cd8ab 100644 --- a/docs/docs/guides/solution-scenarios/domain-discovery.mdx +++ b/docs/docs/guides/solution-scenarios/domain-discovery.mdx @@ -1,5 +1,6 @@ --- -title: Domain Discovery +title: Domain Discovery in ZITADEL +sidebar_label: Domain Discovery --- This guide should explain how domain discovery works and how to configure it in ZITADEL. @@ -87,7 +88,7 @@ Make sure to [Force MFA](/docs/guides/manage/console/instance-settings#multifact ### Verify domains -Switch to the organization **Alpha** and select the tab "Domains". +Switch to the organization **Alpha** and navigate to the settings and "Verified domains". Verify the domain alpha.com following the [organization guide](/docs/guides/manage/console/organizations#domain-verification-and-primary-domain). Do the same for the **Beta** organization. @@ -100,7 +101,7 @@ You can also disable domain verification with acme challenge in the [instance se You should be all setup to try out domain discovery. -The user journeys for the different users would look as follows: +The user journeys for the different users would look as follows: - User (Alice, Bob, Chuck) clicks a login button in your application - Redirected to `login.mycompany.com` (ZITADEL running under a custom domain) diff --git a/docs/docs/guides/solution-scenarios/frontend-calling-backend-API.mdx b/docs/docs/guides/solution-scenarios/frontend-calling-backend-API.mdx index a74c29e6cc..ff6090577f 100644 --- a/docs/docs/guides/solution-scenarios/frontend-calling-backend-API.mdx +++ b/docs/docs/guides/solution-scenarios/frontend-calling-backend-API.mdx @@ -1,5 +1,6 @@ --- -title: Frontend and API communication +title: Frontend and Back-end API Communication in ZITADEL +sidebar_label: Frontent and API Communcation --- This guide contains a use case and ZITADEL integration. diff --git a/docs/docs/guides/solution-scenarios/saas.md b/docs/docs/guides/solution-scenarios/saas.md index a810e4377a..6744074a7d 100644 --- a/docs/docs/guides/solution-scenarios/saas.md +++ b/docs/docs/guides/solution-scenarios/saas.md @@ -1,5 +1,5 @@ --- -title: SaaS Product with Authentication and Authorization +title: Set up a SaaS Product with Authentication and Authorization using ZITADEL sidebar_label: Software-as-a-Service --- diff --git a/docs/docs/guides/start/quickstart.mdx b/docs/docs/guides/start/quickstart.mdx index c9f6e0f8c1..2634cbbd40 100644 --- a/docs/docs/guides/start/quickstart.mdx +++ b/docs/docs/guides/start/quickstart.mdx @@ -1,5 +1,6 @@ --- -title: Quick start guide +title: The ZITADEL Quick Start Guide +sidebar_label: Quick Start Guide --- import VSCodeFolderView from "../../../static/img/guides/quickstart/vscode1.png"; @@ -66,45 +67,55 @@ The order of creation for the above components may vary depending on the specifi ### 1. Sign up for the ZITADEL Cloud customer portal and register your organization -1. Go to [zitadel.com](http://zitadel.com) and click on “Start for FREE”. +1. Go to [zitadel.com](http://zitadel.com) and click “Start for FREE”. -![Home Page](/img/guides/quickstart/1.png) +![Home Page](/img/guides/quickstart/v3_1.png) -2. Enter your details as shown below and click the "Let's go" button. -![Registration Page](/img/guides/quickstart/2.png) +2. You can sign up by entering your email address or via social login, e.g., Google. Let's sign up using an email address. Enter your email address and click "Sign in with Email". -3. You will receive a verification email to verify the user for the Customer Portal. Click the “Sign in” button. +![Registration Page](/img/guides/quickstart/v3_2.png) -![Congratulations Page](/img/guides/quickstart/3.png) -4. You will be prompted for a code, which has been emailed to you. +3. Next, complete your details and click "Continue". -![Code](/img/guides/quickstart/4.png) +![Congratulations Page](/img/guides/quickstart/v3_3.png) -5. Check your inbox for the code. You can either click the “Finish initialization” button in the email itself or copy the code and return to the previous page. In this guide, we will copy the code and then continue from where we left off. -![Inbox](/img/guides/quickstart/5.png) +4. Add a password as shown below. Adhere to the password requirements, agree to the terms of service and privacy policy by selecting the checkbox, and click the "Get started" button. -6. Paste the code and add a password as shown below. Click on the “next” button. +![Code](/img/guides/quickstart/v3_4.png) -![Code](/img/guides/quickstart/6.png) -7. The user is now activated. Click on “next” to log in. +5. Enter your login data. Provide the password you just created. Click "next". -![User Activated](/img/guides/quickstart/7.png) +![Inbox](/img/guides/quickstart/v3_5.png) -8. Login with the username and password that you provided. Click “next”. -![Login](/img/guides/quickstart/8.png) +6. You will now have to verify your email address. -9. You should set up 2-factor authentication. However, we will skip this step for now. Click on “skip”. +![Verify Email](/img/guides/quickstart/v3_17.png) -![2-factor Authentication](/img/guides/quickstart/9.png) -10. You will now be asked to create an instance. +6. Go to your inbox and open the email from ZITADEL and click the "Verify email" button. + +![Code](/img/guides/quickstart/v3_6.png) + + +7. The user is now activated. Click the "login" button. + +![User Activated](/img/guides/quickstart/v3_7.png) + + +8. Now you will have to log in with the username and password that you provided. Click “Sign in with Email”. + +![Login](/img/guides/quickstart/v3_8.png) + + +9. You will now be asked to create an instance. + +![2-factor Authentication](/img/guides/quickstart/v3_9.png) -![Create Instance](/img/guides/quickstart/10.png) ### 2. Create your first instance @@ -112,41 +123,51 @@ As a user of the ZITADEL Cloud Customer Portal, you now can create multiple inst 1. Let’s create an instance. Click on “Create new instance”. -![Create Instance](/img/guides/quickstart/10.png) +![2-factor Authentication](/img/guides/quickstart/v3_9.png) + + 2. Provide a name for your instance, select the “Free” plan and click on the “Continue” button at the bottom of this screen. -![Select Tier](/img/guides/quickstart/v2_1.png) +![Select Tier](/img/guides/quickstart/v3_10.png) + 3. Next, you should see the following screen. Add a username and password for the instance manager and click "Create". -![Instance Details](/img/guides/quickstart/v2_2.png) + +![Instance Details](/img/guides/quickstart/v3_11.png) + 4. The instance creation process will take a few seconds. -![Instance Details](/img/guides/quickstart/v2_3.png) +![Instance Details](/img/guides/quickstart/v3_12.png) + 5. Now you will see the details of your first instance. You can click on "Visit" at the top right to go to your instance. -![Instance Details](/img/guides/quickstart/v2_4.png) +![Instance Details](/img/guides/quickstart/v3_13.png) + 6. To log in to your instance, provide the username and password, and click “next”. -![Instance Details](/img/guides/quickstart/v2_5.png) +![Instance Details](/img/guides/quickstart/v3_14.png) + 7. Skip the 2-factor authentication for now by clicking “skip”. -![Instance Details](/img/guides/quickstart/v2_6.png) +![Instance Details](/img/guides/quickstart/v3_15.png) -10. And there you go! You now have access to your instance. +8. And there you go! You now have access to your instance. + +![Instance Details](/img/guides/quickstart/v3_16.png) -![Access Instance](/img/guides/quickstart/v2_7.png) ### 3. Create your first project -1. To create a project in the instance you just created, click on “Create Project”. +1. To create a project in the instance you just created, click on “Create a project”. + +![Create Project](/img/guides/quickstart/v3_16.png) -![Create Project](/img/guides/quickstart/19.png) 2. Insert “Project1” (or any name of your choice) as the project’s name and click the “Continue” button. diff --git a/docs/docs/legal/policies/account-lockout-policy.md b/docs/docs/legal/policies/account-lockout-policy.md index 14463392d3..12bc9e44ac 100644 --- a/docs/docs/legal/policies/account-lockout-policy.md +++ b/docs/docs/legal/policies/account-lockout-policy.md @@ -1,5 +1,6 @@ --- -title: Account Lockout Policy +title: ZITADEL Account Lockout Policy +sidebar_label: Account Lockout Policy custom_edit_url: null --- diff --git a/docs/docs/legal/policies/feature-development-policy.md b/docs/docs/legal/policies/feature-development-policy.md new file mode 100644 index 0000000000..0b80fc556e --- /dev/null +++ b/docs/docs/legal/policies/feature-development-policy.md @@ -0,0 +1,57 @@ +--- +title: Feature Development Policy +custom_edit_url: null +--- + +This policy clarifies how we handle requests for feature prioritization and development. This policy is applicable to situations where a user wants to prioritize certain features or development for our products and services. + +## Why to do we have this policy? + +Shaping of the roadmap and prioritization of features is done by ZITADEL. +You are welcome to give feedback on the [roadmap](https://zitadel.com/roadmap) and we are happy to [accept upstream pull requests](https://github.com/zitadel/zitadel/CONTRIBUTE.md) for ZITADEL. + +In case you can't contribute directly to the open source version of ZITADEL, but want to accelerate development, we may develop the feature on request, under the conditions layed out in this policy. + +Features that will be part of the publicly available ZITADEL [repositories](https://github.com/zitadel/), ZITADEL Cloud, or optional components to our products that might be available only under a commercial license. + +### Conditions + +All intellectual property and ownership of the changes remain with ZITADEL. +Changes may be published under an Open Source or commercial license. +ZITADEL will guarantee maintenance and warranties according to the license and terms of services under which the changes are being released. + +## Process + +1. [Contact us](https://zitadel.com/contact) about your feature request +2. We will share the scope of the feature and acceptance criteria with you +3. As soon as you confirm the scope we will provide you with a quote containing scope, timeline, estimated effort, and price for the feature development +4. When you accept 50% of the cost is due +5. We inform you when the feature is available and provide you with instructions on how to conduct the acceptance tests +6. You will need to report any issues within 14 days, if not otherwise agreed (see below) +7. On completion (see below) the remaining cost is due + +### Development cost + +We will estimate the overall effort and development cost of the changes. +For features that are not on the roadmap or feature requests where a defined deadline for general availability of the feature must be met, we take 100% of the development cost as base. +For prioritization of a features that are already on the roadmap, we take 50% of the development cost as base. + +We will provide you with a quote for fixed price of the overall effort and ZITADEL will carry any overages. + +Cost, scope and timeline will be agreed in a formal quote. + +### Payment + +50% of the cost will be due on accepting the quote. +We will send you an invoice and expect payment within the given deadline. + +50% on completion of the feature development. +Completion means that the agreed scope is available according to the agreed acceptance criteria. +You had 14 days to verify the acceptance criteria and report any issues. +A feature is considered complete, if the outstanding issues are being solved, or a timeline for resolution of the issues has been mutually agreed, or if we haven't got any response within the last 14 days. + +## Entry into force + +This policy is valid from September 25, 2023. + +Last revised September 25, 2023 diff --git a/docs/docs/self-hosting/deploy/compose.mdx b/docs/docs/self-hosting/deploy/compose.mdx index 57edf063c0..9b3f4f9777 100644 --- a/docs/docs/self-hosting/deploy/compose.mdx +++ b/docs/docs/self-hosting/deploy/compose.mdx @@ -1,5 +1,6 @@ --- -title: Docker Compose +title: Set up ZITADEL with Docker Compose +sidebar_label: Docker Compose --- import CodeBlock from '@theme/CodeBlock'; diff --git a/docs/docs/self-hosting/deploy/knative.mdx b/docs/docs/self-hosting/deploy/knative.mdx index e2a67ccb6c..0613e7f7e2 100644 --- a/docs/docs/self-hosting/deploy/knative.mdx +++ b/docs/docs/self-hosting/deploy/knative.mdx @@ -1,5 +1,6 @@ --- -title: Knative +title: Set up ZITADEL on Knative +sidebar_label: Knative --- import Disclaimer from './_disclaimer.mdx' diff --git a/docs/docs/self-hosting/deploy/kubernetes.mdx b/docs/docs/self-hosting/deploy/kubernetes.mdx index e3f4f0981c..c7879e1bb4 100644 --- a/docs/docs/self-hosting/deploy/kubernetes.mdx +++ b/docs/docs/self-hosting/deploy/kubernetes.mdx @@ -1,5 +1,6 @@ --- -title: Kubernetes +title: Set up ZITADEL on Kubernetes +sidebar_label: Kubernetes --- import Disclaimer from './_disclaimer.mdx' diff --git a/docs/docs/self-hosting/deploy/linux.mdx b/docs/docs/self-hosting/deploy/linux.mdx index a6ed2e43cb..eab3cb50b2 100644 --- a/docs/docs/self-hosting/deploy/linux.mdx +++ b/docs/docs/self-hosting/deploy/linux.mdx @@ -1,5 +1,6 @@ --- -title: Linux +title: Install ZITADEL on Linux +sidebar_label: Linux --- import Disclaimer from './_disclaimer.mdx' diff --git a/docs/docs/self-hosting/deploy/loadbalancing-example/loadbalancing-example.mdx b/docs/docs/self-hosting/deploy/loadbalancing-example/loadbalancing-example.mdx index 9f49d1f5cd..803122751b 100644 --- a/docs/docs/self-hosting/deploy/loadbalancing-example/loadbalancing-example.mdx +++ b/docs/docs/self-hosting/deploy/loadbalancing-example/loadbalancing-example.mdx @@ -1,5 +1,5 @@ --- -title: Load Balancing Example +title: A ZITADEL Load Balancing Example --- import CodeBlock from '@theme/CodeBlock'; diff --git a/docs/docs/self-hosting/deploy/macos.mdx b/docs/docs/self-hosting/deploy/macos.mdx index 53719ef997..7b7e9e98dc 100644 --- a/docs/docs/self-hosting/deploy/macos.mdx +++ b/docs/docs/self-hosting/deploy/macos.mdx @@ -1,5 +1,6 @@ --- -title: MacOS +title: Install ZITADEL on MacOS +sidebar_label: MacOS --- import Disclaimer from './_disclaimer.mdx' diff --git a/docs/docs/self-hosting/deploy/troubleshooting/troubleshooting.mdx b/docs/docs/self-hosting/deploy/troubleshooting/troubleshooting.mdx index aa5b2ed2e0..911aceace3 100644 --- a/docs/docs/self-hosting/deploy/troubleshooting/troubleshooting.mdx +++ b/docs/docs/self-hosting/deploy/troubleshooting/troubleshooting.mdx @@ -1,5 +1,5 @@ --- -title: Troubleshoot +title: Troubleshoot ZITADEL --- import InstanceNotFound from '/docs/self-hosting/deploy/troubleshooting/_instance_not_found.mdx'; diff --git a/docs/docs/self-hosting/manage/configure/configure.mdx b/docs/docs/self-hosting/manage/configure/configure.mdx index 37e909ac10..82c97dc012 100644 --- a/docs/docs/self-hosting/manage/configure/configure.mdx +++ b/docs/docs/self-hosting/manage/configure/configure.mdx @@ -1,5 +1,6 @@ --- -title: Configuration Options +title: Configuration Options in ZITADEL +sidebar_label: Configuration --- import Tabs from "@theme/Tabs"; diff --git a/docs/docs/self-hosting/manage/custom-domain.md b/docs/docs/self-hosting/manage/custom-domain.md index af7c643a52..ffff207722 100644 --- a/docs/docs/self-hosting/manage/custom-domain.md +++ b/docs/docs/self-hosting/manage/custom-domain.md @@ -1,5 +1,6 @@ --- -title: Custom Domain +title: Run ZITADEL on a Custom Domain +sidebar: Custom Domain --- # Run ZITADEL on a (Sub)domain of Your Choice diff --git a/docs/docs/self-hosting/manage/database/_cockroachdb.mdx b/docs/docs/self-hosting/manage/database/_cockroachdb.mdx index ad58f376c7..2836640669 100644 --- a/docs/docs/self-hosting/manage/database/_cockroachdb.mdx +++ b/docs/docs/self-hosting/manage/database/_cockroachdb.mdx @@ -1,4 +1,4 @@ -## Cockroach +## ZITADEL with Cockroach The default database of ZITADEL is [CockroachDB](https://www.cockroachlabs.com). The SQL database provides a bunch of features like horizontal scalability, data regionality and many more. diff --git a/docs/docs/self-hosting/manage/database/_postgres.mdx b/docs/docs/self-hosting/manage/database/_postgres.mdx index 543c895991..bb87a7a1c4 100644 --- a/docs/docs/self-hosting/manage/database/_postgres.mdx +++ b/docs/docs/self-hosting/manage/database/_postgres.mdx @@ -1,4 +1,4 @@ -## Postgres +## ZITADEL with Postgres :::caution Be aware that PostgreSQL is only [Enterprise Supported](/docs/support/software-release-cycles-support#partially-supported). diff --git a/docs/docs/self-hosting/manage/http2.mdx b/docs/docs/self-hosting/manage/http2.mdx index daafe84379..6a342381a0 100644 --- a/docs/docs/self-hosting/manage/http2.mdx +++ b/docs/docs/self-hosting/manage/http2.mdx @@ -1,5 +1,6 @@ --- -title: HTTP/2 Support +title: HTTP/2 Support in ZITADEL +sidebar_label: HTTP/2 Support --- ZITADEL follows a strict API first approach and makes heavy use of the modern API framework called [gRPC](https://grpc.io/). diff --git a/docs/docs/self-hosting/manage/production.md b/docs/docs/self-hosting/manage/production.md index 7ba38c0b5c..14117ebcce 100644 --- a/docs/docs/self-hosting/manage/production.md +++ b/docs/docs/self-hosting/manage/production.md @@ -1,5 +1,6 @@ --- -title: Production Setup +title: ZITADEL Production Setup +sidebar_lable: Production Setup --- As soon as you successfully deployed ZITADEL as a proof of concept using one of our [deployment guides](/docs/self-hosting/deploy/overview), diff --git a/docs/docs/self-hosting/manage/productionchecklist.md b/docs/docs/self-hosting/manage/productionchecklist.md index 4dd93328bd..24c4803a10 100644 --- a/docs/docs/self-hosting/manage/productionchecklist.md +++ b/docs/docs/self-hosting/manage/productionchecklist.md @@ -1,5 +1,6 @@ --- -title: Production Checklist +title: ZITADEL Production Checklist +sidebar_label: Production Checklist --- diff --git a/docs/docs/self-hosting/manage/quotas.md b/docs/docs/self-hosting/manage/quotas.md index f20ad57245..23659242b4 100644 --- a/docs/docs/self-hosting/manage/quotas.md +++ b/docs/docs/self-hosting/manage/quotas.md @@ -1,5 +1,6 @@ --- -title: Usage Quotas +title: Usage Quotas in ZITADEL +sidebar_label: Usage Quotas --- Quotas is an enterprise feature that is relevant if you want to host ZITADEL as a service. diff --git a/docs/docs/self-hosting/manage/updating_scaling.md b/docs/docs/self-hosting/manage/updating_scaling.md index 15e29cc674..66cd502f39 100644 --- a/docs/docs/self-hosting/manage/updating_scaling.md +++ b/docs/docs/self-hosting/manage/updating_scaling.md @@ -1,5 +1,6 @@ --- -title: Updating and Scaling +title: Update and Scale ZITADEL +sidebar_label: Update and Scale --- ## TL;DR diff --git a/docs/docs/support/software-release-cycles-support.md b/docs/docs/support/software-release-cycles-support.md index 1f3333719b..1b601cfd8d 100644 --- a/docs/docs/support/software-release-cycles-support.md +++ b/docs/docs/support/software-release-cycles-support.md @@ -1,5 +1,6 @@ --- -title: Support states & software release cycle +title: ZITADEL Support States and Software Release Cycle +sidebar_label: Support States/Software Release Cycle --- ## Support states diff --git a/docs/docs/support/technical_advisory.mdx b/docs/docs/support/technical_advisory.mdx index 5d63fbcb8a..520dd3991c 100644 --- a/docs/docs/support/technical_advisory.mdx +++ b/docs/docs/support/technical_advisory.mdx @@ -1,5 +1,6 @@ --- -title: Technical Advisory +title: ZITADEL Technical Advisories +sidebar_label: Technical Advisories --- Technical advisories are notices that report major issues with ZITADEL Self-Hosted or the ZITADEL Cloud platform that could potentially impact security or stability in production environments. diff --git a/docs/docs/support/trainings/introduction.md b/docs/docs/support/trainings/introduction.md index 270d6e9061..c597470f38 100644 --- a/docs/docs/support/trainings/introduction.md +++ b/docs/docs/support/trainings/introduction.md @@ -1,5 +1,5 @@ --- -title: Trainings +title: ZITADEL Trainings sidebar_label: Introduction --- diff --git a/docs/docs/support/troubleshooting.mdx b/docs/docs/support/troubleshooting.mdx index 12c1ed6f41..66869b0334 100644 --- a/docs/docs/support/troubleshooting.mdx +++ b/docs/docs/support/troubleshooting.mdx @@ -1,5 +1,6 @@ --- -title: Troubleshoot +title: Troubleshoot ZITADEL +sidebar_label: Troubleshoot --- You will find some possible error messages here, what the problem is and what some possible solutions can be. diff --git a/docs/sidebars.js b/docs/sidebars.js index 55e1ebd8c6..4a3d219012 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -3,7 +3,7 @@ module.exports = { "guides/overview", { type: "category", - label: "Get started", + label: "Get Started", collapsed: false, items: [ "guides/start/quickstart", @@ -33,7 +33,7 @@ module.exports = { "examples/sdks", { type: "category", - label: "Example applications", + label: "Example Applications", items: [ "examples/introduction", { @@ -142,11 +142,11 @@ module.exports = { items: [ { type: "category", - label: "Authenticate users", + label: "Authenticate Users", collapsed: true, link: { type: "generated-index", - title: "Authenticate human users", + title: "Authenticate Human Users", slug: "guides/integrate/human-users", description: "How to authenticate human users with OpenID Connect", @@ -159,10 +159,10 @@ module.exports = { }, { type: "category", - label: "Token introspection", + label: "Token Introspection", link: { type: "generated-index", - title: "Token introspection", + title: "Token Introspection", slug: "/guides/integrate/token-introspection", description: "Token introspection is the process of checking whether an access token is valid and can be used to access protected resources. You have an API that acts as an OAuth resource server and can be accessed by user-facing applications. To validate an access token by calling the ZITADEL introspection API, you can use the JSON Web Token (JWT) Profile (recommended) or Basic Authentication for token introspection. It's crucial to understand that the API is entirely separate from the front end. The API shouldn’t concern itself with the token type received. Instead, it's about how the API chooses to call the introspection endpoint, either through JWT Profile or Basic Authentication. Many APIs assume they might receive a JWT and attempt to verify it based on signature or expiration. However, with ZITADEL, you can send either a JWT or an opaque Bearer token from the client end to the API. This flexibility is one of ZITADEL's standout features.", @@ -175,10 +175,10 @@ module.exports = { }, { type: "category", - label: "Authenticate service users", + label: "Authenticate Service Users", link: { type: "generated-index", - title: "Authenticate Service Users", + title: "Authenticate ZITADEL Service Users", slug: "/guides/integrate/serviceusers", description: "How to authenticate service users for machine-to-machine (M2M) communication between services. You also need to authenticate service users to access ZITADEL's APIs.", @@ -192,7 +192,7 @@ module.exports = { }, { type: "category", - label: "Role management", + label: "Role Management", collapsed: true, items: [ "guides/integrate/retrieve-user-roles" @@ -200,10 +200,10 @@ module.exports = { }, { type: "category", - label: "Build your own login UI", + label: "Build your own Login UI", link: { type: "generated-index", - title: "Build your own login UI", + title: "Build your own Login UI", slug: "/guides/integrate/login-ui", description: "In the following guides you will learn how to create your own login UI with our APIs. The different scenarios like username/password, external identity provider, etc. will be shown." @@ -223,10 +223,10 @@ module.exports = { }, { type: "category", - label: "Configure identity providers", + label: "Configure Identity Providers", link: { type: "generated-index", - title: "Let users login with their preferred identity provider", + title: "Let Users Login with Preferred Identity Provider in ZITADEL", slug: "/guides/integrate/identity-providers", description: "In the following guides you will learn how to configure and setup your preferred external identity provider in ZITADEL.", @@ -252,7 +252,7 @@ module.exports = { items: [ { type: 'link', - label: 'Authenticate service users', + label: 'Authenticate Service Users', href: '/guides/integrate/serviceusers', }, "guides/integrate/access-zitadel-apis", @@ -260,7 +260,7 @@ module.exports = { "guides/integrate/event-api", { type: "category", - label: "Example code", + label: "Example Code", items: [ "examples/call-zitadel-api/go", "examples/call-zitadel-api/dot-net", @@ -274,7 +274,7 @@ module.exports = { label: "Services", link: { type: "generated-index", - title: "Integrate ZITADEL with your favorite services", + title: "Integrate ZITADEL with your Favorite Services", slug: "/guides/integrate/services", description: "With the guides in this section you will learn how to integrate ZITADEL with your services.", @@ -286,11 +286,6 @@ module.exports = { type: 'autogenerated', dirName: 'guides/integrate/services', }, - { - type: 'link', - label: 'Nextcloud', - href: 'https://zitadel.com/blog/zitadel-as-sso-provider-for-selfhosting', - }, { type: 'link', label: 'Bold BI (boldbi.com)', @@ -308,14 +303,19 @@ module.exports = { }, { type: 'link', - label: 'Psono (psono.com)', - href: 'https://doc.psono.com/admin/configuration/oidc-zitadel.html', + label: 'Nextcloud', + href: 'https://zitadel.com/blog/zitadel-as-sso-provider-for-selfhosting', }, { type: 'link', label: 'Netbird (netbird.io)', href: 'https://docs.netbird.io/selfhosted/identity-providers', }, + { + type: 'link', + label: 'Psono (psono.com)', + href: 'https://doc.psono.com/admin/configuration/oidc-zitadel.html', + }, { type: 'link', label: 'Zoho Desk (zoho.com)', @@ -328,7 +328,7 @@ module.exports = { label: "Tools", link: { type: "generated-index", - title: "Integrate ZITADEL with your tools", + title: "Integrate ZITADEL with your Tools", slug: "/guides/integrate/tools", description: "With the guides in this section you will learn how to integrate ZITADEL with your favorite tools.", @@ -336,6 +336,12 @@ module.exports = { }, collapsed: true, items: [ + { + type: 'link', + label: 'Argo CD', + href: 'https://argo-cd.readthedocs.io/en/latest/operator-manual/user-management/zitadel/', + }, + "guides/integrate/tools/apache2", "guides/integrate/authenticated-mongodb-charts", "examples/identity-proxy/oauth2-proxy" ], @@ -344,10 +350,10 @@ module.exports = { }, { type: "category", - label: "Solution scenarios", + label: "Solution Scenarios", link: { type: "generated-index", - title: "Solution scenarios", + title: "Solution Scenarios", slug: "guides/solution-scenarios/introduction", description: "Customers of an SaaS Identity and access management system usually have all distinct use cases and requirements. This guide attempts to explain real-world implementations and break them down into solution scenarios which aim to help you getting started with ZITADEL.", @@ -360,6 +366,7 @@ module.exports = { "guides/solution-scenarios/domain-discovery", "guides/solution-scenarios/configurations", "guides/solution-scenarios/frontend-calling-backend-API", + "guides/solution-scenarios/device-authorization", ], }, { @@ -400,7 +407,7 @@ module.exports = { "concepts/principles", { type: "category", - label: "Eventstore", + label: "Event Store", collapsed: true, items: [ "concepts/eventstore/overview", @@ -418,7 +425,7 @@ module.exports = { "support/troubleshooting", { type: 'category', - label: "Technical advisory", + label: "Technical Advisory", link: { type: 'doc', id: 'support/technical_advisory', @@ -461,7 +468,7 @@ module.exports = { items: [ { type: "category", - label: "Authenticated user", + label: "Authenticated User", link: { type: "generated-index", title: "Auth API", @@ -474,7 +481,7 @@ module.exports = { }, { type: "category", - label: "Organization objects", + label: "Organization Objects", link: { type: "generated-index", title: "Management API", @@ -486,7 +493,7 @@ module.exports = { }, { type: "category", - label: "Instance objects", + label: "Instance Objects", link: { type: "generated-index", title: "Admin API", @@ -498,7 +505,7 @@ module.exports = { }, { type: "category", - label: "Instance lifecycle", + label: "Instance Lifecycle", link: { type: "generated-index", title: "System API", @@ -512,10 +519,10 @@ module.exports = { }, { type: "category", - label: "User lifecycle (Beta)", + label: "User Lifecycle (Beta)", link: { type: "generated-index", - title: "User service API (Beta)", + title: "User Service API (Beta)", slug: "/apis/resources/user_service", description: "This API is intended to manage users in a ZITADEL instance.\n"+ @@ -526,10 +533,10 @@ module.exports = { }, { type: "category", - label: "Session lifecycle (Beta)", + label: "Session Lifecycle (Beta)", link: { type: "generated-index", - title: "Session service API (Beta)", + title: "Session Service API (Beta)", slug: "/apis/resources/session_service", description: "This API is intended to manage sessions in a ZITADEL instance.\n"+ @@ -540,10 +547,10 @@ module.exports = { }, { type: "category", - label: "OIDC lifecycle (Beta)", + label: "OIDC Lifecycle (Beta)", link: { type: "generated-index", - title: "OIDC service API (Beta)", + title: "OIDC Service API (Beta)", slug: "/apis/resources/oidc_service", description: "Get OIDC Auth Request details and create callback URLs.\n"+ @@ -554,10 +561,10 @@ module.exports = { }, { type: "category", - label: "Settings lifecycle (Beta)", + label: "Settings Lifecycle (Beta)", link: { type: "generated-index", - title: "Settings service API (Beta)", + title: "Settings Service API (Beta)", slug: "/apis/resources/settings_service", description: "This API is intended to manage settings in a ZITADEL instance.\n"+ @@ -616,7 +623,7 @@ module.exports = { }, { type: "doc", - label: "gRPC status codes", + label: "gRPC Status Codes", id: "apis/statuscodes" }, { @@ -627,7 +634,7 @@ module.exports = { }, { type: 'link', - label: 'Rate limits (cloud)', // The link label + label: 'Rate Limits (Cloud)', // The link label href: '/legal/rate-limit-policy', // The internal path }, ], @@ -668,11 +675,11 @@ module.exports = { legal: [ { type: "category", - label: "Legal agreements", + label: "Legal Agreements", collapsed: false, link: { type: "generated-index", - title: "Legal agreements", + title: "Legal Agreements", slug: "legal", description: "This section contains important agreements, policies and appendices relevant for users of our websites and services. All documents will be provided in English language.", @@ -682,7 +689,7 @@ module.exports = { "legal/data-processing-agreement", { type: "category", - label: "Service description", + label: "Service Description", collapsed: false, items: [ "legal/cloud-service-description", @@ -692,7 +699,7 @@ module.exports = { }, { type: "category", - label: "Support program", + label: "Support Program", collapsed: true, items: [ "legal/terms-support-service", @@ -713,6 +720,7 @@ module.exports = { "legal/acceptable-use-policy", "legal/rate-limit-policy", "legal/policies/account-lockout-policy", + "legal/policies/feature-development-policy", "legal/vulnerability-disclosure-policy", ], }, diff --git a/docs/static/img/change-email.gif b/docs/static/img/change-email.gif deleted file mode 100644 index 1bd880c7b7..0000000000 Binary files a/docs/static/img/change-email.gif and /dev/null differ diff --git a/docs/static/img/console_verify_domain.gif b/docs/static/img/console_verify_domain.gif deleted file mode 100644 index 42e81b8dd9..0000000000 Binary files a/docs/static/img/console_verify_domain.gif and /dev/null differ diff --git a/docs/static/img/device-auth/device-auth-01.png b/docs/static/img/device-auth/device-auth-01.png new file mode 100644 index 0000000000..f9b92b0894 Binary files /dev/null and b/docs/static/img/device-auth/device-auth-01.png differ diff --git a/docs/static/img/device-auth/device-auth-02.png b/docs/static/img/device-auth/device-auth-02.png new file mode 100644 index 0000000000..9929619c2a Binary files /dev/null and b/docs/static/img/device-auth/device-auth-02.png differ diff --git a/docs/static/img/device-auth/device-auth-03.png b/docs/static/img/device-auth/device-auth-03.png new file mode 100644 index 0000000000..21cbfd610d Binary files /dev/null and b/docs/static/img/device-auth/device-auth-03.png differ diff --git a/docs/static/img/device-auth/device-auth-04.png b/docs/static/img/device-auth/device-auth-04.png new file mode 100644 index 0000000000..36be76ea7d Binary files /dev/null and b/docs/static/img/device-auth/device-auth-04.png differ diff --git a/docs/static/img/device-auth/device-auth-05.png b/docs/static/img/device-auth/device-auth-05.png new file mode 100644 index 0000000000..a3ac7e86cf Binary files /dev/null and b/docs/static/img/device-auth/device-auth-05.png differ diff --git a/docs/static/img/device-auth/device-auth-06.png b/docs/static/img/device-auth/device-auth-06.png new file mode 100644 index 0000000000..ff75932619 Binary files /dev/null and b/docs/static/img/device-auth/device-auth-06.png differ diff --git a/docs/static/img/device-auth/device-auth-07.png b/docs/static/img/device-auth/device-auth-07.png new file mode 100644 index 0000000000..ec14b4368a Binary files /dev/null and b/docs/static/img/device-auth/device-auth-07.png differ diff --git a/docs/static/img/device-auth/device-auth-08.png b/docs/static/img/device-auth/device-auth-08.png new file mode 100644 index 0000000000..db959c8496 Binary files /dev/null and b/docs/static/img/device-auth/device-auth-08.png differ diff --git a/docs/static/img/device-auth/device-auth-09.png b/docs/static/img/device-auth/device-auth-09.png new file mode 100644 index 0000000000..8c0b7aa4c4 Binary files /dev/null and b/docs/static/img/device-auth/device-auth-09.png differ diff --git a/docs/static/img/device-auth/device-auth-10.png b/docs/static/img/device-auth/device-auth-10.png new file mode 100644 index 0000000000..fb38620c9e Binary files /dev/null and b/docs/static/img/device-auth/device-auth-10.png differ diff --git a/docs/static/img/device-auth/device-auth-11.png b/docs/static/img/device-auth/device-auth-11.png new file mode 100644 index 0000000000..dd46cb7bc8 Binary files /dev/null and b/docs/static/img/device-auth/device-auth-11.png differ diff --git a/docs/static/img/guides/integrate/tools/apache-configuration.png b/docs/static/img/guides/integrate/tools/apache-configuration.png new file mode 100644 index 0000000000..9839baf5ab Binary files /dev/null and b/docs/static/img/guides/integrate/tools/apache-configuration.png differ diff --git a/docs/static/img/guides/integrate/tools/apache-redirect_settings.png b/docs/static/img/guides/integrate/tools/apache-redirect_settings.png new file mode 100644 index 0000000000..a1614e0e6a Binary files /dev/null and b/docs/static/img/guides/integrate/tools/apache-redirect_settings.png differ diff --git a/docs/static/img/guides/integrate/tools/apache-urls.png b/docs/static/img/guides/integrate/tools/apache-urls.png new file mode 100644 index 0000000000..934c25168b Binary files /dev/null and b/docs/static/img/guides/integrate/tools/apache-urls.png differ diff --git a/docs/static/img/guides/quickstart/v3_1.png b/docs/static/img/guides/quickstart/v3_1.png new file mode 100644 index 0000000000..58b85bd324 Binary files /dev/null and b/docs/static/img/guides/quickstart/v3_1.png differ diff --git a/docs/static/img/guides/quickstart/v3_10.png b/docs/static/img/guides/quickstart/v3_10.png new file mode 100644 index 0000000000..fa30d09d6c Binary files /dev/null and b/docs/static/img/guides/quickstart/v3_10.png differ diff --git a/docs/static/img/guides/quickstart/v3_11.png b/docs/static/img/guides/quickstart/v3_11.png new file mode 100644 index 0000000000..b36c0e3099 Binary files /dev/null and b/docs/static/img/guides/quickstart/v3_11.png differ diff --git a/docs/static/img/guides/quickstart/v3_12.png b/docs/static/img/guides/quickstart/v3_12.png new file mode 100644 index 0000000000..6592d43687 Binary files /dev/null and b/docs/static/img/guides/quickstart/v3_12.png differ diff --git a/docs/static/img/guides/quickstart/v3_13.png b/docs/static/img/guides/quickstart/v3_13.png new file mode 100644 index 0000000000..c25cff9241 Binary files /dev/null and b/docs/static/img/guides/quickstart/v3_13.png differ diff --git a/docs/static/img/guides/quickstart/v3_14.png b/docs/static/img/guides/quickstart/v3_14.png new file mode 100644 index 0000000000..f644a12d94 Binary files /dev/null and b/docs/static/img/guides/quickstart/v3_14.png differ diff --git a/docs/static/img/guides/quickstart/v3_15.png b/docs/static/img/guides/quickstart/v3_15.png new file mode 100644 index 0000000000..31b03f571d Binary files /dev/null and b/docs/static/img/guides/quickstart/v3_15.png differ diff --git a/docs/static/img/guides/quickstart/v3_16.png b/docs/static/img/guides/quickstart/v3_16.png new file mode 100644 index 0000000000..30be6d0635 Binary files /dev/null and b/docs/static/img/guides/quickstart/v3_16.png differ diff --git a/docs/static/img/guides/quickstart/v3_17.png b/docs/static/img/guides/quickstart/v3_17.png new file mode 100644 index 0000000000..50a378ce12 Binary files /dev/null and b/docs/static/img/guides/quickstart/v3_17.png differ diff --git a/docs/static/img/guides/quickstart/v3_2.png b/docs/static/img/guides/quickstart/v3_2.png new file mode 100644 index 0000000000..1724463298 Binary files /dev/null and b/docs/static/img/guides/quickstart/v3_2.png differ diff --git a/docs/static/img/guides/quickstart/v3_3.png b/docs/static/img/guides/quickstart/v3_3.png new file mode 100644 index 0000000000..39624a118c Binary files /dev/null and b/docs/static/img/guides/quickstart/v3_3.png differ diff --git a/docs/static/img/guides/quickstart/v3_4.png b/docs/static/img/guides/quickstart/v3_4.png new file mode 100644 index 0000000000..6218ca9abf Binary files /dev/null and b/docs/static/img/guides/quickstart/v3_4.png differ diff --git a/docs/static/img/guides/quickstart/v3_5.png b/docs/static/img/guides/quickstart/v3_5.png new file mode 100644 index 0000000000..7857988666 Binary files /dev/null and b/docs/static/img/guides/quickstart/v3_5.png differ diff --git a/docs/static/img/guides/quickstart/v3_6.png b/docs/static/img/guides/quickstart/v3_6.png new file mode 100644 index 0000000000..90594540c0 Binary files /dev/null and b/docs/static/img/guides/quickstart/v3_6.png differ diff --git a/docs/static/img/guides/quickstart/v3_7.png b/docs/static/img/guides/quickstart/v3_7.png new file mode 100644 index 0000000000..11d419f6b7 Binary files /dev/null and b/docs/static/img/guides/quickstart/v3_7.png differ diff --git a/docs/static/img/guides/quickstart/v3_8.png b/docs/static/img/guides/quickstart/v3_8.png new file mode 100644 index 0000000000..12d2ced810 Binary files /dev/null and b/docs/static/img/guides/quickstart/v3_8.png differ diff --git a/docs/static/img/guides/quickstart/v3_9.png b/docs/static/img/guides/quickstart/v3_9.png new file mode 100644 index 0000000000..9815547b0b Binary files /dev/null and b/docs/static/img/guides/quickstart/v3_9.png differ diff --git a/docs/yarn.lock b/docs/yarn.lock index 4b09ca9544..337acb6d35 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -76,7 +76,7 @@ "@algolia/requester-common" "4.14.2" "@algolia/transporter" "4.14.2" -"@algolia/client-search@4.14.2": +"@algolia/client-search@>= 4.9.1 < 6", "@algolia/client-search@4.14.2": version "4.14.2" resolved "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.14.2.tgz" integrity sha512-L5zScdOmcZ6NGiVbLKTvP02UbxZ0njd5Vq9nJAmPFtjffUSOGEp11BmD2oMJ5QvARgx2XbX4KzTTNS5ECYIMWw== @@ -130,7 +130,7 @@ "@algolia/logger-common" "4.14.2" "@algolia/requester-common" "4.14.2" -"@ampproject/remapping@^2.1.0": +"@ampproject/remapping@^2.2.0": version "2.2.0" resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz" integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== @@ -138,14 +138,6 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" -"@ampproject/remapping@^2.2.0": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" - integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - "@apidevtools/json-schema-ref-parser@^10.1.0": version "10.1.0" resolved "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-10.1.0.tgz" @@ -157,32 +149,41 @@ js-yaml "^4.1.0" lodash.clonedeep "^4.5.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.8.3": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz" - integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.8.3": + version "7.22.13" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz" + integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== dependencies: - "@babel/highlight" "^7.18.6" - -"@babel/code-frame@^7.22.10", "@babel/code-frame@^7.22.5": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.10.tgz#1c20e612b768fefa75f6e90d6ecb86329247f0a3" - integrity sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA== - dependencies: - "@babel/highlight" "^7.22.10" + "@babel/highlight" "^7.22.13" chalk "^2.4.2" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.19.4", "@babel/compat-data@^7.20.0": - version "7.20.1" - resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.1.tgz" - integrity sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.19.4", "@babel/compat-data@^7.22.5", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": + version "7.22.20" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.20.tgz" + integrity sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw== -"@babel/compat-data@^7.22.5", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" - integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== +"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.0.0-0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.12.0", "@babel/core@^7.13.0", "@babel/core@^7.18.6", "@babel/core@^7.19.6", "@babel/core@^7.4.0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.4.0-0": + version "7.22.20" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.22.20.tgz" + integrity sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.22.15" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-module-transforms" "^7.22.20" + "@babel/helpers" "^7.22.15" + "@babel/parser" "^7.22.16" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.22.20" + "@babel/types" "^7.22.19" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" -"@babel/core@7.12.9": +"@babel/core@^7.11.6", "@babel/core@7.12.9": version "7.12.9" resolved "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz" integrity sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ== @@ -204,63 +205,12 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.18.6": - version "7.19.6" - resolved "https://registry.npmjs.org/@babel/core/-/core-7.19.6.tgz" - integrity sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg== +"@babel/generator@^7.12.5", "@babel/generator@^7.18.7", "@babel/generator@^7.22.15": + version "7.22.15" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.22.15.tgz" + integrity sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA== dependencies: - "@ampproject/remapping" "^2.1.0" - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.6" - "@babel/helper-compilation-targets" "^7.19.3" - "@babel/helper-module-transforms" "^7.19.6" - "@babel/helpers" "^7.19.4" - "@babel/parser" "^7.19.6" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.6" - "@babel/types" "^7.19.4" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.1" - semver "^6.3.0" - -"@babel/core@^7.19.6": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.10.tgz#aad442c7bcd1582252cb4576747ace35bc122f35" - integrity sha512-fTmqbbUBAwCcre6zPzNngvsI0aNrPZe77AeqvDxWM9Nm+04RrJ3CAmGHA9f7lJQY6ZMhRztNemy4uslDxTX4Qw== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.22.10" - "@babel/generator" "^7.22.10" - "@babel/helper-compilation-targets" "^7.22.10" - "@babel/helper-module-transforms" "^7.22.9" - "@babel/helpers" "^7.22.10" - "@babel/parser" "^7.22.10" - "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.10" - "@babel/types" "^7.22.10" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.2" - semver "^6.3.1" - -"@babel/generator@^7.12.5", "@babel/generator@^7.18.7", "@babel/generator@^7.19.6", "@babel/generator@^7.20.1": - version "7.20.1" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.20.1.tgz" - integrity sha512-u1dMdBUmA7Z0rBB97xh8pIhviK7oItYOkjbsCxTWMknyvbQRBwX7/gn4JXurRdirWMFh+ZtYARqkA6ydogVZpg== - dependencies: - "@babel/types" "^7.20.0" - "@jridgewell/gen-mapping" "^0.3.2" - jsesc "^2.5.1" - -"@babel/generator@^7.22.10": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.10.tgz#c92254361f398e160645ac58831069707382b722" - integrity sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A== - dependencies: - "@babel/types" "^7.22.10" + "@babel/types" "^7.22.15" "@jridgewell/gen-mapping" "^0.3.2" "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" @@ -274,7 +224,7 @@ "@babel/helper-annotate-as-pure@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" + resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz" integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== dependencies: "@babel/types" "^7.22.5" @@ -289,28 +239,18 @@ "@babel/helper-builder-binary-assignment-operator-visitor@^7.22.5": version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.10.tgz#573e735937e99ea75ea30788b57eb52fab7468c9" + resolved "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.10.tgz" integrity sha512-Av0qubwDQxC56DoUReVDeLfMEjYYSN1nZrTUrWkXd7hpU73ymRANkbuDm3yni9npkn+RXy9nNbEJZEzXr7xrfQ== dependencies: "@babel/types" "^7.22.10" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0", "@babel/helper-compilation-targets@^7.19.3": - version "7.20.0" - resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz" - integrity sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ== - dependencies: - "@babel/compat-data" "^7.20.0" - "@babel/helper-validator-option" "^7.18.6" - browserslist "^4.21.3" - semver "^6.3.0" - -"@babel/helper-compilation-targets@^7.22.10", "@babel/helper-compilation-targets@^7.22.5", "@babel/helper-compilation-targets@^7.22.6": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz#01d648bbc25dd88f513d862ee0df27b7d4e67024" - integrity sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0", "@babel/helper-compilation-targets@^7.19.3", "@babel/helper-compilation-targets@^7.22.10", "@babel/helper-compilation-targets@^7.22.15", "@babel/helper-compilation-targets@^7.22.5", "@babel/helper-compilation-targets@^7.22.6": + version "7.22.15" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz" + integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== dependencies: "@babel/compat-data" "^7.22.9" - "@babel/helper-validator-option" "^7.22.5" + "@babel/helper-validator-option" "^7.22.15" browserslist "^4.21.9" lru-cache "^5.1.1" semver "^6.3.1" @@ -330,7 +270,7 @@ "@babel/helper-create-class-features-plugin@^7.22.5": version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.10.tgz#dd2612d59eac45588021ac3d6fa976d08f4e95a3" + resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.10.tgz" integrity sha512-5IBb77txKYQPpOEdUdIhBx8VrZyDCQ+H82H0+5dX1TmuscP5vJKEE3cKurjtIw/vFwzbVH48VweE78kVDBrqjA== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" @@ -353,7 +293,7 @@ "@babel/helper-create-regexp-features-plugin@^7.22.5": version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz#9d8e61a8d9366fe66198f57c40565663de0825f6" + resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz" integrity sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" @@ -374,7 +314,7 @@ "@babel/helper-define-polyfill-provider@^0.4.2": version "0.4.2" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.2.tgz#82c825cadeeeee7aad237618ebbe8fa1710015d7" + resolved "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.2.tgz" integrity sha512-k0qnnOqHn5dK9pZpfD5XXZ9SojAITdCKRn2Lp6rnDGzIbaP0rHyMPk/4wsSxVBVz4RfN0q6VpXWP2pDGIoQ7hw== dependencies: "@babel/helper-compilation-targets" "^7.22.6" @@ -383,15 +323,10 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" -"@babel/helper-environment-visitor@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz" - integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== - -"@babel/helper-environment-visitor@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" - integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== +"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.22.20", "@babel/helper-environment-visitor@^7.22.5": + version "7.22.20" + resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== "@babel/helper-explode-assignable-expression@^7.18.6": version "7.18.6" @@ -400,32 +335,17 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz" - integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== - dependencies: - "@babel/template" "^7.18.10" - "@babel/types" "^7.19.0" - -"@babel/helper-function-name@^7.22.5": +"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0", "@babel/helper-function-name@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz" integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== dependencies: "@babel/template" "^7.22.5" "@babel/types" "^7.22.5" -"@babel/helper-hoist-variables@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz" - integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-hoist-variables@^7.22.5": +"@babel/helper-hoist-variables@^7.18.6", "@babel/helper-hoist-variables@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz" integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== dependencies: "@babel/types" "^7.22.5" @@ -439,49 +359,28 @@ "@babel/helper-member-expression-to-functions@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz#0a7c56117cad3372fbf8d2fb4bf8f8d64a1e76b2" + resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz" integrity sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ== dependencies: "@babel/types" "^7.22.5" -"@babel/helper-module-imports@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz" - integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== +"@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.22.5": + version "7.22.15" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== dependencies: - "@babel/types" "^7.18.6" + "@babel/types" "^7.22.15" -"@babel/helper-module-imports@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz#1a8f4c9f4027d23f520bd76b364d44434a72660c" - integrity sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg== +"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.6", "@babel/helper-module-transforms@^7.22.20", "@babel/helper-module-transforms@^7.22.5": + version "7.22.20" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.20.tgz" + integrity sha512-dLT7JVWIUUxKOs1UnJUBR3S70YK+pKX6AbJgB2vMIvEkZkrfJDbYDJesnPshtKV4LhDOR3Oc5YULeDizRek+5A== dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.6": - version "7.19.6" - resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz" - integrity sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.19.4" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/helper-validator-identifier" "^7.19.1" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.6" - "@babel/types" "^7.19.4" - -"@babel/helper-module-transforms@^7.22.5", "@babel/helper-module-transforms@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz#92dfcb1fbbb2bc62529024f72d942a8c97142129" - integrity sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ== - dependencies: - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-module-imports" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" "@babel/helper-simple-access" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/helper-validator-identifier" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" @@ -492,21 +391,21 @@ "@babel/helper-optimise-call-expression@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" + resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz" integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== dependencies: "@babel/types" "^7.22.5" -"@babel/helper-plugin-utils@7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz" - integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== - "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.22.5" resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz" integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== +"@babel/helper-plugin-utils@7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz" + integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== + "@babel/helper-remap-async-to-generator@^7.18.6", "@babel/helper-remap-async-to-generator@^7.18.9": version "7.18.9" resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz" @@ -519,7 +418,7 @@ "@babel/helper-remap-async-to-generator@^7.22.5", "@babel/helper-remap-async-to-generator@^7.22.9": version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz#53a25b7484e722d7efb9c350c75c032d4628de82" + resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz" integrity sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" @@ -539,23 +438,16 @@ "@babel/helper-replace-supers@^7.22.5", "@babel/helper-replace-supers@^7.22.9": version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz#cbdc27d6d8d18cd22c81ae4293765a5d9afd0779" + resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz" integrity sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg== dependencies: "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-member-expression-to-functions" "^7.22.5" "@babel/helper-optimise-call-expression" "^7.22.5" -"@babel/helper-simple-access@^7.19.4": - version "7.19.4" - resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz" - integrity sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg== - dependencies: - "@babel/types" "^7.19.4" - -"@babel/helper-simple-access@^7.22.5": +"@babel/helper-simple-access@^7.19.4", "@babel/helper-simple-access@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz" integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== dependencies: "@babel/types" "^7.22.5" @@ -569,49 +461,32 @@ "@babel/helper-skip-transparent-expression-wrappers@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" + resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz" integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== dependencies: "@babel/types" "^7.22.5" -"@babel/helper-split-export-declaration@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz" - integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-split-export-declaration@^7.22.6": +"@babel/helper-split-export-declaration@^7.18.6", "@babel/helper-split-export-declaration@^7.22.6": version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz" integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== dependencies: "@babel/types" "^7.22.5" -"@babel/helper-string-parser@^7.19.4": - version "7.19.4" - resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz" - integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== - "@babel/helper-string-parser@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz" integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== -"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": - version "7.19.1" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz" - integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== +"@babel/helper-validator-identifier@^7.19.1", "@babel/helper-validator-identifier@^7.22.19", "@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.22.5": + version "7.22.20" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== -"@babel/helper-validator-identifier@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" - integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== - -"@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.22.5": - version "7.22.5" - resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz" - integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw== +"@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.22.15", "@babel/helper-validator-option@^7.22.5": + version "7.22.15" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz" + integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA== "@babel/helper-wrap-function@^7.18.9": version "7.19.0" @@ -625,58 +500,35 @@ "@babel/helper-wrap-function@^7.22.9": version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.10.tgz#d845e043880ed0b8c18bd194a12005cb16d2f614" + resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.10.tgz" integrity sha512-OnMhjWjuGYtdoO3FmsEFWvBStBAe2QOgwOLsLNDjN+aaiMD8InJk1/O3HSD8lkqTjCgg5YI34Tz15KNNA3p+nQ== dependencies: "@babel/helper-function-name" "^7.22.5" "@babel/template" "^7.22.5" "@babel/types" "^7.22.10" -"@babel/helpers@^7.12.5", "@babel/helpers@^7.19.4": - version "7.20.1" - resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.1.tgz" - integrity sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg== +"@babel/helpers@^7.12.5", "@babel/helpers@^7.22.15": + version "7.22.15" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.15.tgz" + integrity sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw== dependencies: - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.20.1" - "@babel/types" "^7.20.0" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.22.15" + "@babel/types" "^7.22.15" -"@babel/helpers@^7.22.10": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.10.tgz#ae6005c539dfbcb5cd71fb51bfc8a52ba63bc37a" - integrity sha512-a41J4NW8HyZa1I1vAndrraTlPZ/eZoga2ZgS7fEr0tZJGVU4xqdE80CEm0CcNjha5EZ8fTBYLKHF0kqDUuAwQw== +"@babel/highlight@^7.22.13": + version "7.22.20" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz" + integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== dependencies: - "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.10" - "@babel/types" "^7.22.10" - -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== - dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/highlight@^7.22.10": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.10.tgz#02a3f6d8c1cb4521b2fd0ab0da8f4739936137d7" - integrity sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ== - dependencies: - "@babel/helper-validator-identifier" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/parser@^7.12.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.8", "@babel/parser@^7.19.6", "@babel/parser@^7.20.1": - version "7.20.1" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.20.1.tgz" - integrity sha512-hp0AYxaZJhxULfM1zyp7Wgr+pSUKBcP3M+PHnSzWGdXOzg/kHWIgiUWARvubhUKGOEw3xqY4x+lyZ9ytBVcELw== - -"@babel/parser@^7.22.10", "@babel/parser@^7.22.5": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.10.tgz#e37634f9a12a1716136c44624ef54283cabd3f55" - integrity sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ== +"@babel/parser@^7.12.7", "@babel/parser@^7.18.8", "@babel/parser@^7.22.15", "@babel/parser@^7.22.16": + version "7.22.16" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz" + integrity sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" @@ -687,7 +539,7 @@ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz#87245a21cd69a73b0b81bcda98d443d6df08f05e" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz" integrity sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -703,7 +555,7 @@ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz#fef09f9499b1f1c930da8a0c419db42167d792ca" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz" integrity sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -785,15 +637,6 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-proposal-object-rest-spread@7.12.1": - version "7.12.1" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz" - integrity sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-transform-parameters" "^7.12.1" - "@babel/plugin-proposal-object-rest-spread@^7.19.4": version "7.19.4" resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.19.4.tgz" @@ -805,6 +648,15 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.18.8" +"@babel/plugin-proposal-object-rest-spread@7.12.1": + version "7.12.1" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz" + integrity sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-transform-parameters" "^7.12.1" + "@babel/plugin-proposal-optional-catch-binding@^7.18.6": version "7.18.6" resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz" @@ -830,11 +682,6 @@ "@babel/helper-create-class-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": - version "7.21.0-placeholder-for-preset-env.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" - integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== - "@babel/plugin-proposal-private-property-in-object@^7.18.6": version "7.18.6" resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz" @@ -845,6 +692,11 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" +"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": + version "7.21.0-placeholder-for-preset-env.2" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz" + integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== + "@babel/plugin-proposal-unicode-property-regex@^7.18.6", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": version "7.18.6" resolved "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz" @@ -897,21 +749,21 @@ "@babel/plugin-syntax-import-assertions@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz#07d252e2aa0bc6125567f742cd58619cb14dce98" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz" integrity sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-import-attributes@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz#ab840248d834410b829f569f5262b9e517555ecb" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz" integrity sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-import-meta@^7.10.4": version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz" integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== dependencies: "@babel/helper-plugin-utils" "^7.10.4" @@ -923,13 +775,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@7.12.1": - version "7.12.1" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz" - integrity sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-jsx@^7.18.6": version "7.18.6" resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz" @@ -937,6 +782,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-syntax-jsx@7.12.1": + version "7.12.1" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz" + integrity sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-logical-assignment-operators@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" @@ -958,7 +810,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-object-rest-spread@7.8.3", "@babel/plugin-syntax-object-rest-spread@^7.8.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3": +"@babel/plugin-syntax-object-rest-spread@^7.8.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3", "@babel/plugin-syntax-object-rest-spread@7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz" integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== @@ -1002,7 +854,7 @@ "@babel/plugin-syntax-unicode-sets-regex@^7.18.6": version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz" integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.18.6" @@ -1017,14 +869,14 @@ "@babel/plugin-transform-arrow-functions@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz#e5ba566d0c58a5b2ba2a8b795450641950b71958" + resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz" integrity sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-async-generator-functions@^7.22.10": version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.10.tgz#45946cd17f915b10e65c29b8ed18a0a50fc648c8" + resolved "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.10.tgz" integrity sha512-eueE8lvKVzq5wIObKK/7dvoeKJ+xc6TvRn6aysIjS6pSCeLy7S/eVi7pEQknZqyqvzaNKdDtem8nUNTBgDVR2g== dependencies: "@babel/helper-environment-visitor" "^7.22.5" @@ -1043,7 +895,7 @@ "@babel/plugin-transform-async-to-generator@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz#c7a85f44e46f8952f6d27fe57c2ed3cc084c3775" + resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz" integrity sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ== dependencies: "@babel/helper-module-imports" "^7.22.5" @@ -1059,7 +911,7 @@ "@babel/plugin-transform-block-scoped-functions@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz#27978075bfaeb9fa586d3cb63a3d30c1de580024" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz" integrity sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1073,14 +925,14 @@ "@babel/plugin-transform-block-scoping@^7.22.10": version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.10.tgz#88a1dccc3383899eb5e660534a76a22ecee64faa" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.10.tgz" integrity sha512-1+kVpGAOOI1Albt6Vse7c8pHzcZQdQKW+wJH+g8mCaszOdDVwRXa/slHPqIw+oJAJANTKDMuM2cBdV0Dg618Vg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-class-properties@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz#97a56e31ad8c9dc06a0b3710ce7803d5a48cca77" + resolved "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz" integrity sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ== dependencies: "@babel/helper-create-class-features-plugin" "^7.22.5" @@ -1088,7 +940,7 @@ "@babel/plugin-transform-class-static-block@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz#3e40c46f048403472d6f4183116d5e46b1bff5ba" + resolved "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz" integrity sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA== dependencies: "@babel/helper-create-class-features-plugin" "^7.22.5" @@ -1112,7 +964,7 @@ "@babel/plugin-transform-classes@^7.22.6": version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz#e04d7d804ed5b8501311293d1a0e6d43e94c3363" + resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz" integrity sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" @@ -1134,7 +986,7 @@ "@babel/plugin-transform-computed-properties@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz#cd1e994bf9f316bd1c2dafcd02063ec261bb3869" + resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz" integrity sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1149,7 +1001,7 @@ "@babel/plugin-transform-destructuring@^7.22.10": version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.10.tgz#38e2273814a58c810b6c34ea293be4973c4eb5e2" + resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.10.tgz" integrity sha512-dPJrL0VOyxqLM9sritNbMSGx/teueHF/htMKrPT7DNxccXxRDPYqlgPFFdr8u+F+qUZOkZoXue/6rL5O5GduEw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1164,7 +1016,7 @@ "@babel/plugin-transform-dotall-regex@^7.22.5", "@babel/plugin-transform-dotall-regex@^7.4.4": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz#dbb4f0e45766eb544e193fb00e65a1dd3b2a4165" + resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz" integrity sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.22.5" @@ -1179,14 +1031,14 @@ "@babel/plugin-transform-duplicate-keys@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz#b6e6428d9416f5f0bba19c70d1e6e7e0b88ab285" + resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz" integrity sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-dynamic-import@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz#d6908a8916a810468c4edff73b5b75bda6ad393e" + resolved "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz" integrity sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1202,7 +1054,7 @@ "@babel/plugin-transform-exponentiation-operator@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz#402432ad544a1f9a480da865fda26be653e48f6a" + resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz" integrity sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g== dependencies: "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.5" @@ -1210,7 +1062,7 @@ "@babel/plugin-transform-export-namespace-from@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz#57c41cb1d0613d22f548fddd8b288eedb9973a5b" + resolved "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz" integrity sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1225,7 +1077,7 @@ "@babel/plugin-transform-for-of@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz#ab1b8a200a8f990137aff9a084f8de4099ab173f" + resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz" integrity sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1241,7 +1093,7 @@ "@babel/plugin-transform-function-name@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz#935189af68b01898e0d6d99658db6b164205c143" + resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz" integrity sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg== dependencies: "@babel/helper-compilation-targets" "^7.22.5" @@ -1250,7 +1102,7 @@ "@babel/plugin-transform-json-strings@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz#14b64352fdf7e1f737eed68de1a1468bd2a77ec0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz" integrity sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1265,14 +1117,14 @@ "@babel/plugin-transform-literals@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz#e9341f4b5a167952576e23db8d435849b1dd7920" + resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz" integrity sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-logical-assignment-operators@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz#66ae5f068fd5a9a5dc570df16f56c2a8462a9d6c" + resolved "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz" integrity sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1287,7 +1139,7 @@ "@babel/plugin-transform-member-expression-literals@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz#4fcc9050eded981a468347dd374539ed3e058def" + resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz" integrity sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1302,7 +1154,7 @@ "@babel/plugin-transform-modules-amd@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz#4e045f55dcf98afd00f85691a68fc0780704f526" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz" integrity sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ== dependencies: "@babel/helper-module-transforms" "^7.22.5" @@ -1319,7 +1171,7 @@ "@babel/plugin-transform-modules-commonjs@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz#7d9875908d19b8c0536085af7b053fd5bd651bfa" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz" integrity sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA== dependencies: "@babel/helper-module-transforms" "^7.22.5" @@ -1338,7 +1190,7 @@ "@babel/plugin-transform-modules-systemjs@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz#18c31410b5e579a0092638f95c896c2a98a5d496" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz" integrity sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ== dependencies: "@babel/helper-hoist-variables" "^7.22.5" @@ -1356,7 +1208,7 @@ "@babel/plugin-transform-modules-umd@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz#4694ae40a87b1745e3775b6a7fe96400315d4f98" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz" integrity sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ== dependencies: "@babel/helper-module-transforms" "^7.22.5" @@ -1372,7 +1224,7 @@ "@babel/plugin-transform-named-capturing-groups-regex@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f" + resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz" integrity sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.22.5" @@ -1387,14 +1239,14 @@ "@babel/plugin-transform-new-target@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz#1b248acea54ce44ea06dfd37247ba089fcf9758d" + resolved "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz" integrity sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-nullish-coalescing-operator@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz#f8872c65776e0b552e0849d7596cddd416c3e381" + resolved "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz" integrity sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1402,7 +1254,7 @@ "@babel/plugin-transform-numeric-separator@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz#57226a2ed9e512b9b446517ab6fa2d17abb83f58" + resolved "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz" integrity sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1410,7 +1262,7 @@ "@babel/plugin-transform-object-rest-spread@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz#9686dc3447df4753b0b2a2fae7e8bc33cdc1f2e1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz" integrity sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ== dependencies: "@babel/compat-data" "^7.22.5" @@ -1429,7 +1281,7 @@ "@babel/plugin-transform-object-super@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz#794a8d2fcb5d0835af722173c1a9d704f44e218c" + resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz" integrity sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1437,7 +1289,7 @@ "@babel/plugin-transform-optional-catch-binding@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz#842080be3076703be0eaf32ead6ac8174edee333" + resolved "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz" integrity sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1445,7 +1297,7 @@ "@babel/plugin-transform-optional-chaining@^7.22.10", "@babel/plugin-transform-optional-chaining@^7.22.5": version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.10.tgz#076d28a7e074392e840d4ae587d83445bac0372a" + resolved "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.10.tgz" integrity sha512-MMkQqZAZ+MGj+jGTG3OTuhKeBpNcO+0oCEbrGNEaOmiEn+1MzRyQlYsruGiU8RTK3zV6XwrVJTmwiDOyYK6J9g== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1454,7 +1306,7 @@ "@babel/plugin-transform-parameters@^7.12.1", "@babel/plugin-transform-parameters@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz#c3542dd3c39b42c8069936e48717a8d179d63a18" + resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz" integrity sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1468,7 +1320,7 @@ "@babel/plugin-transform-private-methods@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz#21c8af791f76674420a147ae62e9935d790f8722" + resolved "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz" integrity sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA== dependencies: "@babel/helper-create-class-features-plugin" "^7.22.5" @@ -1476,7 +1328,7 @@ "@babel/plugin-transform-private-property-in-object@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz#07a77f28cbb251546a43d175a1dda4cf3ef83e32" + resolved "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz" integrity sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" @@ -1493,14 +1345,14 @@ "@babel/plugin-transform-property-literals@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz#b5ddabd73a4f7f26cd0e20f5db48290b88732766" + resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz" integrity sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-react-constant-elements@^7.18.12": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.22.5.tgz#6dfa7c1c37f7d7279e417ceddf5a04abb8bb9c29" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.22.5.tgz" integrity sha512-BF5SXoO+nX3h5OhlN78XbbDrBOffv+AxPP2ENaJOVqjWCgBDeOY3WcaUcddutGSfoap+5NEQ/q/4I3WZIvgkXA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1548,7 +1400,7 @@ "@babel/plugin-transform-regenerator@^7.22.10": version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz#8ceef3bd7375c4db7652878b0241b2be5d0c3cca" + resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz" integrity sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1563,7 +1415,7 @@ "@babel/plugin-transform-reserved-words@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz#832cd35b81c287c4bcd09ce03e22199641f964fb" + resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz" integrity sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1589,7 +1441,7 @@ "@babel/plugin-transform-shorthand-properties@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz#6e277654be82b5559fc4b9f58088507c24f0c624" + resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz" integrity sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1604,7 +1456,7 @@ "@babel/plugin-transform-spread@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz#6487fd29f229c95e284ba6c98d65eafb893fea6b" + resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz" integrity sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1619,7 +1471,7 @@ "@babel/plugin-transform-sticky-regex@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz#295aba1595bfc8197abd02eae5fc288c0deb26aa" + resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz" integrity sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1633,7 +1485,7 @@ "@babel/plugin-transform-template-literals@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz#8f38cf291e5f7a8e60e9f733193f0bcc10909bff" + resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz" integrity sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1647,7 +1499,7 @@ "@babel/plugin-transform-typeof-symbol@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz#5e2ba478da4b603af8673ff7c54f75a97b716b34" + resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz" integrity sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1670,14 +1522,14 @@ "@babel/plugin-transform-unicode-escapes@^7.22.10": version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz#c723f380f40a2b2f57a62df24c9005834c8616d9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz" integrity sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-unicode-property-regex@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz#098898f74d5c1e86660dc112057b2d11227f1c81" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz" integrity sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.22.5" @@ -1693,7 +1545,7 @@ "@babel/plugin-transform-unicode-regex@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz#ce7e7bb3ef208c4ff67e02a22816656256d7a183" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz" integrity sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.22.5" @@ -1701,7 +1553,7 @@ "@babel/plugin-transform-unicode-sets-regex@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz#77788060e511b708ffc7d42fdfbc5b37c3004e91" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz" integrity sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.22.5" @@ -1790,7 +1642,7 @@ "@babel/preset-env@^7.19.4": version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.10.tgz#3263b9fe2c8823d191d28e61eac60a79f9ce8a0f" + resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.10.tgz" integrity sha512-riHpLb1drNkpLlocmSyEg4oYJIQFeXAK/d7rI6mbD0XsvoTOOweXDmQPG/ErxsEhWk3rl3Q/3F6RFQlVFS8m0A== dependencies: "@babel/compat-data" "^7.22.9" @@ -1874,15 +1726,6 @@ core-js-compat "^3.31.0" semver "^6.3.1" -"@babel/preset-modules@0.1.6-no-external-plugins": - version "0.1.6-no-external-plugins" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" - integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/types" "^7.4.4" - esutils "^2.0.2" - "@babel/preset-modules@^0.1.5": version "0.1.5" resolved "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz" @@ -1894,6 +1737,15 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" +"@babel/preset-modules@0.1.6-no-external-plugins": + version "0.1.6-no-external-plugins" + resolved "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz" + integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + "@babel/preset-react@^7.18.6": version "7.18.6" resolved "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz" @@ -1917,7 +1769,7 @@ "@babel/regjsgen@^0.8.0": version "0.8.0" - resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" + resolved "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime-corejs3@^7.18.6": @@ -1937,109 +1789,55 @@ "@babel/runtime@^7.20.13": version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.10.tgz#ae3e9631fd947cb7e3610d3e9d8fef5f76696682" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz" integrity sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ== dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.12.7", "@babel/template@^7.18.10": - version "7.18.10" - resolved "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz" - integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== +"@babel/template@^7.12.7", "@babel/template@^7.18.10", "@babel/template@^7.22.15", "@babel/template@^7.22.5": + version "7.22.15" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.18.10" - "@babel/types" "^7.18.10" + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" -"@babel/template@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" - integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw== +"@babel/traverse@^7.12.9", "@babel/traverse@^7.18.8", "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.22.15", "@babel/traverse@^7.22.20": + version "7.22.20" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.20.tgz" + integrity sha512-eU260mPZbU7mZ0N+X10pxXhQFMGTeLb9eFS0mxehS8HZp9o1uSnFeWQuG1UPrlxgA7QoUzFhOnilHDp0AXCyHw== dependencies: - "@babel/code-frame" "^7.22.5" - "@babel/parser" "^7.22.5" - "@babel/types" "^7.22.5" - -"@babel/traverse@^7.12.9", "@babel/traverse@^7.18.8", "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.19.6", "@babel/traverse@^7.20.1": - version "7.20.1" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz" - integrity sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.20.1" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.20.1" - "@babel/types" "^7.20.0" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/traverse@^7.22.10": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.10.tgz#20252acb240e746d27c2e82b4484f199cf8141aa" - integrity sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig== - dependencies: - "@babel/code-frame" "^7.22.10" - "@babel/generator" "^7.22.10" - "@babel/helper-environment-visitor" "^7.22.5" + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.22.15" + "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-function-name" "^7.22.5" "@babel/helper-hoist-variables" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.22.10" - "@babel/types" "^7.22.10" + "@babel/parser" "^7.22.16" + "@babel/types" "^7.22.19" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.12.7", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.19.4", "@babel/types@^7.20.0", "@babel/types@^7.4.4": - version "7.20.0" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.20.0.tgz" - integrity sha512-Jlgt3H0TajCW164wkTOTzHkZb075tMQMULzrLUoUeKmO7eFL96GgDxf7/Axhc5CAuKE3KFyVW1p6ysKsi2oXAg== - dependencies: - "@babel/helper-string-parser" "^7.19.4" - "@babel/helper-validator-identifier" "^7.19.1" - to-fast-properties "^2.0.0" - -"@babel/types@^7.22.10", "@babel/types@^7.22.5": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.10.tgz#4a9e76446048f2c66982d1a989dd12b8a2d2dc03" - integrity sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg== +"@babel/types@^7.12.7", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.19.4", "@babel/types@^7.20.0", "@babel/types@^7.22.10", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.4.4": + version "7.22.19" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.22.19.tgz" + integrity sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg== dependencies: "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.19" to-fast-properties "^2.0.0" +"@braintree/sanitize-url@^6.0.1": + version "6.0.4" + resolved "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz" + integrity sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A== + "@bufbuild/buf-darwin-arm64@1.15.0": version "1.15.0" resolved "https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.15.0.tgz" integrity sha512-sLN6uGc8sIBALa7Q4fB6rW9NM0MXK32pH6RRDUdl7aDrp/3A6TLKKBGiHcY81axUyxDTUNFb8dOwhHTI2H8FzQ== -"@bufbuild/buf-darwin-x64@1.15.0": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.15.0.tgz#33023704c80c8dea32e463e0728bb6297a0abf69" - integrity sha512-iHml29I/hOl7ORyp9ohiV7fC1WqPbM5UjogwVpA8j06o5SgxRhp42nd80XRAXCM+65ecwiu5JVuspicGzQFOgg== - -"@bufbuild/buf-linux-aarch64@1.15.0": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.15.0.tgz#8e833440f090b1cafda143135f797aebc6d9e6b0" - integrity sha512-YQHXqV1HhdpmIUrYg+gZNWCf43XHJJO5TlJT+pzXB/92PoN8gNP3KdxeRaM2sExcCs91G6zy1/Ms9N6DpeidUQ== - -"@bufbuild/buf-linux-x64@1.15.0": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.15.0.tgz#68c2cc39833c018c1e2e9660e9a87f6f61df52e3" - integrity sha512-DD2OcsfofawRPQKXLFMqV2GSzi4WyE7kKE1PvXBtJy7sombv5TM26vgdb+DQv4T4Z2i7vhKshnflNkfd3QXtXA== - -"@bufbuild/buf-win32-arm64@1.15.0": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.15.0.tgz#d9b441854bd4bd26ab678da31936561ab8d82368" - integrity sha512-wk65iDXWRicfrt/9Gb1voAn9eGP2giQfKMrKOoEyytnDHFolMSmQimKH6iQ1uS5Vn0gI/BVp582cF1m9YsbXEg== - -"@bufbuild/buf-win32-x64@1.15.0": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.15.0.tgz#48ed4c94d195d6a763686821ef23da6afa8a868b" - integrity sha512-KVoMj52ghYfLwGjQ+t19XZiQy8jGSGUYIe/yVZz08rsm5msXHGYOt++Bk3wr48rcv8gts8jo2/h1Ebkj+F6emw== - "@bufbuild/buf@^1.14.0": version "1.15.0" resolved "https://registry.npmjs.org/@bufbuild/buf/-/buf-1.15.0.tgz" @@ -2167,7 +1965,7 @@ chalk "^4.1.2" tslib "^2.4.0" -"@docusaurus/mdx-loader@2.2.0", "@docusaurus/mdx-loader@>=2.0.1 <2.3.0": +"@docusaurus/mdx-loader@>=2.0.1 <2.3.0", "@docusaurus/mdx-loader@2.2.0": version "2.2.0" resolved "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.2.0.tgz" integrity sha512-X2bzo3T0jW0VhUU+XdQofcEeozXOTmKQMvc8tUnWRdTnCvj4XEcBVdC3g+/jftceluiwSTNRAX4VBOJdNt18jA== @@ -2226,7 +2024,7 @@ utility-types "^3.10.0" webpack "^5.73.0" -"@docusaurus/plugin-content-docs@2.2.0", "@docusaurus/plugin-content-docs@>=2.0.1 <2.3.0": +"@docusaurus/plugin-content-docs@>=2.0.1 <2.3.0", "@docusaurus/plugin-content-docs@2.2.0": version "2.2.0" resolved "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.2.0.tgz" integrity sha512-BOazBR0XjzsHE+2K1wpNxz5QZmrJgmm3+0Re0EVPYFGW8qndCWGNtXW/0lGKhecVPML8yyFeAmnUCIs7xM2wPw== @@ -2327,7 +2125,7 @@ "@docusaurus/theme-search-algolia" "2.2.0" "@docusaurus/types" "2.2.0" -"@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2": +"@docusaurus/react-loadable@5.5.2": version "5.5.2" resolved "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz" integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ== @@ -2335,7 +2133,7 @@ "@types/react" "*" prop-types "^15.6.2" -"@docusaurus/theme-classic@2.2.0": +"@docusaurus/theme-classic@>=2.2.0", "@docusaurus/theme-classic@2.2.0": version "2.2.0" resolved "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.2.0.tgz" integrity sha512-kjbg/qJPwZ6H1CU/i9d4l/LcFgnuzeiGgMQlt6yPqKo0SOJIBMPuz7Rnu3r/WWbZFPi//o8acclacOzmXdUUEg== @@ -2366,7 +2164,7 @@ tslib "^2.4.0" utility-types "^3.10.0" -"@docusaurus/theme-common@2.2.0", "@docusaurus/theme-common@>=2.0.1 <2.3.0": +"@docusaurus/theme-common@>=2.0.1 <2.3.0", "@docusaurus/theme-common@2.2.0": version "2.2.0" resolved "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.2.0.tgz" integrity sha512-R8BnDjYoN90DCL75gP7qYQfSjyitXuP9TdzgsKDmSFPNyrdE3twtPNa2dIN+h+p/pr+PagfxwWbd6dn722A1Dw== @@ -2416,7 +2214,7 @@ fs-extra "^10.1.0" tslib "^2.4.0" -"@docusaurus/types@2.2.0": +"@docusaurus/types@*", "@docusaurus/types@2.2.0": version "2.2.0" resolved "https://registry.npmjs.org/@docusaurus/types/-/types-2.2.0.tgz" integrity sha512-b6xxyoexfbRNRI8gjblzVOnLr4peCJhGbYGPpJ3LFqpi5nsFfoK4mmDLvWdeah0B7gmJeXabN7nQkFoqeSdmOw== @@ -2437,7 +2235,7 @@ dependencies: tslib "^2.4.0" -"@docusaurus/utils-validation@2.2.0", "@docusaurus/utils-validation@>=2.0.1 <2.3.0": +"@docusaurus/utils-validation@>=2.0.1 <2.3.0", "@docusaurus/utils-validation@2.2.0": version "2.2.0" resolved "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.2.0.tgz" integrity sha512-I1hcsG3yoCkasOL5qQAYAfnmVoLei7apugT6m4crQjmDGxq+UkiRrq55UqmDDyZlac/6ax/JC0p+usZ6W4nVyg== @@ -2448,7 +2246,7 @@ js-yaml "^4.1.0" tslib "^2.4.0" -"@docusaurus/utils@2.2.0", "@docusaurus/utils@>=2.0.1 <2.3.0": +"@docusaurus/utils@>=2.0.1 <2.3.0", "@docusaurus/utils@2.2.0": version "2.2.0" resolved "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.2.0.tgz" integrity sha512-oNk3cjvx7Tt1Lgh/aeZAmFpGV2pDr5nHKrBVx6hTkzGhrnMuQqLt6UPlQjdYQ3QHXwyF/ZtZMO1D5Pfi0lu7SA== @@ -2627,7 +2425,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": +"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -2767,47 +2565,47 @@ "@svgr/babel-plugin-add-jsx-attribute@^6.5.1": version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz#74a5d648bd0347bda99d82409d87b8ca80b9a1ba" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz" integrity sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ== "@svgr/babel-plugin-remove-jsx-attribute@*": version "8.0.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz#69177f7937233caca3a1afb051906698f2f59186" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz" integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA== "@svgr/babel-plugin-remove-jsx-empty-expression@*": version "8.0.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz#c2c48104cfd7dcd557f373b70a56e9e3bdae1d44" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz" integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA== "@svgr/babel-plugin-replace-jsx-attribute-value@^6.5.1": version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.5.1.tgz#fb9d22ea26d2bc5e0a44b763d4c46d5d3f596c60" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.5.1.tgz" integrity sha512-8DPaVVE3fd5JKuIC29dqyMB54sA6mfgki2H2+swh+zNJoynC8pMPzOkidqHOSc6Wj032fhl8Z0TVn1GiPpAiJg== "@svgr/babel-plugin-svg-dynamic-title@^6.5.1": version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.5.1.tgz#01b2024a2b53ffaa5efceaa0bf3e1d5a4c520ce4" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.5.1.tgz" integrity sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw== "@svgr/babel-plugin-svg-em-dimensions@^6.5.1": version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.5.1.tgz#dd3fa9f5b24eb4f93bcf121c3d40ff5facecb217" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.5.1.tgz" integrity sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA== "@svgr/babel-plugin-transform-react-native-svg@^6.5.1": version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.5.1.tgz#1d8e945a03df65b601551097d8f5e34351d3d305" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.5.1.tgz" integrity sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg== "@svgr/babel-plugin-transform-svg-component@^6.5.1": version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.5.1.tgz#48620b9e590e25ff95a80f811544218d27f8a250" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.5.1.tgz" integrity sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ== "@svgr/babel-preset@^6.5.1": version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-6.5.1.tgz#b90de7979c8843c5c580c7e2ec71f024b49eb828" + resolved "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-6.5.1.tgz" integrity sha512-6127fvO/FF2oi5EzSQOAjo1LE3OtNVh11R+/8FXa+mHx1ptAaS4cknIjnUA7e6j6fwGGJ17NzaTJFUwOV2zwCw== dependencies: "@svgr/babel-plugin-add-jsx-attribute" "^6.5.1" @@ -2819,9 +2617,9 @@ "@svgr/babel-plugin-transform-react-native-svg" "^6.5.1" "@svgr/babel-plugin-transform-svg-component" "^6.5.1" -"@svgr/core@^6.5.1": +"@svgr/core@*", "@svgr/core@^6.0.0", "@svgr/core@^6.5.1": version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/core/-/core-6.5.1.tgz#d3e8aa9dbe3fbd747f9ee4282c1c77a27410488a" + resolved "https://registry.npmjs.org/@svgr/core/-/core-6.5.1.tgz" integrity sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw== dependencies: "@babel/core" "^7.19.6" @@ -2832,7 +2630,7 @@ "@svgr/hast-util-to-babel-ast@^6.5.1": version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.5.1.tgz#81800bd09b5bcdb968bf6ee7c863d2288fdb80d2" + resolved "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.5.1.tgz" integrity sha512-1hnUxxjd83EAxbL4a0JDJoD3Dao3hmjvyvyEV8PzWmLK3B9m9NPlW7GKjFyoWE8nM7HnXzPcmmSyOW8yOddSXw== dependencies: "@babel/types" "^7.20.0" @@ -2840,7 +2638,7 @@ "@svgr/plugin-jsx@^6.5.1": version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-6.5.1.tgz#0e30d1878e771ca753c94e69581c7971542a7072" + resolved "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-6.5.1.tgz" integrity sha512-+UdQxI3jgtSjCykNSlEMuy1jSRQlGC7pqBCPvkG/2dATdWo082zHTTK3uhnAju2/6XpE6B5mZ3z4Z8Ns01S8Gw== dependencies: "@babel/core" "^7.19.6" @@ -2850,7 +2648,7 @@ "@svgr/plugin-svgo@^6.5.1": version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-6.5.1.tgz#0f91910e988fc0b842f88e0960c2862e022abe84" + resolved "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-6.5.1.tgz" integrity sha512-omvZKf8ixP9z6GWgwbtmP9qQMPX4ODXi+wzbVZgomNFsUIlHA1sf4fThdwTWSsZGgvGAG6yE+b/F5gWUkcZ/iQ== dependencies: cosmiconfig "^7.0.1" @@ -2859,7 +2657,7 @@ "@svgr/webpack@^6.2.1": version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-6.5.1.tgz#ecf027814fc1cb2decc29dc92f39c3cf691e40e8" + resolved "https://registry.npmjs.org/@svgr/webpack/-/webpack-6.5.1.tgz" integrity sha512-cQ/AsnBkXPkEK8cLbv4Dm7JGXq2XrumKnL1dRpJD9rIO2fTIlJI9a1uCciYG1F2aUsox/hJQyNGbt3soDxSRkA== dependencies: "@babel/core" "^7.19.6" @@ -2876,52 +2674,7 @@ resolved "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.78.tgz" integrity sha512-596KRua/d5Gx1buHKKchSyHuwoIL4S1BRD/wCvYNLNZ3xOzcuBBmXOjrDVigKi1ztNDeS07p30RO5UyYur0XAA== -"@swc/core-darwin-x64@1.3.78": - version "1.3.78" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.3.78.tgz#0279831884f3275eea67c34a87fb503b35a6f6c7" - integrity sha512-w0RsD1onQAj0vuLAoOVi48HgnW6D6oBEIZP17l0HYejCDBZ+FRZLjml7wgNAWMqHcd2qNRqgtZ+v7aLza2JtBQ== - -"@swc/core-linux-arm-gnueabihf@1.3.78": - version "1.3.78" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.78.tgz#4268cac6945329f47216ae57e8ad17bf0e5cc294" - integrity sha512-v1CpRn+H6fha1WIqmdRvJM40pFdjUHrGfhf4Ygci72nlAU41l5XimN8Iwkm8FgIwf2wnv0lLzedSM4IHvpq/yA== - -"@swc/core-linux-arm64-gnu@1.3.78": - version "1.3.78" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.78.tgz#dcedcc8fb3addaca8660c8c712376850a04d5251" - integrity sha512-Sis17dz9joJRFVvR/gteOZSUNrrrioo81RQzani0Zr5ZZOfWLMTB9DA+0MVlfnVa2taYcsJHJZFoAv9JkLwbzg== - -"@swc/core-linux-arm64-musl@1.3.78": - version "1.3.78" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.78.tgz#c09c29419879e819a1790994da614887714fa675" - integrity sha512-E5F8/qp+QupnfBnsP4vN1PKyCmAHYHDG1GMyPE/zLFOUYLgw+jK4C9rfyLBR0o2bWo1ay2WCIjusBZD9XHGOSA== - -"@swc/core-linux-x64-gnu@1.3.78": - version "1.3.78" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.78.tgz#e295812b2c871a559fda2314c7063f965d7a3139" - integrity sha512-iDxa+RknnTQlyy+WfPor1FM6y44ERNI2E0xiUV6gV6uPwegCngi8LFC+E7IvP6+p+yXtAkesunAaiZ8nn0s+rw== - -"@swc/core-linux-x64-musl@1.3.78": - version "1.3.78" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.78.tgz#e9742111dc62b857492559491cff277ce7f952f2" - integrity sha512-dWtIYUFL5sMTE2UKshkXTusHcK8+zAhhGzvqWq1wJS45pqTlrAbzpyqB780fle880x3A6DMitWmsAFARdNzpuQ== - -"@swc/core-win32-arm64-msvc@1.3.78": - version "1.3.78" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.78.tgz#59d76fbd58e0efcc003cf2a08984e697d285be8d" - integrity sha512-CXFaGEc2M9Su3UoUMC8AnzKb9g+GwPxXfakLWZsjwS448h6jcreExq3nwtBNdVGzQ26xqeVLMFfb1l/oK99Hwg== - -"@swc/core-win32-ia32-msvc@1.3.78": - version "1.3.78" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.78.tgz#5aac382bc8e1d3c74228f823033e19d04720cb28" - integrity sha512-FaH1jwWnJpWkdImpMoiZpMg9oy9UUyZwltzN7hFwjR48e3Li82cRFb+9PifIBHCUSBM+CrrsJXbHP213IMVAyw== - -"@swc/core-win32-x64-msvc@1.3.78": - version "1.3.78" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.78.tgz#3ee7a3bd46503bf81a88343d545d7e1aca8d57de" - integrity sha512-oYxa+tPdhlx1aH14AIoF6kvVjo49tEOW0drNqoEaVHufvgH0y43QU2Jum3b2+xXztmMRtzK2CSN3GPOAXDKKKg== - -"@swc/core@^1.3.74": +"@swc/core@^1.2.147", "@swc/core@^1.3.74": version "1.3.78" resolved "https://registry.npmjs.org/@swc/core/-/core-1.3.78.tgz" integrity sha512-y6DQP571v7fbUUY7nz5G4lNIRGofuO48K5pGhD9VnuOCTuptfooCdi8wnigIrIhM/M4zQ53m/YCMDCbOtDgEww== @@ -2979,6 +2732,23 @@ dependencies: "@types/node" "*" +"@types/d3-scale-chromatic@^3.0.0": + version "3.0.0" + resolved "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz" + integrity sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw== + +"@types/d3-scale@^4.0.3": + version "4.0.4" + resolved "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.4.tgz" + integrity sha512-eq1ZeTj0yr72L8MQk6N6heP603ubnywSDRfNpi5enouR112HzGLS6RIvExCzZTraFF4HdzNpJMwA/zGiMoHUUw== + dependencies: + "@types/d3-time" "*" + +"@types/d3-time@*": + version "3.0.0" + resolved "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz" + integrity sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg== + "@types/debug@^4.0.0": version "4.1.7" resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz" @@ -2996,7 +2766,7 @@ "@types/eslint@*": version "8.44.2" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.2.tgz#0d21c505f98a89b8dd4d37fa162b09da6089199a" + resolved "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz" integrity sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg== dependencies: "@types/estree" "*" @@ -3009,7 +2779,7 @@ "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": version "4.17.35" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz#c95dd4424f0d32e525d23812aa8ab8e4d3906c4f" + resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz" integrity sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg== dependencies: "@types/node" "*" @@ -3054,7 +2824,7 @@ "@types/http-errors@*": version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.1.tgz#20172f9578b225f6c7da63446f56d4ce108d5a65" + resolved "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz" integrity sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ== "@types/http-proxy@^1.17.8": @@ -3109,7 +2879,7 @@ "@types/mime@*": version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" + resolved "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz" integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== "@types/mime@^1": @@ -3124,7 +2894,7 @@ "@types/node@*": version "20.5.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.1.tgz#178d58ee7e4834152b0e8b4d30cbfab578b9bb30" + resolved "https://registry.npmjs.org/@types/node/-/node-20.5.1.tgz" integrity sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg== "@types/node@^14.11.8": @@ -3134,7 +2904,7 @@ "@types/node@^17.0.5": version "17.0.45" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" + resolved "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz" integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== "@types/parse-json@^4.0.0": @@ -3203,7 +2973,7 @@ "@types/history" "^4.7.11" "@types/react" "*" -"@types/react@*": +"@types/react@*", "@types/react@>= 16.8.0 < 19.0.0", "@types/react@>=16": version "17.0.56" resolved "https://registry.npmjs.org/@types/react/-/react-17.0.56.tgz" integrity sha512-Z13f9Qz7Hg8f2g2NsBjiJSVWmON2b3K8RIqFK8mMKCIgvD0CD0ZChTukz87H3lI28X3ukXoNFGzo3ZW1ICTtPA== @@ -3231,7 +3001,7 @@ "@types/send@*": version "0.17.1" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.1.tgz#ed4932b8a2a805f1fe362a70f4e62d0ac994e301" + resolved "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz" integrity sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q== dependencies: "@types/mime" "^1" @@ -3246,7 +3016,7 @@ "@types/serve-static@*", "@types/serve-static@^1.13.10": version "1.15.2" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.2.tgz#3e5419ecd1e40e7405d34093f10befb43f63381a" + resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz" integrity sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw== dependencies: "@types/http-errors" "*" @@ -3284,7 +3054,7 @@ dependencies: "@types/yargs-parser" "*" -"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": +"@webassemblyjs/ast@^1.11.5", "@webassemblyjs/ast@1.11.6": version "1.11.6" resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz" integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q== @@ -3385,7 +3155,7 @@ "@webassemblyjs/wasm-gen" "1.11.6" "@webassemblyjs/wasm-parser" "1.11.6" -"@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5": +"@webassemblyjs/wasm-parser@^1.11.5", "@webassemblyjs/wasm-parser@1.11.6": version "1.11.6" resolved "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz" integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ== @@ -3440,7 +3210,7 @@ acorn-walk@^8.0.0: resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== -acorn@^8.0.4, acorn@^8.7.1, acorn@^8.8.2: +acorn@^8, acorn@^8.0.4, acorn@^8.7.1, acorn@^8.8.2: version "8.10.0" resolved "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== @@ -3458,7 +3228,14 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv-formats@2.1.1, ajv-formats@^2.1.1: +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv-formats@2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz" integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== @@ -3477,17 +3254,7 @@ ajv-keywords@^5.0.0: dependencies: fast-deep-equal "^3.1.3" -ajv@8.1.0: - version "8.1.0" - resolved "https://registry.npmjs.org/ajv/-/ajv-8.1.0.tgz" - integrity sha512-B/Sk2Ix7A36fs/ZkuGLIR86EdjbgR6fsAcbx9lOP/QBSXujDNbVmIS/U4Itz5k8fPFDeVZl/zQ/gJW4Jrq6XjQ== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.9.1: version "6.12.6" resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -3497,7 +3264,7 @@ ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.8.0: +ajv@^8.0.0, ajv@^8.8.0, ajv@^8.8.2: version "8.11.0" resolved "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz" integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== @@ -3507,6 +3274,16 @@ ajv@^8.0.0, ajv@^8.8.0: require-from-string "^2.0.2" uri-js "^4.2.2" +ajv@8.1.0: + version "8.1.0" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.1.0.tgz" + integrity sha512-B/Sk2Ix7A36fs/ZkuGLIR86EdjbgR6fsAcbx9lOP/QBSXujDNbVmIS/U4Itz5k8fPFDeVZl/zQ/gJW4Jrq6XjQ== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + algoliasearch-helper@^3.10.0: version "3.11.1" resolved "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.11.1.tgz" @@ -3514,7 +3291,7 @@ algoliasearch-helper@^3.10.0: dependencies: "@algolia/events" "^4.0.1" -algoliasearch@^4.0.0, algoliasearch@^4.13.1: +algoliasearch@^4.0.0, algoliasearch@^4.13.1, "algoliasearch@>= 3.1 < 6", "algoliasearch@>= 4.9.1 < 6": version "4.14.2" resolved "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.14.2.tgz" integrity sha512-ngbEQonGEmf8dyEh5f+uOIihv4176dgbuOZspiuhmTTBRBuzWu3KCGHre6uHj5YyuC7pNvQGzB6ZNJyZi0z+Sg== @@ -3605,16 +3382,16 @@ argparse@^2.0.1: resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - array-flatten@^2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz" integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + array-union@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" @@ -3645,16 +3422,16 @@ assert@^2.0.0: object-is "^1.0.1" util "^0.12.0" -async@3.2.1: - version "3.2.1" - resolved "https://registry.npmjs.org/async/-/async-3.2.1.tgz" - integrity sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg== - async@^3.2.4: version "3.2.4" resolved "https://registry.npmjs.org/async/-/async-3.2.4.tgz" integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== +async@3.2.1: + version "3.2.1" + resolved "https://registry.npmjs.org/async/-/async-3.2.1.tgz" + integrity sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" @@ -3732,7 +3509,7 @@ babel-plugin-polyfill-corejs2@^0.3.3: babel-plugin-polyfill-corejs2@^0.4.5: version "0.4.5" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz#8097b4cb4af5b64a1d11332b6fb72ef5e64a054c" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz" integrity sha512-19hwUH5FKl49JEsvyTcoHakh6BE0wgXLLptIyKZ3PijHc/Ci521wygORCUCCred+E/twuqRyAkE02BAWPmsHOg== dependencies: "@babel/compat-data" "^7.22.6" @@ -3749,7 +3526,7 @@ babel-plugin-polyfill-corejs3@^0.6.0: babel-plugin-polyfill-corejs3@^0.8.3: version "0.8.3" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.3.tgz#b4f719d0ad9bb8e0c23e3e630c0c8ec6dd7a1c52" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.3.tgz" integrity sha512-z41XaniZL26WLrvjy7soabMXrfPWARN25PZoriDEiLMxAp50AUW3t35BGQUMg5xK3UrpVTtagIDklxYa+MhiNA== dependencies: "@babel/helper-define-polyfill-provider" "^0.4.2" @@ -3764,7 +3541,7 @@ babel-plugin-polyfill-regenerator@^0.4.1: babel-plugin-polyfill-regenerator@^0.5.2: version "0.5.2" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.2.tgz#80d0f3e1098c080c8b5a65f41e9427af692dc326" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.2.tgz" integrity sha512-tAlOptU0Xj34V1Y2PNTL4Y0FOJMDB6bZmoW39FeCQIhigGLkqu3Fj6uiXpxIf6Ij274ENdYx64y6Au+ZKlb1IA== dependencies: "@babel/helper-define-polyfill-provider" "^0.4.2" @@ -3814,7 +3591,12 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.0.0, bn.js@^5.1.1: +bn.js@^5.0.0: + version "5.2.1" + resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + +bn.js@^5.1.1: version "5.2.1" resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== @@ -3968,19 +3750,9 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4.18.1, browserslist@^4.21.3, browserslist@^4.21.4, browserslist@^4.21.5: - version "4.21.5" - resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz" - integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== - dependencies: - caniuse-lite "^1.0.30001449" - electron-to-chromium "^1.4.284" - node-releases "^2.0.8" - update-browserslist-db "^1.0.10" - -browserslist@^4.21.10, browserslist@^4.21.9: +browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4.18.1, browserslist@^4.21.10, browserslist@^4.21.4, browserslist@^4.21.5, browserslist@^4.21.9, "browserslist@>= 4.21.0": version "4.21.10" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.10.tgz#dbbac576628c13d3b2231332cb2ec5a46e015bb0" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz" integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ== dependencies: caniuse-lite "^1.0.30001517" @@ -4060,7 +3832,7 @@ camel-case@^4.1.2: pascal-case "^3.1.2" tslib "^2.0.3" -camelcase-css@2.0.1, camelcase-css@^2.0.1: +camelcase-css@^2.0.1, camelcase-css@2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz" integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== @@ -4085,22 +3857,17 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001464: - version "1.0.30001474" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001474.tgz" - integrity sha512-iaIZ8gVrWfemh5DG3T9/YqarVZoYf0r188IjaGwx68j4Pf0SGY6CQkmJUIE+NZHkkecQGohzXmBGEwWDr9aM3Q== - -caniuse-lite@^1.0.30001517: - version "1.0.30001522" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001522.tgz#44b87a406c901269adcdb834713e23582dd71856" - integrity sha512-TKiyTVZxJGhsTszLuzb+6vUZSjVOAhClszBr2Ta2k9IwtNBT/4dzmL6aywt0HCgEZlmwJzXJd8yNiob6HgwTRg== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001517: + version "1.0.30001538" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz" + integrity sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw== ccount@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz" integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== -chalk@^2.0.0, chalk@^2.4.2: +chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -4270,7 +4037,7 @@ clone-deep@^4.0.1: clone-response@^1.0.2: version "1.0.3" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" + resolved "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz" integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== dependencies: mimic-response "^1.0.0" @@ -4299,16 +4066,16 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - color-name@^1.1.4, color-name@~1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + colord@^2.9.1: version "2.9.2" resolved "https://registry.npmjs.org/colord/-/colord-2.9.2.tgz" @@ -4321,7 +4088,7 @@ colorette@^1.2.0: colorette@^2.0.10: version "2.0.20" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== combine-promises@^1.1.0: @@ -4346,12 +4113,12 @@ comma-separated-tokens@^2.0.0: resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz" integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== -commander@2.20.3, commander@^2.20.0: +commander@^2.20.0, commander@2.20.3: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^4.0.0, commander@~4.1.1: +commander@^4.0.0: version "4.1.1" resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== @@ -4371,6 +4138,16 @@ commander@^8.3.0: resolved "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== +commander@~4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + +commander@7: + version "7.2.0" + resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + commondir@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz" @@ -4529,7 +4306,7 @@ core-js-compat@^3.25.1: core-js-compat@^3.31.0: version "3.32.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.32.1.tgz#55f9a7d297c0761a8eb1d31b593e0f5b6ffae964" + resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.1.tgz" integrity sha512-GSvKDv4wE0bPnQtjklV101juQ85g6H3rm5PDP20mqlS5j0kXF3pP97YvAu5hl+uFHqMictp3b2VxOHljWMAtuA== dependencies: browserslist "^4.21.10" @@ -4549,6 +4326,20 @@ core-util-is@~1.0.0: resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +cose-base@^1.0.0: + version "1.0.3" + resolved "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz" + integrity sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg== + dependencies: + layout-base "^1.0.0" + +cose-base@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz" + integrity sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g== + dependencies: + layout-base "^2.0.0" + cosmiconfig@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz" @@ -4786,10 +4577,316 @@ csso@^4.2.0: csstype@^3.0.2: version "3.1.2" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" + resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz" integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== -debug@2.6.9, debug@^2.6.0: +cytoscape-cose-bilkent@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz" + integrity sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ== + dependencies: + cose-base "^1.0.0" + +cytoscape-fcose@^2.1.0: + version "2.2.0" + resolved "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz" + integrity sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ== + dependencies: + cose-base "^2.2.0" + +cytoscape@^3.2.0, cytoscape@^3.23.0: + version "3.26.0" + resolved "https://registry.npmjs.org/cytoscape/-/cytoscape-3.26.0.tgz" + integrity sha512-IV+crL+KBcrCnVVUCZW+zRRRFUZQcrtdOPXki+o4CFUWLdAEYvuZLcBSJC9EBK++suamERKzeY7roq2hdovV3w== + dependencies: + heap "^0.2.6" + lodash "^4.17.21" + +d3-array@^3.2.0, "d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3: + version "3.2.4" + resolved "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz" + integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== + dependencies: + internmap "1 - 2" + +"d3-array@1 - 2": + version "2.12.1" + resolved "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz" + integrity sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ== + dependencies: + internmap "^1.0.0" + +d3-axis@3: + version "3.0.0" + resolved "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz" + integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw== + +d3-brush@3: + version "3.0.0" + resolved "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz" + integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "3" + d3-transition "3" + +d3-chord@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz" + integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g== + dependencies: + d3-path "1 - 3" + +"d3-color@1 - 3", d3-color@3: + version "3.1.0" + resolved "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +d3-contour@4: + version "4.0.2" + resolved "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz" + integrity sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA== + dependencies: + d3-array "^3.2.0" + +d3-delaunay@6: + version "6.0.4" + resolved "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz" + integrity sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A== + dependencies: + delaunator "5" + +"d3-dispatch@1 - 3", d3-dispatch@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz" + integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== + +"d3-drag@2 - 3", d3-drag@3: + version "3.0.0" + resolved "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz" + integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== + dependencies: + d3-dispatch "1 - 3" + d3-selection "3" + +"d3-dsv@1 - 3", d3-dsv@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz" + integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q== + dependencies: + commander "7" + iconv-lite "0.6" + rw "1" + +"d3-ease@1 - 3", d3-ease@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + +d3-fetch@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz" + integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw== + dependencies: + d3-dsv "1 - 3" + +d3-force@3: + version "3.0.0" + resolved "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz" + integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg== + dependencies: + d3-dispatch "1 - 3" + d3-quadtree "1 - 3" + d3-timer "1 - 3" + +"d3-format@1 - 3", d3-format@3: + version "3.1.0" + resolved "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz" + integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== + +d3-geo@3: + version "3.1.0" + resolved "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz" + integrity sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA== + dependencies: + d3-array "2.5.0 - 3" + +d3-hierarchy@3: + version "3.1.2" + resolved "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz" + integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA== + +"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + +d3-path@^3.1.0, "d3-path@1 - 3", d3-path@3: + version "3.1.0" + resolved "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz" + integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== + +d3-path@1: + version "1.0.9" + resolved "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz" + integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg== + +d3-polygon@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz" + integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg== + +"d3-quadtree@1 - 3", d3-quadtree@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz" + integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw== + +d3-random@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz" + integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ== + +d3-sankey@^0.12.3: + version "0.12.3" + resolved "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz" + integrity sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ== + dependencies: + d3-array "1 - 2" + d3-shape "^1.2.0" + +d3-scale-chromatic@3: + version "3.0.0" + resolved "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz" + integrity sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g== + dependencies: + d3-color "1 - 3" + d3-interpolate "1 - 3" + +d3-scale@4: + version "4.0.2" + resolved "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz" + integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== + dependencies: + d3-array "2.10.0 - 3" + d3-format "1 - 3" + d3-interpolate "1.2.0 - 3" + d3-time "2.1.1 - 3" + d3-time-format "2 - 4" + +"d3-selection@2 - 3", d3-selection@3: + version "3.0.0" + resolved "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz" + integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== + +d3-shape@^1.2.0: + version "1.3.7" + resolved "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz" + integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw== + dependencies: + d3-path "1" + +d3-shape@3: + version "3.2.0" + resolved "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz" + integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== + dependencies: + d3-path "^3.1.0" + +"d3-time-format@2 - 4", d3-time-format@4: + version "4.1.0" + resolved "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz" + integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== + dependencies: + d3-time "1 - 3" + +"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3: + version "3.1.0" + resolved "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz" + integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== + dependencies: + d3-array "2 - 3" + +"d3-timer@1 - 3", d3-timer@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + +"d3-transition@2 - 3", d3-transition@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz" + integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== + dependencies: + d3-color "1 - 3" + d3-dispatch "1 - 3" + d3-ease "1 - 3" + d3-interpolate "1 - 3" + d3-timer "1 - 3" + +d3-zoom@3: + version "3.0.0" + resolved "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz" + integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "2 - 3" + d3-transition "2 - 3" + +d3@^7.4.0, d3@^7.8.2: + version "7.8.5" + resolved "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz" + integrity sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA== + dependencies: + d3-array "3" + d3-axis "3" + d3-brush "3" + d3-chord "3" + d3-color "3" + d3-contour "4" + d3-delaunay "6" + d3-dispatch "3" + d3-drag "3" + d3-dsv "3" + d3-ease "3" + d3-fetch "3" + d3-force "3" + d3-format "3" + d3-geo "3" + d3-hierarchy "3" + d3-interpolate "3" + d3-path "3" + d3-polygon "3" + d3-quadtree "3" + d3-random "3" + d3-scale "4" + d3-scale-chromatic "3" + d3-selection "3" + d3-shape "3" + d3-time "3" + d3-time-format "4" + d3-timer "3" + d3-transition "3" + d3-zoom "3" + +dagre-d3-es@7.0.10: + version "7.0.10" + resolved "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.10.tgz" + integrity sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A== + dependencies: + d3 "^7.8.2" + lodash-es "^4.17.21" + +dayjs@^1.11.7: + version "1.11.10" + resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz" + integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ== + +debug@^2.6.0: version "2.6.9" resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -4803,6 +4900,13 @@ debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.4: dependencies: ms "2.1.2" +debug@2.6.9: + version "2.6.9" + resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" @@ -4871,21 +4975,28 @@ del@^6.1.1: rimraf "^3.0.2" slash "^3.0.0" +delaunator@5: + version "5.0.0" + resolved "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz" + integrity sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw== + dependencies: + robust-predicates "^3.0.0" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -depd@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - depd@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== +depd@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + dequal@^2.0.0: version "2.0.3" resolved "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz" @@ -5097,7 +5208,21 @@ domhandler@^5.0.1, domhandler@^5.0.2, domhandler@^5.0.3: dependencies: domelementtype "^2.3.0" -domutils@^2.5.2, domutils@^2.8.0: +dompurify@^3.0.5: + version "3.0.5" + resolved "https://registry.npmjs.org/dompurify/-/dompurify-3.0.5.tgz" + integrity sha512-F9e6wPGtY+8KNMRAVfxeCOHU0/NPWMSENNq4pQctuXRqqdEPW7q3CrLbR5Nse044WwacyjHGOMlvNsBe1y6z9A== + +domutils@^2.5.2: + version "2.8.0" + resolved "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + +domutils@^2.8.0: version "2.8.0" resolved "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz" integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== @@ -5130,16 +5255,16 @@ dot-prop@^5.2.0: dependencies: is-obj "^2.0.0" -duplexer3@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e" - integrity sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA== - duplexer@^0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== +duplexer3@^0.1.4: + version "0.1.5" + resolved "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz" + integrity sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA== + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" @@ -5150,15 +5275,15 @@ ee-first@1.1.1: resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.4.284: - version "1.4.284" - resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz" - integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== - electron-to-chromium@^1.4.477: - version "1.4.498" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.498.tgz#cef35341123f62a35ba7084e439c911d25e0d81b" - integrity sha512-4LODxAzKGVy7CJyhhN5mebwe7U2L29P+0G+HUriHnabm0d7LSff8Yn7t+Wq+2/9ze2Fu1dhX7mww090xfv7qXQ== + version "1.4.526" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.526.tgz" + integrity sha512-tjjTMjmZAx1g6COrintLTa2/jcafYKxKoiEkdQOrVdbLaHh2wCt2nsAF8ZHweezkrP+dl/VG9T5nabcYoo0U5Q== + +elkjs@^0.8.2: + version "0.8.2" + resolved "https://registry.npmjs.org/elkjs/-/elkjs-0.8.2.tgz" + integrity sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ== elliptic@^6.5.3: version "6.5.4" @@ -5494,7 +5619,7 @@ feed@^4.2.2: dependencies: xml-js "^1.6.11" -file-loader@^6.2.0: +file-loader@*, file-loader@^6.2.0: version "6.2.0" resolved "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz" integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== @@ -5661,7 +5786,17 @@ fs-extra@^10.1.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^9.0.0, fs-extra@^9.0.1: +fs-extra@^9.0.0: + version "9.1.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^9.0.1: version "9.1.0" resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz" integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== @@ -5673,7 +5808,7 @@ fs-extra@^9.0.0, fs-extra@^9.0.1: fs-monkey@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.4.tgz#ee8c1b53d3fe8bb7e5d2c5c5dfc0168afdd2f747" + resolved "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.4.tgz" integrity sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ== fs.realpath@^1.0.0: @@ -5747,7 +5882,14 @@ glob-parent@^5.1.2, glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" -glob-parent@^6.0.1, glob-parent@^6.0.2: +glob-parent@^6.0.1: + version "6.0.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-parent@^6.0.2: version "6.0.2" resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== @@ -5759,18 +5901,6 @@ glob-to-regexp@^0.4.1: resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@7.1.6: - version "7.1.6" - resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@^7.0.0, glob@^7.1.3, glob@^7.1.6: version "7.2.3" resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" @@ -5783,6 +5913,18 @@ glob@^7.0.0, glob@^7.1.3, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" +glob@7.1.6: + version "7.1.6" + resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + global-dirs@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz" @@ -6005,22 +6147,6 @@ hast-util-parse-selector@^3.0.0: dependencies: "@types/hast" "^2.0.0" -hast-util-raw@6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-6.0.1.tgz" - integrity sha512-ZMuiYA+UF7BXBtsTBNcLBF5HzXzkyE6MLzJnL605LKE8GJylNjGc4jjxazAHUtcwT5/CEt6afRKViYB4X66dig== - dependencies: - "@types/hast" "^2.0.0" - hast-util-from-parse5 "^6.0.0" - hast-util-to-parse5 "^6.0.0" - html-void-elements "^1.0.0" - parse5 "^6.0.0" - unist-util-position "^3.0.0" - vfile "^4.0.0" - web-namespaces "^1.0.0" - xtend "^4.0.0" - zwitch "^1.0.0" - hast-util-raw@^7.2.0: version "7.2.3" resolved "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz" @@ -6038,6 +6164,22 @@ hast-util-raw@^7.2.0: web-namespaces "^2.0.0" zwitch "^2.0.0" +hast-util-raw@6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-6.0.1.tgz" + integrity sha512-ZMuiYA+UF7BXBtsTBNcLBF5HzXzkyE6MLzJnL605LKE8GJylNjGc4jjxazAHUtcwT5/CEt6afRKViYB4X66dig== + dependencies: + "@types/hast" "^2.0.0" + hast-util-from-parse5 "^6.0.0" + hast-util-to-parse5 "^6.0.0" + html-void-elements "^1.0.0" + parse5 "^6.0.0" + unist-util-position "^3.0.0" + vfile "^4.0.0" + web-namespaces "^1.0.0" + xtend "^4.0.0" + zwitch "^1.0.0" + hast-util-to-parse5@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz" @@ -6093,6 +6235,11 @@ he@^1.2.0: resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +heap@^0.2.6: + version "0.2.7" + resolved "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz" + integrity sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg== + hexoid@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz" @@ -6210,6 +6357,16 @@ http-deceiver@^1.2.7: resolved "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz" integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz" + integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + http-errors@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" @@ -6221,19 +6378,9 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz" - integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - http-parser-js@>=0.5.1: version "0.5.8" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" + resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz" integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== http-proxy-middleware@^2.0.3: @@ -6283,6 +6430,13 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" +iconv-lite@0.6: + version "0.6.3" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + iconv-lite@0.6.3: version "0.6.3" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" @@ -6353,7 +6507,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.0.4: +inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.0.4, inherits@2, inherits@2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -6363,21 +6517,31 @@ inherits@2.0.3: resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== -ini@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz" - integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== - ini@^1.3.5, ini@~1.3.0: version "1.3.8" resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +ini@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + inline-style-parser@0.1.1: version "0.1.1" resolved "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz" integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== +internmap@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz" + integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw== + +"internmap@1 - 2": + version "2.0.3" + resolved "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz" + integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== + interpret@^1.0.0: version "1.4.0" resolved "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz" @@ -6390,17 +6554,17 @@ invariant@^2.2.4: dependencies: loose-envify "^1.0.0" -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - ipaddr.js@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz" integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== -is-alphabetical@1.0.4, is-alphabetical@^1.0.0: +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-alphabetical@^1.0.0, is-alphabetical@1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz" integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== @@ -6452,7 +6616,7 @@ is-ci@^2.0.0: is-core-module@^2.9.0: version "2.13.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz" integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== dependencies: has "^1.0.3" @@ -6618,16 +6782,16 @@ is-yarn-global@^0.3.0: resolved "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz" integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== - isarray@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" @@ -6695,7 +6859,7 @@ js-levenshtein@^1.1.6: resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@3.14.1, js-yaml@^3.13.1: +js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -6710,6 +6874,14 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +js-yaml@3.14.1: + version "3.14.1" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" @@ -6758,7 +6930,7 @@ json-schema-compare@^0.2.2: dependencies: lodash "^4.17.4" -json-schema-merge-allof@0.8.1, json-schema-merge-allof@^0.8.1: +json-schema-merge-allof@^0.8.1, json-schema-merge-allof@0.8.1: version "0.8.1" resolved "https://registry.npmjs.org/json-schema-merge-allof/-/json-schema-merge-allof-0.8.1.tgz" integrity sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w== @@ -6777,7 +6949,7 @@ json-schema-traverse@^1.0.0: resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json5@^2.1.2, json5@^2.2.1, json5@^2.2.2: +json5@^2.1.2, json5@^2.2.3: version "2.2.3" resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== @@ -6798,6 +6970,11 @@ keyv@^3.0.0: dependencies: json-buffer "3.0.0" +khroma@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/khroma/-/khroma-2.0.0.tgz" + integrity sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g== + kind-of@^6.0.0, kind-of@^6.0.2: version "6.0.3" resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" @@ -6825,6 +7002,16 @@ latest-version@^5.1.0: dependencies: package-json "^6.3.0" +layout-base@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz" + integrity sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg== + +layout-base@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz" + integrity sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg== + leven@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" @@ -6886,6 +7073,11 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash-es@^4.17.21: + version "4.17.21" + resolved "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz" @@ -6916,12 +7108,12 @@ lodash.memoize@^4.1.2: resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== -lodash.uniq@4.5.0, lodash.uniq@^4.5.0: +lodash.uniq@^4.5.0, lodash.uniq@4.5.0: version "4.5.0" resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz" integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== -lodash@4.17.21, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4: +lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@4.17.21: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -6952,7 +7144,7 @@ lowercase-keys@^2.0.0: lru-cache@^5.1.1: version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== dependencies: yallist "^3.0.2" @@ -7008,7 +7200,7 @@ mdast-util-definitions@^5.0.0: "@types/unist" "^2.0.0" unist-util-visit "^4.0.0" -mdast-util-from-markdown@^1.0.0: +mdast-util-from-markdown@^1.0.0, mdast-util-from-markdown@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.0.tgz" integrity sha512-HN3W1gRIuN/ZW295c7zi7g9lVBllMgZE40RxCX37wrTPWXCWtpvOZdfnuK+1WNpvZje6XuJeI3Wnb4TJEUem+g== @@ -7026,20 +7218,6 @@ mdast-util-from-markdown@^1.0.0: unist-util-stringify-position "^3.0.0" uvu "^0.5.0" -mdast-util-to-hast@10.0.1: - version "10.0.1" - resolved "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz" - integrity sha512-BW3LM9SEMnjf4HXXVApZMt8gLQWVNXc3jryK0nJu/rOXPOnlkUjmdkDlmxMirpbU9ILncGFIwLH/ubnWBbcdgA== - dependencies: - "@types/mdast" "^3.0.0" - "@types/unist" "^2.0.0" - mdast-util-definitions "^4.0.0" - mdurl "^1.0.0" - unist-builder "^2.0.0" - unist-util-generated "^1.0.0" - unist-util-position "^3.0.0" - unist-util-visit "^2.0.0" - mdast-util-to-hast@^12.1.0: version "12.3.0" resolved "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz" @@ -7054,6 +7232,20 @@ mdast-util-to-hast@^12.1.0: unist-util-position "^4.0.0" unist-util-visit "^4.0.0" +mdast-util-to-hast@10.0.1: + version "10.0.1" + resolved "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz" + integrity sha512-BW3LM9SEMnjf4HXXVApZMt8gLQWVNXc3jryK0nJu/rOXPOnlkUjmdkDlmxMirpbU9ILncGFIwLH/ubnWBbcdgA== + dependencies: + "@types/mdast" "^3.0.0" + "@types/unist" "^2.0.0" + mdast-util-definitions "^4.0.0" + mdurl "^1.0.0" + unist-builder "^2.0.0" + unist-util-generated "^1.0.0" + unist-util-position "^3.0.0" + unist-util-visit "^2.0.0" + mdast-util-to-string@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz" @@ -7093,7 +7285,7 @@ medium-zoom@^1.0.6: memfs@^3.1.2, memfs@^3.4.1: version "3.6.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" + resolved "https://registry.npmjs.org/memfs/-/memfs-3.6.0.tgz" integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== dependencies: fs-monkey "^1.0.4" @@ -7113,6 +7305,32 @@ merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +mermaid@>=8.11.0: + version "10.4.0" + resolved "https://registry.npmjs.org/mermaid/-/mermaid-10.4.0.tgz" + integrity sha512-4QCQLp79lvz7UZxow5HUX7uWTPJOaQBVExduo91tliXC7v78i6kssZOPHxLL+Xs30KU72cpPn3g3imw/xm/gaw== + dependencies: + "@braintree/sanitize-url" "^6.0.1" + "@types/d3-scale" "^4.0.3" + "@types/d3-scale-chromatic" "^3.0.0" + cytoscape "^3.23.0" + cytoscape-cose-bilkent "^4.1.0" + cytoscape-fcose "^2.1.0" + d3 "^7.4.0" + d3-sankey "^0.12.3" + dagre-d3-es "7.0.10" + dayjs "^1.11.7" + dompurify "^3.0.5" + elkjs "^0.8.2" + khroma "^2.0.0" + lodash-es "^4.17.21" + mdast-util-from-markdown "^1.3.0" + non-layered-tidy-tree-layout "^2.0.2" + stylis "^4.1.3" + ts-dedent "^2.2.0" + uuid "^9.0.0" + web-worker "^1.2.0" + methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" @@ -7329,12 +7547,7 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.51.0: - version "1.51.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz" - integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== - -mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": +"mime-db@>= 1.43.0 < 2", mime-db@1.52.0: version "1.52.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== @@ -7344,6 +7557,11 @@ mime-db@~1.33.0: resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz" integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== +mime-db@1.51.0: + version "1.51.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz" + integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== + mime-format@2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/mime-format/-/mime-format-2.0.1.tgz" @@ -7351,6 +7569,13 @@ mime-format@2.0.1: dependencies: charset "^1.0.0" +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + mime-types@2.1.18: version "2.1.18" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz" @@ -7365,13 +7590,6 @@ mime-types@2.1.34: dependencies: mime-db "1.51.0" -mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - mime@1.6.0: version "1.6.0" resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" @@ -7409,7 +7627,7 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz" integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== -minimatch@3.1.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1: +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -7435,7 +7653,7 @@ mri@^1.1.0: mrmime@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27" + resolved "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz" integrity sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw== ms@2.0.0: @@ -7517,7 +7735,7 @@ node-fetch-h2@^2.3.0: dependencies: http2-client "^1.2.5" -node-fetch@2.6.7, node-fetch@^2.6.1: +node-fetch@^2.6.1, node-fetch@2.6.7: version "2.6.7" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== @@ -7569,13 +7787,13 @@ node-readfiles@^0.2.0: node-releases@^2.0.13: version "2.0.13" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz" integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== -node-releases@^2.0.8: - version "2.0.10" - resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz" - integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w== +non-layered-tidy-tree-layout@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz" + integrity sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" @@ -7611,7 +7829,7 @@ nprogress@^0.2.0: nth-check@^2.0.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + resolved "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz" integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== dependencies: boolbase "^1.0.0" @@ -7923,7 +8141,7 @@ pascal-case@^3.1.2: no-case "^3.0.4" tslib "^2.0.3" -path-browserify@1.0.1, path-browserify@^1.0.1: +path-browserify@^1.0.1, path-browserify@1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz" integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== @@ -7978,6 +8196,13 @@ path-root@^0.1.1: dependencies: path-root-regex "^0.1.0" +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" @@ -7988,13 +8213,6 @@ path-to-regexp@2.2.1: resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz" integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ== -path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== - dependencies: - isarray "0.0.1" - path-type@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" @@ -8370,7 +8588,7 @@ postcss-zindex@^5.1.0: resolved "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-5.1.0.tgz" integrity sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A== -postcss@^8.0.9, postcss@^8.3.11, postcss@^8.4.14, postcss@^8.4.17, postcss@^8.4.19, postcss@^8.4.7: +"postcss@^7.0.0 || ^8.0.1", postcss@^8.0.0, postcss@^8.0.9, postcss@^8.1.0, postcss@^8.2.14, postcss@^8.2.15, postcss@^8.2.2, postcss@^8.3.11, postcss@^8.4.14, postcss@^8.4.17, postcss@^8.4.19, postcss@^8.4.21, postcss@^8.4.4, postcss@^8.4.7, postcss@>=8.0.9: version "8.4.21" resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz" integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== @@ -8488,11 +8706,6 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" - integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== - punycode@^1.3.2: version "1.4.1" resolved "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" @@ -8503,6 +8716,11 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" + integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== + pupa@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz" @@ -8515,7 +8733,7 @@ pure-color@^1.2.0: resolved "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz" integrity sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA== -qs@6.10.3, qs@^6.10.3: +qs@^6.10.3, qs@6.10.3: version "6.10.3" resolved "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz" integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== @@ -8571,16 +8789,16 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" -range-parser@1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz" - integrity sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A== - range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== +range-parser@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz" + integrity sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A== + raw-body@2.5.1: version "2.5.1" resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz" @@ -8599,7 +8817,7 @@ raw-loader@^4.0.2: loader-utils "^2.0.0" schema-utils "^3.0.0" -rc@1.2.8, rc@^1.2.8: +rc@^1.2.8, rc@1.2.8: version "1.2.8" resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -8657,7 +8875,7 @@ react-dev-utils@^12.0.1: strip-ansi "^6.0.1" text-table "^0.2.0" -react-dom@17.0.2: +react-dom@*, "react-dom@^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18", "react-dom@^16 || ^17 || ^18", "react-dom@^16.6.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.8.4 || ^17.0.0", "react-dom@^17.0.0 || ^16.3.0 || ^15.5.4", "react-dom@>= 16.8.0 < 19.0.0", react-dom@17.0.2: version "17.0.2" resolved "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz" integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== @@ -8733,6 +8951,14 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1: dependencies: "@babel/runtime" "^7.10.3" +react-loadable@*, "react-loadable@npm:@docusaurus/react-loadable@5.5.2": + version "5.5.2" + resolved "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz" + integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ== + dependencies: + "@types/react" "*" + prop-types "^15.6.2" + react-magic-dropzone@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/react-magic-dropzone/-/react-magic-dropzone-1.0.1.tgz" @@ -8769,7 +8995,7 @@ react-modal@^3.15.1: react-lifecycles-compat "^3.0.0" warning "^4.0.3" -react-redux@^7.2.0: +react-redux@^7.2.0, "react-redux@^7.2.1 || ^8.0.2": version "7.2.9" resolved "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz" integrity sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ== @@ -8801,7 +9027,7 @@ react-router-dom@^5.3.3: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-router@5.3.4, react-router@^5.3.3: +react-router@^5.3.3, react-router@>=5, react-router@5.3.4: version "5.3.4" resolved "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz" integrity sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA== @@ -8818,14 +9044,14 @@ react-router@5.3.4, react-router@^5.3.3: react-textarea-autosize@^8.3.2: version "8.5.2" - resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.5.2.tgz#6421df2b5b50b9ca8c5e96fd31be688ea7fa2f9d" + resolved "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.2.tgz" integrity sha512-uOkyjkEl0ByEK21eCJMHDGBAAd/BoFQBawYK5XItjAmCTeSbjxghd8qnt7nzsLYzidjnoObu6M26xts0YGKsGg== dependencies: "@babel/runtime" "^7.20.13" use-composed-ref "^1.3.0" use-latest "^1.2.1" -react@17.0.2: +react@*, "react@^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18", "react@^15.0.2 || ^16.0.0 || ^17.0.0", "react@^15.3.0 || 16 || 17 || 18", "react@^16 || ^17 || ^18", "react@^16.13.1 || ^17.0.0", "react@^16.6.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.3 || ^17 || ^18", "react@^16.8.4 || ^17.0.0", "react@^16.8.4 || ^17.0.0 || ^18.0.0", "react@^16.9.0 || ^17.0.0 || ^18", "react@^17.0.0 || ^16.3.0 || ^15.5.4", "react@>= 16", "react@>= 16.8.0", "react@>= 16.8.0 < 19.0.0", react@>=0.14.9, react@>=15, react@>=16, react@17.0.2: version "17.0.2" resolved "https://registry.npmjs.org/react/-/react-17.0.2.tgz" integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== @@ -8909,7 +9135,7 @@ redux-thunk@^2.4.2: resolved "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz" integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q== -redux@^4.0.0, redux@^4.2.0: +"redux@^3.1.0 || ^4.0.0", redux@^4, redux@^4.0.0, redux@^4.2.0: version "4.2.1" resolved "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz" integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== @@ -8940,7 +9166,7 @@ regenerator-runtime@^0.13.10: regenerator-runtime@^0.14.0: version "0.14.0" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz" integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== regenerator-transform@^0.15.0: @@ -8952,7 +9178,7 @@ regenerator-transform@^0.15.0: regenerator-transform@^0.15.2: version "0.15.2" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4" + resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz" integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg== dependencies: "@babel/runtime" "^7.8.4" @@ -8971,7 +9197,7 @@ regexpu-core@^5.1.0: regexpu-core@^5.3.1: version "5.3.2" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" + resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz" integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ== dependencies: "@babel/regjsgen" "^0.8.0" @@ -8983,7 +9209,7 @@ regexpu-core@^5.3.1: registry-auth-token@^4.0.0: version "4.2.2" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.2.tgz#f02d49c3668884612ca031419491a13539e21fac" + resolved "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.2.tgz" integrity sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg== dependencies: rc "1.2.8" @@ -9049,6 +9275,15 @@ remark-mdx@1.6.22: remark-parse "8.0.3" unified "9.2.0" +remark-parse@^10.0.0: + version "10.0.1" + resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz" + integrity sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw== + dependencies: + "@types/mdast" "^3.0.0" + mdast-util-from-markdown "^1.0.0" + unified "^10.0.0" + remark-parse@8.0.3: version "8.0.3" resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.3.tgz" @@ -9071,15 +9306,6 @@ remark-parse@8.0.3: vfile-location "^3.0.0" xtend "^4.0.1" -remark-parse@^10.0.0: - version "10.0.1" - resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz" - integrity sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw== - dependencies: - "@types/mdast" "^3.0.0" - mdast-util-from-markdown "^1.0.0" - unified "^10.0.0" - remark-rehype@^10.0.0: version "10.1.0" resolved "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz" @@ -9201,6 +9427,11 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +robust-predicates@^3.0.0: + version "3.0.2" + resolved "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz" + integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg== + rtl-detect@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/rtl-detect/-/rtl-detect-1.0.4.tgz" @@ -9223,6 +9454,11 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +rw@1: + version "1.3.3" + resolved "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz" + integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== + rxjs@^7.5.4: version "7.5.5" resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz" @@ -9237,17 +9473,22 @@ sade@^1.7.3: dependencies: mri "^1.1.0" -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@>=5.1.0, safe-buffer@~5.2.0, safe-buffer@5.2.1: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.1.0: +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safer-buffer@^2.1.0, "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -9265,15 +9506,6 @@ scheduler@^0.20.2: loose-envify "^1.1.0" object-assign "^4.1.1" -schema-utils@2.7.0: - version "2.7.0" - resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz" - integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== - dependencies: - "@types/json-schema" "^7.0.4" - ajv "^6.12.2" - ajv-keywords "^3.4.1" - schema-utils@^2.6.5: version "2.7.1" resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz" @@ -9302,6 +9534,15 @@ schema-utils@^4.0.0: ajv-formats "^2.1.1" ajv-keywords "^5.0.0" +schema-utils@2.7.0: + version "2.7.0" + resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz" + integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== + dependencies: + "@types/json-schema" "^7.0.4" + ajv "^6.12.2" + ajv-keywords "^3.4.1" + section-matter@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz" @@ -9329,13 +9570,6 @@ semver-diff@^3.1.1: dependencies: semver "^6.3.0" -semver@7.3.5: - version "7.3.5" - resolved "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== - dependencies: - lru-cache "^6.0.0" - semver@^5.4.1: version "5.7.2" resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz" @@ -9346,13 +9580,48 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0, semve resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: +semver@^7.3.2: version "7.5.4" resolved "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" +semver@^7.3.4: + version "7.5.4" + resolved "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + +semver@^7.3.5: + version "7.5.4" + resolved "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + +semver@^7.3.7: + version "7.5.4" + resolved "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + +semver@^7.3.8: + version "7.5.4" + resolved "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + +semver@7.3.5: + version "7.3.5" + resolved "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + send@0.18.0: version "0.18.0" resolved "https://registry.npmjs.org/send/-/send-0.18.0.tgz" @@ -9554,7 +9823,7 @@ sisteransi@^1.0.5: resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== -sitemap@7.1.1, sitemap@^7.1.1: +sitemap@^7.1.1, sitemap@7.1.1: version "7.1.1" resolved "https://registry.npmjs.org/sitemap/-/sitemap-7.1.1.tgz" integrity sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg== @@ -9664,16 +9933,16 @@ state-toggle@^1.0.0: resolved "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz" integrity sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ== -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - "statuses@>= 1.4.0 < 2": version "1.5.0" resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + std-env@^3.0.1: version "3.1.1" resolved "https://registry.npmjs.org/std-env/-/std-env-3.1.1.tgz" @@ -9697,6 +9966,20 @@ stream-http@^3.2.0: readable-stream "^3.6.0" xtend "^4.0.2" +string_decoder@^1.1.1, string_decoder@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" @@ -9715,20 +9998,6 @@ string-width@^5.0.1: emoji-regex "^9.2.2" strip-ansi "^7.0.1" -string_decoder@^1.1.1, string_decoder@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - stringify-object@^3.3.0: version "3.3.0" resolved "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz" @@ -9772,7 +10041,7 @@ strip-json-comments@~2.0.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== -style-to-object@0.3.0, style-to-object@^0.3.0: +style-to-object@^0.3.0, style-to-object@0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz" integrity sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA== @@ -9794,6 +10063,11 @@ stylehacks@^5.1.1: browserslist "^4.21.4" postcss-selector-parser "^6.0.4" +stylis@^4.1.3: + version "4.3.0" + resolved "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz" + integrity sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ== + sucrase@^3.21.0, sucrase@^3.29.0: version "3.31.0" resolved "https://registry.npmjs.org/sucrase/-/sucrase-3.31.0.tgz" @@ -9851,7 +10125,7 @@ supports-preserve-symlinks-flag@^1.0.0: svg-parser@^2.0.4: version "2.0.4" - resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" + resolved "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz" integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== svgo@^2.7.0, svgo@^2.8.0: @@ -10053,6 +10327,11 @@ trough@^2.0.0: resolved "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz" integrity sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g== +ts-dedent@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz" + integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ== + ts-interface-checker@^0.1.9: version "0.1.13" resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz" @@ -10093,6 +10372,11 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +"typescript@>= 2.7": + version "5.2.2" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz" + integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== + ua-parser-js@^0.7.30: version "0.7.35" resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.35.tgz" @@ -10126,7 +10410,7 @@ unicode-match-property-value-ecmascript@^2.0.0: unicode-match-property-value-ecmascript@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" + resolved "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz" integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== unicode-property-aliases-ecmascript@^2.0.0: @@ -10134,18 +10418,6 @@ unicode-property-aliases-ecmascript@^2.0.0: resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz" integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ== -unified@9.2.0: - version "9.2.0" - resolved "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz" - integrity sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg== - dependencies: - bail "^1.0.0" - extend "^3.0.0" - is-buffer "^2.0.0" - is-plain-obj "^2.0.0" - trough "^1.0.0" - vfile "^4.0.0" - unified@^10.0.0: version "10.1.2" resolved "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz" @@ -10171,6 +10443,18 @@ unified@^9.2.2: trough "^1.0.0" vfile "^4.0.0" +unified@9.2.0: + version "9.2.0" + resolved "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz" + integrity sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg== + dependencies: + bail "^1.0.0" + extend "^3.0.0" + is-buffer "^2.0.0" + is-plain-obj "^2.0.0" + trough "^1.0.0" + vfile "^4.0.0" + unique-string@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz" @@ -10178,7 +10462,7 @@ unique-string@^2.0.0: dependencies: crypto-random-string "^2.0.0" -unist-builder@2.0.3, unist-builder@^2.0.0: +unist-builder@^2.0.0, unist-builder@2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz" integrity sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw== @@ -10261,7 +10545,7 @@ unist-util-visit-parents@^5.1.1: "@types/unist" "^2.0.0" unist-util-is "^5.0.0" -unist-util-visit@2.0.3, unist-util-visit@^2.0.0, unist-util-visit@^2.0.3: +unist-util-visit@^2.0.0, unist-util-visit@^2.0.3, unist-util-visit@2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz" integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q== @@ -10284,23 +10568,15 @@ universalify@^2.0.0: resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== -unpipe@1.0.0, unpipe@~1.0.0: +unpipe@~1.0.0, unpipe@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -update-browserslist-db@^1.0.10: - version "1.0.10" - resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz" - integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - update-browserslist-db@^1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" - integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== + version "1.0.12" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.12.tgz" + integrity sha512-tE1smlR58jxbFMtrMpFNRmsrOXlpNXss965T1CrpwuZUzUAg/TBQc94SpyhDLSzrqrJS9xTRBthnZAGcE1oaxg== dependencies: escalade "^3.1.1" picocolors "^1.0.0" @@ -10363,7 +10639,7 @@ url@^0.11.0: use-composed-ref@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda" + resolved "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz" integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ== use-editable@^2.3.3: @@ -10378,7 +10654,7 @@ use-isomorphic-layout-effect@^1.1.1: use-latest@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.2.1.tgz#d13dfb4b08c28e3e33991546a2cee53e14038cf2" + resolved "https://registry.npmjs.org/use-latest/-/use-latest-1.2.1.tgz" integrity sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw== dependencies: use-isomorphic-layout-effect "^1.1.1" @@ -10421,11 +10697,16 @@ utils-merge@1.0.1: resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -uuid@8.3.2, uuid@^8.3.2: +uuid@^8.3.2, uuid@8.3.2: version "8.3.2" resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuid@^9.0.0: + version "9.0.1" + resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + uvu@^0.5.0: version "0.5.6" resolved "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz" @@ -10538,7 +10819,7 @@ vm-browserify@^1.1.2: resolved "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== -wait-on@6.0.1, wait-on@^6.0.1: +wait-on@^6.0.1, wait-on@6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/wait-on/-/wait-on-6.0.1.tgz" integrity sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw== @@ -10581,6 +10862,11 @@ web-namespaces@^2.0.0: resolved "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz" integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ== +web-worker@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz" + integrity sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA== + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" @@ -10660,7 +10946,7 @@ webpack-sources@^3.2.2, webpack-sources@^3.2.3: resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@^5.61.0, webpack@^5.73.0: +"webpack@^4.0.0 || ^5.0.0", "webpack@^4.37.0 || ^5.0.0", webpack@^5.0.0, webpack@^5.1.0, webpack@^5.20.0, webpack@^5.61.0, webpack@^5.73.0, "webpack@>= 4", webpack@>=2, "webpack@>=4.41.1 || 5.x", webpack@>=5, "webpack@3 || 4 || 5": version "5.88.2" resolved "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz" integrity sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ== @@ -10700,7 +10986,7 @@ webpackbar@^5.0.2: pretty-time "^1.1.0" std-env "^3.0.1" -websocket-driver@>=0.5.1, websocket-driver@^0.7.4: +websocket-driver@^0.7.4, websocket-driver@>=0.5.1: version "0.7.4" resolved "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz" integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== @@ -10815,7 +11101,7 @@ write-file-atomic@^3.0.0: ws@^7.3.1: version "7.5.9" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== ws@^8.4.2: @@ -10864,7 +11150,7 @@ y18n@^5.0.5: yallist@^3.0.2: version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== yallist@^4.0.0: @@ -10877,7 +11163,7 @@ yaml-ast-parser@0.0.43: resolved "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz" integrity sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A== -yaml@1.10.2, yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: +yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2, yaml@1.10.2: version "1.10.2" resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== diff --git a/feature/check.go b/feature/check.go new file mode 100644 index 0000000000..eb66ce1193 --- /dev/null +++ b/feature/check.go @@ -0,0 +1,45 @@ +package feature + +import ( + "context" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/command" + "github.com/zitadel/zitadel/internal/command/preparation" + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/repository/feature" +) + +type Checker interface { + CheckInstanceBooleanFeature(ctx context.Context, f domain.Feature) (feature.Boolean, error) +} + +func NewCheck(eventstore *eventstore.Eventstore) *Check { + return &Check{eventstore: eventstore} +} + +type Check struct { + eventstore *eventstore.Eventstore +} + +func (c *Check) CheckInstanceBooleanFeature(ctx context.Context, f domain.Feature) (feature.Boolean, error) { + return getInstanceFeature[feature.Boolean](ctx, f, c.eventstore.Filter) +} + +func getInstanceFeature[T feature.SetEventType](ctx context.Context, f domain.Feature, filter preparation.FilterToQueryReducer) (T, error) { + instanceID := authz.GetInstance(ctx).InstanceID() + writeModel, err := command.NewInstanceFeatureWriteModel[T](instanceID, f) + if err != nil { + return writeModel.Value, err + } + events, err := filter(ctx, writeModel.Query()) + if err != nil { + return writeModel.Value, err + } + writeModel.AppendEvents(events...) + if err = writeModel.Reduce(); err != nil { + return writeModel.Value, err + } + return writeModel.Value, nil +} diff --git a/go.mod b/go.mod index 459c9696ae..f4e513fbb1 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/boombuler/barcode v1.0.1 github.com/cockroachdb/cockroach-go/v2 v2.3.5 github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be + github.com/crewjam/saml v0.4.13 github.com/descope/virtualwebauthn v1.0.2 github.com/dop251/goja v0.0.0-20230828202809-3dbe69dd2b8e github.com/dop251/goja_nodejs v0.0.0-20230821135201-94e508132562 @@ -51,7 +52,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/pquerna/otp v1.4.0 github.com/rakyll/statik v0.1.7 - github.com/rs/cors v1.9.0 + github.com/rs/cors v1.10.0 github.com/sony/sonyflake v1.2.0 github.com/spf13/cobra v1.7.0 github.com/spf13/viper v1.16.0 @@ -59,9 +60,9 @@ require ( github.com/superseriousbusiness/exifremove v0.0.0-20210330092427-6acd27eac203 github.com/ttacon/libphonenumber v1.2.1 github.com/zitadel/logging v0.4.0 - github.com/zitadel/oidc/v2 v2.10.0 - github.com/zitadel/passwap v0.3.0 - github.com/zitadel/saml v0.1.0 + github.com/zitadel/oidc/v2 v2.11.0 + github.com/zitadel/passwap v0.4.0 + github.com/zitadel/saml v0.1.2 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.43.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.43.0 go.opentelemetry.io/otel v1.17.0 @@ -72,11 +73,11 @@ require ( go.opentelemetry.io/otel/sdk v1.17.0 go.opentelemetry.io/otel/sdk/metric v0.40.0 go.opentelemetry.io/otel/trace v1.17.0 - golang.org/x/crypto v0.12.0 - golang.org/x/net v0.14.0 - golang.org/x/oauth2 v0.11.0 + golang.org/x/crypto v0.13.0 + golang.org/x/net v0.15.0 + golang.org/x/oauth2 v0.12.0 golang.org/x/sync v0.3.0 - golang.org/x/text v0.12.0 + golang.org/x/text v0.13.0 google.golang.org/api v0.138.0 google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d google.golang.org/grpc v1.57.0 @@ -87,10 +88,13 @@ require ( require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.43.1 // indirect + github.com/crewjam/httperr v0.2.0 // indirect + github.com/dmarkham/enumer v1.5.8 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-sql-driver/mysql v1.6.0 // indirect github.com/go-webauthn/x v0.1.4 // indirect + github.com/golang-jwt/jwt/v4 v4.4.3 // indirect github.com/golang-jwt/jwt/v5 v5.0.0 // indirect github.com/golang/glog v1.1.1 // indirect github.com/google/go-tpm v0.9.0 // indirect @@ -98,11 +102,17 @@ require ( github.com/google/s2a-go v0.1.5 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect + github.com/muhlemmer/httpforwarded v0.1.0 // indirect + github.com/pascaldekloe/name v1.0.0 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/smartystreets/assertions v1.0.0 // indirect + github.com/zenazn/goji v1.0.1 // indirect golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect ) @@ -115,7 +125,7 @@ require ( cloud.google.com/go/trace v1.10.1 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/amdonov/xmlsig v0.1.0 // indirect - github.com/beevik/etree v1.2.0 // indirect + github.com/beevik/etree v1.2.0 github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect @@ -193,7 +203,7 @@ require ( go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.17.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect - golang.org/x/sys v0.11.0 + golang.org/x/sys v0.12.0 golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/appengine v1.6.7 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index f658e0227d..9e7ae585c7 100644 --- a/go.sum +++ b/go.sum @@ -165,9 +165,14 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo= +github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= +github.com/crewjam/saml v0.4.13 h1:TYHggH/hwP7eArqiXSJUvtOPNzQDyQ7vwmwEqlFWhMc= +github.com/crewjam/saml v0.4.13/go.mod h1:igEejV+fihTIlHXYP8zOec3V5A8y3lws5bQBFsTm4gA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/descope/virtualwebauthn v1.0.2 h1:cAvfS9wHh6On9HAE4Gjn3fJkf8MPQW2LzN8BPKEPs0M= @@ -179,6 +184,8 @@ github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwu github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dmarkham/enumer v1.5.8 h1:fIF11F9l5jyD++YYvxcSH5WgHfeaSGPaN/T4kOQ4qEM= +github.com/dmarkham/enumer v1.5.8/go.mod h1:d10o8R3t/gROm2p3BXqTkMt2+HMuxEmWCXzorAruYak= github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja v0.0.0-20230531210528-d7324b2d74f7/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= github.com/dop251/goja v0.0.0-20230828202809-3dbe69dd2b8e h1:UvQD6hTSfeM6hhTQ24Dlw2RppP05W7SWbWb6kubJAog= @@ -322,6 +329,8 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= +github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= @@ -623,6 +632,8 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= +github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -675,6 +686,8 @@ github.com/muesli/kmeans v0.3.1 h1:KshLQ8wAETfLWOJKMuDCVYHnafddSa1kwGh/IypGIzY= github.com/muesli/kmeans v0.3.1/go.mod h1:8/OvJW7cHc1BpRf8URb43m+vR105DDe+Kj1WcFXYDqc= github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM= github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM= +github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/vWUetyzY= +github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -707,6 +720,8 @@ github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnh github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/name v1.0.0 h1:n7LKFgHixETzxpRv2R77YgPUFo85QHGZKrdaYm7eY5U= +github.com/pascaldekloe/name v1.0.0/go.mod h1:Z//MfYJnH4jVpQ9wkclwu2I2MkHmXTlT9wR5UZScttM= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= @@ -763,16 +778,18 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE= -github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/cors v1.10.0 h1:62NOS1h+r8p1mW6FM0FSB0exioXLhd/sh15KpjWBZ+8= +github.com/rs/cors v1.10.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russellhaering/goxmldsig v1.2.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/russellhaering/goxmldsig v1.4.0 h1:8UcDh/xGyQiyrW+Fq5t8f+l2DLB1+zlhYzkPUJ7Qhys= github.com/russellhaering/goxmldsig v1.4.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -862,14 +879,16 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= +github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zitadel/logging v0.4.0 h1:lRAIFgaRoJpLNbsL7jtIYHcMDoEJP9QZB4GqMfl4xaA= github.com/zitadel/logging v0.4.0/go.mod h1:6uALRJawpkkuUPCkgzfgcPR3c2N908wqnOnIrRelUFc= -github.com/zitadel/oidc/v2 v2.10.0 h1:mKCOA1SF7R+XmKmicbOAMY2wxp3szZMuM3IzrkBGplQ= -github.com/zitadel/oidc/v2 v2.10.0/go.mod h1:rEM7F10FKuieuQUlQf9fRoSiTkSs8rx5Dj4SgfsMPWU= -github.com/zitadel/passwap v0.3.0 h1:kC/vzN9xQlEQjUAZs0z2P5nKrZs9AuTqprteSQ2S4Ag= -github.com/zitadel/passwap v0.3.0/go.mod h1:sIpG6HfmnP28qwxu8kf+ot53ERbLwU9fOITstAwZSms= -github.com/zitadel/saml v0.1.0 h1:FZKKFRCamoKmFH3kGOW0ObcDozaJz7NdHn+WPm8PcXc= -github.com/zitadel/saml v0.1.0/go.mod h1:M+X+3vMUulpoLofKeH/W1/qjQQ3owitc2GuGDu3oYpM= +github.com/zitadel/oidc/v2 v2.11.0 h1:Am4/yQr4iiM5bznRgF3FOp+wLdKx2gzSU73uyI9vvBE= +github.com/zitadel/oidc/v2 v2.11.0/go.mod h1:enFSVBQI6aE0TEB1ntjXs9r6O6DEosxX4uhEBLBVD8o= +github.com/zitadel/passwap v0.4.0 h1:cMaISx+Ve7ilgG7Q8xOli4Z6IWr8Gndss+jeBk5A3O0= +github.com/zitadel/passwap v0.4.0/go.mod h1:yHaDM4A68yRkdic5BZ4iUNoc19hT+kYt8n1/Nz+I87g= +github.com/zitadel/saml v0.1.2 h1:RICwNTuP2upX4A1sZ8iq1rv4/x3DhZHzFx1e5bTKoTo= +github.com/zitadel/saml v0.1.2/go.mod h1:M+X+3vMUulpoLofKeH/W1/qjQQ3owitc2GuGDu3oYpM= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= @@ -941,12 +960,13 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -987,6 +1007,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1037,8 +1059,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1048,8 +1070,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= -golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= +golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= +golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1132,8 +1154,8 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1153,8 +1175,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1220,6 +1242,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1379,6 +1403,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/api/assets/asset.go b/internal/api/assets/asset.go index 0714348a90..3dd03b3f2d 100644 --- a/internal/api/assets/asset.go +++ b/internal/api/assets/asset.go @@ -146,7 +146,7 @@ func UploadHandleFunc(s AssetsService, uploader Uploader) func(http.ResponseWrit return } if size > uploader.MaxFileSize() { - s.ErrorHandler()(w, r, fmt.Errorf("file to big, max file size is %vKB", uploader.MaxFileSize()/1024), http.StatusBadRequest) + s.ErrorHandler()(w, r, fmt.Errorf("file too big, max file size is %vKB", uploader.MaxFileSize()/1024), http.StatusBadRequest) return } diff --git a/internal/api/authz/context.go b/internal/api/authz/context.go index ec2e13d0ee..bfdd648fe4 100644 --- a/internal/api/authz/context.go +++ b/internal/api/authz/context.go @@ -78,9 +78,12 @@ func VerifyTokenAndCreateCtxData(ctx context.Context, token, orgID, orgDomain st if err != nil { return CtxData{}, errors.ThrowPermissionDenied(err, "AUTH-GHpw2", "could not read projectid by clientid") } - } - if err := checkOrigin(ctx, origins); err != nil { - return CtxData{}, err + // We used to check origins for every token, but service users shouldn't be used publicly (native app / SPA). + // Therefore, mostly won't send an origin and aren't able to configure them anyway. + // For the current time we will only check origins for tokens issued to users through apps (code / implicit flow). + if err := checkOrigin(ctx, origins); err != nil { + return CtxData{}, err + } } if orgID == "" && orgDomain == "" { orgID = resourceOwner diff --git a/internal/api/grpc/admin/feature.go b/internal/api/grpc/admin/feature.go new file mode 100644 index 0000000000..2fe443d5e8 --- /dev/null +++ b/internal/api/grpc/admin/feature.go @@ -0,0 +1,20 @@ +package admin + +import ( + "context" + + object_pb "github.com/zitadel/zitadel/internal/api/grpc/object" + "github.com/zitadel/zitadel/internal/domain" + admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin" +) + +func (s *Server) ActivateFeatureLoginDefaultOrg(ctx context.Context, _ *admin_pb.ActivateFeatureLoginDefaultOrgRequest) (*admin_pb.ActivateFeatureLoginDefaultOrgResponse, error) { + details, err := s.command.SetBooleanInstanceFeature(ctx, domain.FeatureLoginDefaultOrg, true) + if err != nil { + return nil, err + } + return &admin_pb.ActivateFeatureLoginDefaultOrgResponse{ + Details: object_pb.DomainToChangeDetailsPb(details), + }, nil + +} diff --git a/internal/api/grpc/admin/idp.go b/internal/api/grpc/admin/idp.go index d5bc2cef98..f382946ed7 100644 --- a/internal/api/grpc/admin/idp.go +++ b/internal/api/grpc/admin/idp.go @@ -426,6 +426,37 @@ func (s *Server) UpdateAppleProvider(ctx context.Context, req *admin_pb.UpdateAp }, nil } +func (s *Server) AddSAMLProvider(ctx context.Context, req *admin_pb.AddSAMLProviderRequest) (*admin_pb.AddSAMLProviderResponse, error) { + id, details, err := s.command.AddInstanceSAMLProvider(ctx, addSAMLProviderToCommand(req)) + if err != nil { + return nil, err + } + return &admin_pb.AddSAMLProviderResponse{ + Id: id, + Details: object_pb.DomainToAddDetailsPb(details), + }, nil +} + +func (s *Server) UpdateSAMLProvider(ctx context.Context, req *admin_pb.UpdateSAMLProviderRequest) (*admin_pb.UpdateSAMLProviderResponse, error) { + details, err := s.command.UpdateInstanceSAMLProvider(ctx, req.Id, updateSAMLProviderToCommand(req)) + if err != nil { + return nil, err + } + return &admin_pb.UpdateSAMLProviderResponse{ + Details: object_pb.DomainToChangeDetailsPb(details), + }, nil +} + +func (s *Server) RegenerateSAMLProviderCertificate(ctx context.Context, req *admin_pb.RegenerateSAMLProviderCertificateRequest) (*admin_pb.RegenerateSAMLProviderCertificateResponse, error) { + details, err := s.command.RegenerateInstanceSAMLProviderCertificate(ctx, req.Id) + if err != nil { + return nil, err + } + return &admin_pb.RegenerateSAMLProviderCertificateResponse{ + Details: object_pb.DomainToChangeDetailsPb(details), + }, nil +} + func (s *Server) DeleteProvider(ctx context.Context, req *admin_pb.DeleteProviderRequest) (*admin_pb.DeleteProviderResponse, error) { details, err := s.command.DeleteInstanceProvider(ctx, req.Id) if err != nil { diff --git a/internal/api/grpc/admin/idp_converter.go b/internal/api/grpc/admin/idp_converter.go index 3ab176276c..c5b6ede88a 100644 --- a/internal/api/grpc/admin/idp_converter.go +++ b/internal/api/grpc/admin/idp_converter.go @@ -1,6 +1,8 @@ package admin import ( + "github.com/crewjam/saml" + idp_grpc "github.com/zitadel/zitadel/internal/api/grpc/idp" "github.com/zitadel/zitadel/internal/api/grpc/object" "github.com/zitadel/zitadel/internal/command" @@ -9,6 +11,7 @@ import ( "github.com/zitadel/zitadel/internal/eventstore/v1/models" "github.com/zitadel/zitadel/internal/query" admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin" + idp_pb "github.com/zitadel/zitadel/pkg/grpc/idp" ) func addOIDCIDPRequestToDomain(req *admin_pb.AddOIDCIDPRequest) *domain.IDPConfig { @@ -464,3 +467,40 @@ func updateAppleProviderToCommand(req *admin_pb.UpdateAppleProviderRequest) comm IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions), } } + +func addSAMLProviderToCommand(req *admin_pb.AddSAMLProviderRequest) command.SAMLProvider { + return command.SAMLProvider{ + Name: req.Name, + Metadata: req.GetMetadataXml(), + MetadataURL: req.GetMetadataUrl(), + Binding: bindingToCommand(req.Binding), + WithSignedRequest: req.WithSignedRequest, + IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions), + } +} + +func updateSAMLProviderToCommand(req *admin_pb.UpdateSAMLProviderRequest) command.SAMLProvider { + return command.SAMLProvider{ + Name: req.Name, + Metadata: req.GetMetadataXml(), + MetadataURL: req.GetMetadataUrl(), + Binding: bindingToCommand(req.Binding), + WithSignedRequest: req.WithSignedRequest, + IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions), + } +} + +func bindingToCommand(binding idp_pb.SAMLBinding) string { + switch binding { + case idp_pb.SAMLBinding_SAML_BINDING_UNSPECIFIED: + return "" + case idp_pb.SAMLBinding_SAML_BINDING_POST: + return saml.HTTPPostBinding + case idp_pb.SAMLBinding_SAML_BINDING_REDIRECT: + return saml.HTTPRedirectBinding + case idp_pb.SAMLBinding_SAML_BINDING_ARTIFACT: + return saml.HTTPArtifactBinding + default: + return "" + } +} diff --git a/internal/api/grpc/management/idp.go b/internal/api/grpc/management/idp.go index 7ed3c4551e..11873d8480 100644 --- a/internal/api/grpc/management/idp.go +++ b/internal/api/grpc/management/idp.go @@ -418,6 +418,37 @@ func (s *Server) UpdateAppleProvider(ctx context.Context, req *mgmt_pb.UpdateApp }, nil } +func (s *Server) AddSAMLProvider(ctx context.Context, req *mgmt_pb.AddSAMLProviderRequest) (*mgmt_pb.AddSAMLProviderResponse, error) { + id, details, err := s.command.AddOrgSAMLProvider(ctx, authz.GetCtxData(ctx).OrgID, addSAMLProviderToCommand(req)) + if err != nil { + return nil, err + } + return &mgmt_pb.AddSAMLProviderResponse{ + Id: id, + Details: object_pb.DomainToAddDetailsPb(details), + }, nil +} + +func (s *Server) UpdateSAMLProvider(ctx context.Context, req *mgmt_pb.UpdateSAMLProviderRequest) (*mgmt_pb.UpdateSAMLProviderResponse, error) { + details, err := s.command.UpdateOrgSAMLProvider(ctx, authz.GetCtxData(ctx).OrgID, req.Id, updateSAMLProviderToCommand(req)) + if err != nil { + return nil, err + } + return &mgmt_pb.UpdateSAMLProviderResponse{ + Details: object_pb.DomainToChangeDetailsPb(details), + }, nil +} + +func (s *Server) RegenerateSAMLProviderCertificate(ctx context.Context, req *mgmt_pb.RegenerateSAMLProviderCertificateRequest) (*mgmt_pb.RegenerateSAMLProviderCertificateResponse, error) { + details, err := s.command.RegenerateOrgSAMLProviderCertificate(ctx, authz.GetCtxData(ctx).OrgID, req.Id) + if err != nil { + return nil, err + } + return &mgmt_pb.RegenerateSAMLProviderCertificateResponse{ + Details: object_pb.DomainToChangeDetailsPb(details), + }, nil +} + func (s *Server) DeleteProvider(ctx context.Context, req *mgmt_pb.DeleteProviderRequest) (*mgmt_pb.DeleteProviderResponse, error) { details, err := s.command.DeleteOrgProvider(ctx, authz.GetCtxData(ctx).OrgID, req.Id) if err != nil { diff --git a/internal/api/grpc/management/idp_converter.go b/internal/api/grpc/management/idp_converter.go index 7630be7ce8..e6df255f33 100644 --- a/internal/api/grpc/management/idp_converter.go +++ b/internal/api/grpc/management/idp_converter.go @@ -3,6 +3,8 @@ package management import ( "context" + "github.com/crewjam/saml" + "github.com/zitadel/zitadel/internal/api/authz" idp_grpc "github.com/zitadel/zitadel/internal/api/grpc/idp" "github.com/zitadel/zitadel/internal/api/grpc/object" @@ -12,6 +14,7 @@ import ( "github.com/zitadel/zitadel/internal/eventstore/v1/models" iam_model "github.com/zitadel/zitadel/internal/iam/model" "github.com/zitadel/zitadel/internal/query" + idp_pb "github.com/zitadel/zitadel/pkg/grpc/idp" mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management" ) @@ -481,3 +484,40 @@ func updateAppleProviderToCommand(req *mgmt_pb.UpdateAppleProviderRequest) comma IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions), } } + +func addSAMLProviderToCommand(req *mgmt_pb.AddSAMLProviderRequest) command.SAMLProvider { + return command.SAMLProvider{ + Name: req.Name, + Metadata: req.GetMetadataXml(), + MetadataURL: req.GetMetadataUrl(), + Binding: bindingToCommand(req.Binding), + WithSignedRequest: req.WithSignedRequest, + IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions), + } +} + +func updateSAMLProviderToCommand(req *mgmt_pb.UpdateSAMLProviderRequest) command.SAMLProvider { + return command.SAMLProvider{ + Name: req.Name, + Metadata: req.GetMetadataXml(), + MetadataURL: req.GetMetadataUrl(), + Binding: bindingToCommand(req.Binding), + WithSignedRequest: req.WithSignedRequest, + IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions), + } +} + +func bindingToCommand(binding idp_pb.SAMLBinding) string { + switch binding { + case idp_pb.SAMLBinding_SAML_BINDING_UNSPECIFIED: + return "" + case idp_pb.SAMLBinding_SAML_BINDING_POST: + return saml.HTTPPostBinding + case idp_pb.SAMLBinding_SAML_BINDING_REDIRECT: + return saml.HTTPRedirectBinding + case idp_pb.SAMLBinding_SAML_BINDING_ARTIFACT: + return saml.HTTPArtifactBinding + default: + return "" + } +} diff --git a/internal/api/grpc/system/feature.go b/internal/api/grpc/system/feature.go new file mode 100644 index 0000000000..a577af4f00 --- /dev/null +++ b/internal/api/grpc/system/feature.go @@ -0,0 +1,34 @@ +package system + +import ( + "context" + + object_pb "github.com/zitadel/zitadel/internal/api/grpc/object" + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/errors" + system_pb "github.com/zitadel/zitadel/pkg/grpc/system" +) + +func (s *Server) SetInstanceFeature(ctx context.Context, req *system_pb.SetInstanceFeatureRequest) (*system_pb.SetInstanceFeatureResponse, error) { + details, err := s.setInstanceFeature(ctx, req) + if err != nil { + return nil, err + } + return &system_pb.SetInstanceFeatureResponse{ + Details: object_pb.DomainToChangeDetailsPb(details), + }, nil + +} + +func (s *Server) setInstanceFeature(ctx context.Context, req *system_pb.SetInstanceFeatureRequest) (*domain.ObjectDetails, error) { + feat := domain.Feature(req.FeatureId) + if !feat.IsAFeature() { + return nil, errors.ThrowInvalidArgument(nil, "SYST-SGV45", "Errors.Feature.NotExisting") + } + switch t := req.Value.(type) { + case *system_pb.SetInstanceFeatureRequest_Bool: + return s.command.SetBooleanInstanceFeature(ctx, feat, t.Bool) + default: + return nil, errors.ThrowInvalidArgument(nil, "SYST-dag5g", "Errors.Feature.TypeNotSupported") + } +} diff --git a/internal/api/grpc/user/v2/server.go b/internal/api/grpc/user/v2/server.go index 83cf58c5df..8e0f24d5dc 100644 --- a/internal/api/grpc/user/v2/server.go +++ b/internal/api/grpc/user/v2/server.go @@ -22,6 +22,7 @@ type Server struct { userCodeAlg crypto.EncryptionAlgorithm idpAlg crypto.EncryptionAlgorithm idpCallback func(ctx context.Context) string + samlRootURL func(ctx context.Context, idpID string) string } type Config struct{} @@ -32,6 +33,7 @@ func CreateServer( userCodeAlg crypto.EncryptionAlgorithm, idpAlg crypto.EncryptionAlgorithm, idpCallback func(ctx context.Context) string, + samlRootURL func(ctx context.Context, idpID string) string, ) *Server { return &Server{ command: command, @@ -39,6 +41,7 @@ func CreateServer( userCodeAlg: userCodeAlg, idpAlg: idpAlg, idpCallback: idpCallback, + samlRootURL: samlRootURL, } } diff --git a/internal/api/grpc/user/v2/user.go b/internal/api/grpc/user/v2/user.go index 76cd067c49..5ee56f2b68 100644 --- a/internal/api/grpc/user/v2/user.go +++ b/internal/api/grpc/user/v2/user.go @@ -145,14 +145,23 @@ func (s *Server) startIDPIntent(ctx context.Context, idpID string, urls *user.Re if err != nil { return nil, err } - authURL, err := s.command.AuthURLFromProvider(ctx, idpID, intentWriteModel.AggregateID, s.idpCallback(ctx)) + content, redirect, err := s.command.AuthFromProvider(ctx, idpID, intentWriteModel.AggregateID, s.idpCallback(ctx), s.samlRootURL(ctx, idpID)) if err != nil { return nil, err } - return &user.StartIdentityProviderIntentResponse{ - Details: object.DomainToDetailsPb(details), - NextStep: &user.StartIdentityProviderIntentResponse_AuthUrl{AuthUrl: authURL}, - }, nil + if redirect { + return &user.StartIdentityProviderIntentResponse{ + Details: object.DomainToDetailsPb(details), + NextStep: &user.StartIdentityProviderIntentResponse_AuthUrl{AuthUrl: content}, + }, nil + } else { + return &user.StartIdentityProviderIntentResponse{ + Details: object.DomainToDetailsPb(details), + NextStep: &user.StartIdentityProviderIntentResponse_PostForm{ + PostForm: []byte(content), + }, + }, nil + } } func (s *Server) startLDAPIntent(ctx context.Context, idpID string, ldapCredentials *user.LDAPCredentials) (*user.StartIdentityProviderIntentResponse, error) { @@ -172,8 +181,14 @@ func (s *Server) startLDAPIntent(ctx context.Context, idpID string, ldapCredenti return nil, err } return &user.StartIdentityProviderIntentResponse{ - Details: object.DomainToDetailsPb(details), - NextStep: &user.StartIdentityProviderIntentResponse_IdpIntent{IdpIntent: &user.IDPIntent{IdpIntentId: intentWriteModel.AggregateID, IdpIntentToken: token}}, + Details: object.DomainToDetailsPb(details), + NextStep: &user.StartIdentityProviderIntentResponse_IdpIntent{ + IdpIntent: &user.IDPIntent{ + IdpIntentId: intentWriteModel.AggregateID, + IdpIntentToken: token, + UserId: userID, + }, + }, }, nil } @@ -200,7 +215,7 @@ func (s *Server) checkLinkedExternalUser(ctx context.Context, idpID, externalUse } func (s *Server) ldapLogin(ctx context.Context, idpID, username, password string) (idp.User, string, map[string][]string, error) { - provider, err := s.command.GetProvider(ctx, idpID, "") + provider, err := s.command.GetProvider(ctx, idpID, "", "") if err != nil { return nil, "", nil, err } @@ -256,6 +271,7 @@ func idpIntentToIDPIntentPb(intent *command.IDPIntentWriteModel, alg crypto.Encr UserName: intent.IDPUserName, RawInformation: rawInformation, }, + UserId: intent.UserID, } if intent.IDPIDToken != "" || intent.IDPAccessToken != nil { information.IdpInformation.Access, err = idpOAuthTokensToPb(intent.IDPIDToken, intent.IDPAccessToken, alg) @@ -272,6 +288,14 @@ func idpIntentToIDPIntentPb(intent *command.IDPIntentWriteModel, alg crypto.Encr information.IdpInformation.Access = access } + if intent.Assertion != nil { + assertion, err := crypto.Decrypt(intent.Assertion, alg) + if err != nil { + return nil, err + } + information.IdpInformation.Access = IDPSAMLResponseToPb(assertion) + } + return information, nil } @@ -323,6 +347,14 @@ func IDPEntryAttributesToPb(entryAttributes map[string][]string) (*user.IDPInfor }, nil } +func IDPSAMLResponseToPb(assertion []byte) *user.IDPInformation_Saml { + return &user.IDPInformation_Saml{ + Saml: &user.IDPSAMLAccessInformation{ + Assertion: assertion, + }, + } +} + func (s *Server) checkIntentToken(token string, intentID string) error { return crypto.CheckToken(s.idpAlg, token, intentID) } diff --git a/internal/api/grpc/user/v2/user_integration_test.go b/internal/api/grpc/user/v2/user_integration_test.go index cf69969f33..4bb1fe8710 100644 --- a/internal/api/grpc/user/v2/user_integration_test.go +++ b/internal/api/grpc/user/v2/user_integration_test.go @@ -5,8 +5,8 @@ package user_test import ( "context" "fmt" + "net/url" "os" - "strings" "testing" "time" @@ -624,14 +624,24 @@ func TestServer_AddIDPLink(t *testing.T) { func TestServer_StartIdentityProviderIntent(t *testing.T) { idpID := Tester.AddGenericOAuthProvider(t) + samlIdpID := Tester.AddSAMLProvider(t) + samlRedirectIdpID := Tester.AddSAMLRedirectProvider(t) + samlPostIdpID := Tester.AddSAMLPostProvider(t) type args struct { ctx context.Context req *user.StartIdentityProviderIntentRequest } + type want struct { + details *object.Details + url string + parametersExisting []string + parametersEqual map[string]string + postForm bool + } tests := []struct { name string args args - want *user.StartIdentityProviderIntentResponse + want want wantErr bool }{ { @@ -642,11 +652,10 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) { IdpId: idpID, }, }, - want: nil, wantErr: true, }, { - name: "next step auth url", + name: "next step oauth auth url", args: args{ CTX, &user.StartIdentityProviderIntentRequest{ @@ -659,14 +668,91 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) { }, }, }, - want: &user.StartIdentityProviderIntentResponse{ - Details: &object.Details{ + want: want{ + details: &object.Details{ ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Organisation.ID, }, - NextStep: &user.StartIdentityProviderIntentResponse_AuthUrl{ - AuthUrl: "https://example.com/oauth/v2/authorize?client_id=clientID&prompt=select_account&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fidps%2Fcallback&response_type=code&scope=openid+profile+email&state=", + url: "https://example.com/oauth/v2/authorize", + parametersEqual: map[string]string{ + "client_id": "clientID", + "prompt": "select_account", + "redirect_uri": "http://localhost:8080/idps/callback", + "response_type": "code", + "scope": "openid profile email", }, + parametersExisting: []string{"state"}, + }, + wantErr: false, + }, + { + name: "next step saml default", + args: args{ + CTX, + &user.StartIdentityProviderIntentRequest{ + IdpId: samlIdpID, + Content: &user.StartIdentityProviderIntentRequest_Urls{ + Urls: &user.RedirectURLs{ + SuccessUrl: "https://example.com/success", + FailureUrl: "https://example.com/failure", + }, + }, + }, + }, + want: want{ + details: &object.Details{ + ChangeDate: timestamppb.Now(), + ResourceOwner: Tester.Organisation.ID, + }, + url: "http://localhost:8000/sso", + parametersExisting: []string{"RelayState", "SAMLRequest"}, + }, + wantErr: false, + }, + { + name: "next step saml auth url", + args: args{ + CTX, + &user.StartIdentityProviderIntentRequest{ + IdpId: samlRedirectIdpID, + Content: &user.StartIdentityProviderIntentRequest_Urls{ + Urls: &user.RedirectURLs{ + SuccessUrl: "https://example.com/success", + FailureUrl: "https://example.com/failure", + }, + }, + }, + }, + want: want{ + details: &object.Details{ + ChangeDate: timestamppb.Now(), + ResourceOwner: Tester.Organisation.ID, + }, + url: "http://localhost:8000/sso", + parametersExisting: []string{"RelayState", "SAMLRequest"}, + }, + wantErr: false, + }, + { + name: "next step saml form", + args: args{ + CTX, + &user.StartIdentityProviderIntentRequest{ + IdpId: samlPostIdpID, + Content: &user.StartIdentityProviderIntentRequest_Urls{ + Urls: &user.RedirectURLs{ + SuccessUrl: "https://example.com/success", + FailureUrl: "https://example.com/failure", + }, + }, + }, + }, + want: want{ + details: &object.Details{ + ChangeDate: timestamppb.Now(), + ResourceOwner: Tester.Organisation.ID, + }, + postForm: true, }, wantErr: false, }, @@ -680,12 +766,25 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) { require.NoError(t, err) } - if nextStep := tt.want.GetNextStep(); nextStep != nil { - if !strings.HasPrefix(got.GetAuthUrl(), tt.want.GetAuthUrl()) { - assert.Failf(t, "auth url does not match", "expected: %s, but got: %s", tt.want.GetAuthUrl(), got.GetAuthUrl()) + if tt.want.url != "" { + authUrl, err := url.Parse(got.GetAuthUrl()) + assert.NoError(t, err) + + assert.Len(t, authUrl.Query(), len(tt.want.parametersEqual)+len(tt.want.parametersExisting)) + + for _, existing := range tt.want.parametersExisting { + assert.True(t, authUrl.Query().Has(existing)) + } + for key, equal := range tt.want.parametersEqual { + assert.Equal(t, equal, authUrl.Query().Get(key)) } } - integration.AssertDetails(t, tt.want, got) + if tt.want.postForm { + assert.NotEmpty(t, got.GetPostForm()) + } + integration.AssertDetails(t, &user.StartIdentityProviderIntentResponse{ + Details: tt.want.details, + }, got) }) } } @@ -694,7 +793,10 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) { idpID := Tester.AddGenericOAuthProvider(t) intentID := Tester.CreateIntent(t, idpID) successfulID, token, changeDate, sequence := Tester.CreateSuccessfulOAuthIntent(t, idpID, "", "id") + successfulWithUserID, WithUsertoken, WithUserchangeDate, WithUsersequence := Tester.CreateSuccessfulOAuthIntent(t, idpID, "user", "id") ldapSuccessfulID, ldapToken, ldapChangeDate, ldapSequence := Tester.CreateSuccessfulLDAPIntent(t, idpID, "", "id") + ldapSuccessfulWithUserID, ldapWithUserToken, ldapWithUserChangeDate, ldapWithUserSequence := Tester.CreateSuccessfulLDAPIntent(t, idpID, "user", "id") + samlSuccessfulID, samlToken, samlChangeDate, samlSequence := Tester.CreateSuccessfulSAMLIntent(t, idpID, "", "id") type args struct { ctx context.Context req *user.RetrieveIdentityProviderIntentRequest @@ -764,6 +866,44 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) { }, wantErr: false, }, + { + name: "retrieve successful intent with linked user", + args: args{ + CTX, + &user.RetrieveIdentityProviderIntentRequest{ + IdpIntentId: successfulWithUserID, + IdpIntentToken: WithUsertoken, + }, + }, + want: &user.RetrieveIdentityProviderIntentResponse{ + Details: &object.Details{ + ChangeDate: timestamppb.New(WithUserchangeDate), + ResourceOwner: Tester.Organisation.ID, + Sequence: WithUsersequence, + }, + UserId: "user", + IdpInformation: &user.IDPInformation{ + Access: &user.IDPInformation_Oauth{ + Oauth: &user.IDPOAuthAccessInformation{ + AccessToken: "accessToken", + IdToken: gu.Ptr("idToken"), + }, + }, + IdpId: idpID, + UserId: "id", + UserName: "username", + RawInformation: func() *structpb.Struct { + s, err := structpb.NewStruct(map[string]interface{}{ + "sub": "id", + "preferred_username": "username", + }) + require.NoError(t, err) + return s + }(), + }, + }, + wantErr: false, + }, { name: "retrieve successful ldap intent", args: args{ @@ -809,6 +949,90 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) { }, wantErr: false, }, + { + name: "retrieve successful ldap intent with linked user", + args: args{ + CTX, + &user.RetrieveIdentityProviderIntentRequest{ + IdpIntentId: ldapSuccessfulWithUserID, + IdpIntentToken: ldapWithUserToken, + }, + }, + want: &user.RetrieveIdentityProviderIntentResponse{ + Details: &object.Details{ + ChangeDate: timestamppb.New(ldapWithUserChangeDate), + ResourceOwner: Tester.Organisation.ID, + Sequence: ldapWithUserSequence, + }, + UserId: "user", + IdpInformation: &user.IDPInformation{ + Access: &user.IDPInformation_Ldap{ + Ldap: &user.IDPLDAPAccessInformation{ + Attributes: func() *structpb.Struct { + s, err := structpb.NewStruct(map[string]interface{}{ + "id": []interface{}{"id"}, + "username": []interface{}{"username"}, + "language": []interface{}{"en"}, + }) + require.NoError(t, err) + return s + }(), + }, + }, + IdpId: idpID, + UserId: "id", + UserName: "username", + RawInformation: func() *structpb.Struct { + s, err := structpb.NewStruct(map[string]interface{}{ + "id": "id", + "preferredUsername": "username", + "preferredLanguage": "en", + }) + require.NoError(t, err) + return s + }(), + }, + }, + wantErr: false, + }, + { + name: "retrieve successful saml intent", + args: args{ + CTX, + &user.RetrieveIdentityProviderIntentRequest{ + IdpIntentId: samlSuccessfulID, + IdpIntentToken: samlToken, + }, + }, + want: &user.RetrieveIdentityProviderIntentResponse{ + Details: &object.Details{ + ChangeDate: timestamppb.New(samlChangeDate), + ResourceOwner: Tester.Organisation.ID, + Sequence: samlSequence, + }, + IdpInformation: &user.IDPInformation{ + Access: &user.IDPInformation_Saml{ + Saml: &user.IDPSAMLAccessInformation{ + Assertion: []byte(""), + }, + }, + IdpId: idpID, + UserId: "id", + UserName: "", + RawInformation: func() *structpb.Struct { + s, err := structpb.NewStruct(map[string]interface{}{ + "id": "id", + "attributes": map[string]interface{}{ + "attribute1": []interface{}{"value1"}, + }, + }) + require.NoError(t, err) + return s + }(), + }, + }, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/api/grpc/user/v2/user_test.go b/internal/api/grpc/user/v2/user_test.go index e454bc5ae3..48fbf7550f 100644 --- a/internal/api/grpc/user/v2/user_test.go +++ b/internal/api/grpc/user/v2/user_test.go @@ -84,9 +84,65 @@ func Test_idpIntentToIDPIntentPb(t *testing.T) { resp: nil, err: caos_errs.ThrowInternal(nil, "id", "invalid key id"), }, + }, { + "successful oauth", + args{ + intent: &command.IDPIntentWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: "intentID", + ProcessedSequence: 123, + ResourceOwner: "ro", + InstanceID: "instanceID", + ChangeDate: time.Date(2019, 4, 1, 1, 1, 1, 1, time.Local), + }, + IDPID: "idpID", + IDPUser: []byte(`{"userID": "idpUserID", "username": "username"}`), + IDPUserID: "idpUserID", + IDPUserName: "username", + IDPAccessToken: &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("accessToken"), + }, + IDPIDToken: "idToken", + UserID: "", + State: domain.IDPIntentStateSucceeded, + }, + alg: decryption(nil), + }, + res{ + resp: &user.RetrieveIdentityProviderIntentResponse{ + Details: &object_pb.Details{ + Sequence: 123, + ChangeDate: timestamppb.New(time.Date(2019, 4, 1, 1, 1, 1, 1, time.Local)), + ResourceOwner: "ro", + }, + IdpInformation: &user.IDPInformation{ + Access: &user.IDPInformation_Oauth{ + Oauth: &user.IDPOAuthAccessInformation{ + AccessToken: "accessToken", + IdToken: gu.Ptr("idToken"), + }, + }, + IdpId: "idpID", + UserId: "idpUserID", + UserName: "username", + RawInformation: func() *structpb.Struct { + s, err := structpb.NewStruct(map[string]interface{}{ + "userID": "idpUserID", + "username": "username", + }) + require.NoError(t, err) + return s + }(), + }, + }, + err: nil, + }, }, { - "successful oauth", + "successful oauth with linked user", args{ intent: &command.IDPIntentWriteModel{ WriteModel: eventstore.WriteModel{ @@ -138,11 +194,72 @@ func Test_idpIntentToIDPIntentPb(t *testing.T) { return s }(), }, + UserId: "userID", }, err: nil, }, }, { "successful ldap", + args{ + intent: &command.IDPIntentWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: "intentID", + ProcessedSequence: 123, + ResourceOwner: "ro", + InstanceID: "instanceID", + ChangeDate: time.Date(2019, 4, 1, 1, 1, 1, 1, time.Local), + }, + IDPID: "idpID", + IDPUser: []byte(`{"userID": "idpUserID", "username": "username"}`), + IDPUserID: "idpUserID", + IDPUserName: "username", + IDPEntryAttributes: map[string][]string{ + "id": {"idpUserID"}, + "firstName": {"firstname1", "firstname2"}, + "lastName": {"lastname"}, + }, + UserID: "", + State: domain.IDPIntentStateSucceeded, + }, + }, + res{ + resp: &user.RetrieveIdentityProviderIntentResponse{ + Details: &object_pb.Details{ + Sequence: 123, + ChangeDate: timestamppb.New(time.Date(2019, 4, 1, 1, 1, 1, 1, time.Local)), + ResourceOwner: "ro", + }, + IdpInformation: &user.IDPInformation{ + Access: &user.IDPInformation_Ldap{ + Ldap: &user.IDPLDAPAccessInformation{ + Attributes: func() *structpb.Struct { + s, err := structpb.NewStruct(map[string]interface{}{ + "id": []interface{}{"idpUserID"}, + "firstName": []interface{}{"firstname1", "firstname2"}, + "lastName": []interface{}{"lastname"}, + }) + require.NoError(t, err) + return s + }(), + }, + }, + IdpId: "idpID", + UserId: "idpUserID", + UserName: "username", + RawInformation: func() *structpb.Struct { + s, err := structpb.NewStruct(map[string]interface{}{ + "userID": "idpUserID", + "username": "username", + }) + require.NoError(t, err) + return s + }(), + }, + }, + err: nil, + }, + }, { + "successful ldap with linked user", args{ intent: &command.IDPIntentWriteModel{ WriteModel: eventstore.WriteModel{ @@ -198,6 +315,7 @@ func Test_idpIntentToIDPIntentPb(t *testing.T) { return s }(), }, + UserId: "userID", }, err: nil, }, diff --git a/internal/api/idp/idp.go b/internal/api/idp/idp.go index 486d472389..f1fa32fc57 100644 --- a/internal/api/idp/idp.go +++ b/internal/api/idp/idp.go @@ -1,18 +1,23 @@ package idp import ( + "bytes" "context" + "encoding/xml" "errors" + "fmt" + "io" "net/http" + "github.com/crewjam/saml" "github.com/gorilla/mux" "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" http_utils "github.com/zitadel/zitadel/internal/api/http" + "github.com/zitadel/zitadel/internal/api/ui/login" "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/crypto" - "github.com/zitadel/zitadel/internal/domain" z_errs "github.com/zitadel/zitadel/internal/errors" "github.com/zitadel/zitadel/internal/form" "github.com/zitadel/zitadel/internal/idp" @@ -25,19 +30,26 @@ import ( "github.com/zitadel/zitadel/internal/idp/providers/ldap" "github.com/zitadel/zitadel/internal/idp/providers/oauth" openid "github.com/zitadel/zitadel/internal/idp/providers/oidc" + saml2 "github.com/zitadel/zitadel/internal/idp/providers/saml" "github.com/zitadel/zitadel/internal/query" ) const ( - HandlerPrefix = "/idps" - callbackPath = "/callback" - ldapCallbackPath = callbackPath + "/ldap" + HandlerPrefix = "/idps" + + idpPrefix = "/{" + varIDPID + ":[0-9]+}" + + callbackPath = "/callback" + metadataPath = idpPrefix + "/saml/metadata" + acsPath = idpPrefix + "/saml/acs" + certificatePath = idpPrefix + "/saml/certificate" paramIntentID = "id" paramToken = "token" paramUserID = "user" paramError = "error" paramErrorDescription = "error_description" + varIDPID = "idpid" ) type Handler struct { @@ -46,6 +58,8 @@ type Handler struct { parser *form.Parser encryptionAlgorithm crypto.EncryptionAlgorithm callbackURL func(ctx context.Context) string + samlRootURL func(ctx context.Context, idpID string) string + loginSAMLRootURL func(ctx context.Context) string } type externalIDPCallbackData struct { @@ -58,6 +72,12 @@ type externalIDPCallbackData struct { User string `schema:"user"` } +type externalSAMLIDPCallbackData struct { + IDPID string + Response string + RelayState string +} + // CallbackURL generates the instance specific URL to the IDP callback handler func CallbackURL(externalSecure bool) func(ctx context.Context) string { return func(ctx context.Context) string { @@ -65,6 +85,18 @@ func CallbackURL(externalSecure bool) func(ctx context.Context) string { } } +func SAMLRootURL(externalSecure bool) func(ctx context.Context, idpID string) string { + return func(ctx context.Context, idpID string) string { + return http_utils.BuildOrigin(authz.GetInstance(ctx).RequestedHost(), externalSecure) + HandlerPrefix + "/" + idpID + "/" + } +} + +func LoginSAMLRootURL(externalSecure bool) func(ctx context.Context) string { + return func(ctx context.Context) string { + return http_utils.BuildOrigin(authz.GetInstance(ctx).RequestedHost(), externalSecure) + login.HandlerPrefix + login.EndpointSAMLACS + } +} + func NewHandler( commands *command.Commands, queries *query.Queries, @@ -78,14 +110,166 @@ func NewHandler( parser: form.NewParser(), encryptionAlgorithm: encryptionAlgorithm, callbackURL: CallbackURL(externalSecure), + samlRootURL: SAMLRootURL(externalSecure), + loginSAMLRootURL: LoginSAMLRootURL(externalSecure), } router := mux.NewRouter() router.Use(instanceInterceptor) router.HandleFunc(callbackPath, h.handleCallback) + router.HandleFunc(metadataPath, h.handleMetadata) + router.HandleFunc(certificatePath, h.handleCertificate) + router.HandleFunc(acsPath, h.handleACS) return router } +func parseSAMLRequest(r *http.Request) *externalSAMLIDPCallbackData { + vars := mux.Vars(r) + return &externalSAMLIDPCallbackData{ + IDPID: vars[varIDPID], + Response: r.FormValue("SAMLResponse"), + RelayState: r.FormValue("RelayState"), + } +} + +func (h *Handler) getProvider(ctx context.Context, idpID string) (idp.Provider, error) { + return h.commands.GetProvider(ctx, idpID, h.callbackURL(ctx), h.samlRootURL(ctx, idpID)) +} + +func (h *Handler) handleCertificate(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + data := parseSAMLRequest(r) + + provider, err := h.getProvider(ctx, data.IDPID) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + samlProvider, ok := provider.(*saml2.Provider) + if !ok { + http.Error(w, z_errs.ThrowInvalidArgument(nil, "SAML-lrud8s9coi", "Errors.Intent.IDPInvalid").Error(), http.StatusBadRequest) + return + } + + certPem := new(bytes.Buffer) + if _, err := certPem.Write(samlProvider.Certificate); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + w.Header().Set("Content-Disposition", "attachment; filename=idp.crt") + w.Header().Set("Content-Type", r.Header.Get("Content-Type")) + _, err = io.Copy(w, certPem) + if err != nil { + http.Error(w, fmt.Errorf("failed to response with certificate: %w", err).Error(), http.StatusInternalServerError) + return + } +} + +func (h *Handler) handleMetadata(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + data := parseSAMLRequest(r) + + provider, err := h.getProvider(ctx, data.IDPID) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + samlProvider, ok := provider.(*saml2.Provider) + if !ok { + http.Error(w, z_errs.ThrowInvalidArgument(nil, "SAML-lrud8s9coi", "Errors.Intent.IDPInvalid").Error(), http.StatusBadRequest) + return + } + + sp, err := samlProvider.GetSP() + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + metadata := sp.ServiceProvider.Metadata() + + for i, spDesc := range metadata.SPSSODescriptors { + spDesc.AssertionConsumerServices = append( + spDesc.AssertionConsumerServices, + saml.IndexedEndpoint{ + Binding: saml.HTTPPostBinding, + Location: h.loginSAMLRootURL(ctx), + Index: len(spDesc.AssertionConsumerServices) + 1, + }, saml.IndexedEndpoint{ + Binding: saml.HTTPArtifactBinding, + Location: h.loginSAMLRootURL(ctx), + Index: len(spDesc.AssertionConsumerServices) + 2, + }, + ) + metadata.SPSSODescriptors[i] = spDesc + } + + buf, _ := xml.MarshalIndent(metadata, "", " ") + w.Header().Set("Content-Type", "application/samlmetadata+xml") + _, err = w.Write(buf) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } +} + +func (h *Handler) handleACS(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + data := parseSAMLRequest(r) + + provider, err := h.getProvider(ctx, data.IDPID) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + samlProvider, ok := provider.(*saml2.Provider) + if !ok { + err := z_errs.ThrowInvalidArgument(nil, "SAML-ui9wyux0hp", "Errors.Intent.IDPInvalid") + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + sp, err := samlProvider.GetSP() + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + intent, err := h.commands.GetActiveIntent(ctx, data.RelayState) + if err != nil { + if z_errs.IsNotFound(err) { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + redirectToFailureURLErr(w, r, intent, err) + return + } + + session := saml2.Session{ + ServiceProvider: sp, + RequestID: intent.RequestID, + Request: r, + } + + idpUser, err := session.FetchUser(r.Context()) + if err != nil { + cmdErr := h.commands.FailIDPIntent(ctx, intent, err.Error()) + logging.WithFields("intent", intent.AggregateID).OnError(cmdErr).Error("failed to push failed event on idp intent") + redirectToFailureURLErr(w, r, intent, err) + return + } + + userID, err := h.checkExternalUser(ctx, intent.IDPID, idpUser.GetID()) + logging.WithFields("intent", intent.AggregateID).OnError(err).Error("could not check if idp user already exists") + + token, err := h.commands.SucceedSAMLIDPIntent(ctx, intent, idpUser, userID, session.Assertion) + if err != nil { + redirectToFailureURLErr(w, r, intent, z_errs.ThrowInternal(err, "IDP-JdD3g", "Errors.Intent.TokenCreationFailed")) + return + } + redirectToSuccessURL(w, r, intent, token, userID) +} + func (h *Handler) handleCallback(w http.ResponseWriter, r *http.Request) { ctx := r.Context() data, err := h.parseCallbackRequest(r) @@ -111,7 +295,7 @@ func (h *Handler) handleCallback(w http.ResponseWriter, r *http.Request) { return } - provider, err := h.commands.GetProvider(ctx, intent.IDPID, h.callbackURL(ctx)) + provider, err := h.getProvider(ctx, intent.IDPID) if err != nil { cmdErr := h.commands.FailIDPIntent(ctx, intent, err.Error()) logging.WithFields("intent", intent.AggregateID).OnError(cmdErr).Error("failed to push failed event on idp intent") @@ -119,7 +303,7 @@ func (h *Handler) handleCallback(w http.ResponseWriter, r *http.Request) { return } - idpUser, idpSession, err := h.fetchIDPUser(ctx, provider, data.Code, data.User) + idpUser, idpSession, err := h.fetchIDPUserFromCode(ctx, provider, data.Code, data.User) if err != nil { cmdErr := h.commands.FailIDPIntent(ctx, intent, err.Error()) logging.WithFields("intent", intent.AggregateID).OnError(cmdErr).Error("failed to push failed event on idp intent") @@ -170,23 +354,6 @@ func (h *Handler) parseCallbackRequest(r *http.Request) (*externalIDPCallbackDat return data, nil } -func (h *Handler) getActiveIntent(w http.ResponseWriter, r *http.Request, state string) *command.IDPIntentWriteModel { - intent, err := h.commands.GetIntentWriteModel(r.Context(), state, "") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return nil - } - if intent.State == domain.IDPIntentStateUnspecified { - http.Error(w, reason("IDP-Hk38e", "Errors.Intent.NotStarted"), http.StatusBadRequest) - return nil - } - if intent.State != domain.IDPIntentStateStarted { - redirectToFailureURL(w, r, intent, "IDP-Sfrgs", "Errors.Intent.NotStarted") - return nil - } - return intent -} - func redirectToSuccessURL(w http.ResponseWriter, r *http.Request, intent *command.IDPIntentWriteModel, token, userID string) { queries := intent.SuccessURL.Query() queries.Set(paramIntentID, intent.AggregateID) @@ -218,7 +385,7 @@ func redirectToFailureURL(w http.ResponseWriter, r *http.Request, i *command.IDP http.Redirect(w, r, i.FailureURL.String(), http.StatusFound) } -func (h *Handler) fetchIDPUser(ctx context.Context, identityProvider idp.Provider, code string, appleUser string) (user idp.User, idpTokens idp.Session, err error) { +func (h *Handler) fetchIDPUserFromCode(ctx context.Context, identityProvider idp.Provider, code string, appleUser string) (user idp.User, idpTokens idp.Session, err error) { var session idp.Session switch provider := identityProvider.(type) { case *oauth.Provider: @@ -235,7 +402,7 @@ func (h *Handler) fetchIDPUser(ctx context.Context, identityProvider idp.Provide session = &openid.Session{Provider: provider.Provider, Code: code} case *apple.Provider: session = &apple.Session{Session: &openid.Session{Provider: provider.Provider, Code: code}, UserFormValue: appleUser} - case *jwt.Provider, *ldap.Provider: + case *jwt.Provider, *ldap.Provider, *saml2.Provider: return nil, nil, z_errs.ThrowInvalidArgument(nil, "IDP-52jmn", "Errors.ExternalIDP.IDPTypeNotImplemented") default: return nil, nil, z_errs.ThrowUnimplemented(nil, "IDP-SSDg", "Errors.ExternalIDP.IDPTypeNotImplemented") diff --git a/internal/api/idp/idp_integration_test.go b/internal/api/idp/idp_integration_test.go new file mode 100644 index 0000000000..790f1926ae --- /dev/null +++ b/internal/api/idp/idp_integration_test.go @@ -0,0 +1,488 @@ +//go:build integration + +package idp_test + +import ( + "context" + "crypto" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "encoding/xml" + "io" + "net/http" + "net/url" + "os" + "strings" + "testing" + "time" + + "github.com/beevik/etree" + "github.com/crewjam/saml" + "github.com/crewjam/saml/samlidp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + saml_xml "github.com/zitadel/saml/pkg/provider/xml" + "golang.org/x/crypto/bcrypt" + + http_util "github.com/zitadel/zitadel/internal/api/http" + "github.com/zitadel/zitadel/internal/integration" + user "github.com/zitadel/zitadel/pkg/grpc/user/v2beta" +) + +var ( + CTX context.Context + ErrCTX context.Context + Tester *integration.Tester + Client user.UserServiceClient +) + +func TestMain(m *testing.M) { + os.Exit(func() int { + ctx, errCtx, cancel := integration.Contexts(time.Hour) + defer cancel() + + Tester = integration.NewTester(ctx) + defer Tester.Done() + + CTX, ErrCTX = Tester.WithAuthorization(ctx, integration.OrgOwner), errCtx + Client = Tester.Client.UserV2 + return m.Run() + }()) +} + +func TestServer_SAMLCertificate(t *testing.T) { + samlRedirectIdpID := Tester.AddSAMLRedirectProvider(t) + oauthIdpID := Tester.AddGenericOAuthProvider(t) + + type args struct { + ctx context.Context + idpID string + } + tests := []struct { + name string + args args + want int + }{ + { + name: "saml certificate, invalid idp", + args: args{ + ctx: CTX, + idpID: "unknown", + }, + want: http.StatusNotFound, + }, + { + name: "saml certificate, invalid idp type", + args: args{ + ctx: CTX, + idpID: oauthIdpID, + }, + want: http.StatusBadRequest, + }, + { + name: "saml certificate, ok", + args: args{ + ctx: CTX, + idpID: samlRedirectIdpID, + }, + want: http.StatusOK, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + certificateURL := http_util.BuildOrigin(Tester.Host(), Tester.Server.Config.ExternalSecure) + "/idps/" + tt.args.idpID + "/saml/certificate" + resp, err := http.Get(certificateURL) + assert.NoError(t, err) + assert.Equal(t, tt.want, resp.StatusCode) + if tt.want == http.StatusOK { + b, err := io.ReadAll(resp.Body) + defer resp.Body.Close() + assert.NoError(t, err) + + block, _ := pem.Decode(b) + _, err = x509.ParseCertificate(block.Bytes) + assert.NoError(t, err) + } + }) + } +} + +func TestServer_SAMLMetadata(t *testing.T) { + samlRedirectIdpID := Tester.AddSAMLRedirectProvider(t) + oauthIdpID := Tester.AddGenericOAuthProvider(t) + + type args struct { + ctx context.Context + idpID string + } + tests := []struct { + name string + args args + want int + }{ + { + name: "saml metadata, invalid idp", + args: args{ + ctx: CTX, + idpID: "unknown", + }, + want: http.StatusNotFound, + }, + { + name: "saml metadata, invalid idp type", + args: args{ + ctx: CTX, + idpID: oauthIdpID, + }, + want: http.StatusBadRequest, + }, + { + name: "saml metadata, ok", + args: args{ + ctx: CTX, + idpID: samlRedirectIdpID, + }, + want: http.StatusOK, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + metadataURL := http_util.BuildOrigin(Tester.Host(), Tester.Server.Config.ExternalSecure) + "/idps/" + tt.args.idpID + "/saml/metadata" + resp, err := http.Get(metadataURL) + assert.NoError(t, err) + assert.Equal(t, tt.want, resp.StatusCode) + if tt.want == http.StatusOK { + b, err := io.ReadAll(resp.Body) + defer resp.Body.Close() + assert.NoError(t, err) + + _, err = saml_xml.ParseMetadataXmlIntoStruct(b) + assert.NoError(t, err) + } + + }) + } +} + +func TestServer_SAMLACS(t *testing.T) { + userHuman := Tester.CreateHumanUser(CTX) + samlRedirectIdpID := Tester.AddSAMLRedirectProvider(t) + externalUserID := "test1" + linkedExternalUserID := "test2" + Tester.CreateUserIDPlink(CTX, userHuman.UserId, linkedExternalUserID, samlRedirectIdpID, linkedExternalUserID) + idp, err := getIDP( + http_util.BuildOrigin(Tester.Host(), Tester.Server.Config.ExternalSecure), + []string{samlRedirectIdpID}, + externalUserID, + linkedExternalUserID, + ) + assert.NoError(t, err) + + type args struct { + ctx context.Context + successURL string + failureURL string + idpID string + username string + intentID string + response string + } + type want struct { + successful bool + user string + } + tests := []struct { + name string + args args + want want + wantErr bool + }{ + { + name: "intent invalid", + args: args{ + ctx: CTX, + successURL: "https://example.com/success", + failureURL: "https://example.com/failure", + idpID: samlRedirectIdpID, + username: externalUserID, + intentID: "notexisting", + }, + want: want{ + successful: false, + user: "", + }, + wantErr: true, + }, + { + name: "response invalid", + args: args{ + ctx: CTX, + successURL: "https://example.com/success", + failureURL: "https://example.com/failure", + idpID: samlRedirectIdpID, + username: externalUserID, + response: "invalid", + }, + want: want{ + successful: false, + user: "", + }, + }, + { + name: "saml flow redirect, ok", + args: args{ + ctx: CTX, + successURL: "https://example.com/success", + failureURL: "https://example.com/failure", + idpID: samlRedirectIdpID, + username: externalUserID, + }, + want: want{ + successful: true, + user: "", + }, + }, + { + name: "saml flow redirect with link, ok", + args: args{ + ctx: CTX, + successURL: "https://example.com/success", + failureURL: "https://example.com/failure", + idpID: samlRedirectIdpID, + username: linkedExternalUserID, + }, + want: want{ + successful: true, + user: userHuman.UserId, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Client.StartIdentityProviderFlow(tt.args.ctx, + &user.StartIdentityProviderFlowRequest{ + IdpId: tt.args.idpID, + Content: &user.StartIdentityProviderFlowRequest_Urls{ + Urls: &user.RedirectURLs{ + SuccessUrl: tt.args.successURL, + FailureUrl: tt.args.failureURL, + }, + }, + }, + ) + // can't fail as covered in other tests + require.NoError(t, err) + + //parse returned URL to continue flow to callback with the same intentID==RelayState + authURL, err := url.Parse(got.GetAuthUrl()) + require.NoError(t, err) + samlRequest := &http.Request{Method: http.MethodGet, URL: authURL} + assert.NotEmpty(t, authURL) + + //generate necessary information to create request to callback URL + relayState := authURL.Query().Get("RelayState") + //test purposes, use defined intentID + if tt.args.intentID != "" { + relayState = tt.args.intentID + } + callbackURL := http_util.BuildOrigin(Tester.Host(), Tester.Server.Config.ExternalSecure) + "/idps/" + tt.args.idpID + "/saml/acs" + response := createResponse(t, idp, samlRequest, tt.args.username) + //test purposes, use defined response + if tt.args.response != "" { + response = tt.args.response + } + req := httpPostFormRequest(t, callbackURL, relayState, response) + + //do request to callback URL and check redirect to either success or failure url + location, err := integration.CheckRedirect(req) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + assert.Equal(t, relayState, location.Query().Get("id")) + if tt.want.successful { + assert.True(t, strings.HasPrefix(location.String(), tt.args.successURL)) + assert.NotEmpty(t, location.Query().Get("token")) + assert.Equal(t, tt.want.user, location.Query().Get("user")) + } else { + assert.True(t, strings.HasPrefix(location.String(), tt.args.failureURL)) + } + } + }) + } +} + +var key = func() crypto.PrivateKey { + b, _ := pem.Decode([]byte(`-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA0OhbMuizgtbFOfwbK7aURuXhZx6VRuAs3nNibiuifwCGz6u9 +yy7bOR0P+zqN0YkjxaokqFgra7rXKCdeABmoLqCC0U+cGmLNwPOOA0PaD5q5xKhQ +4Me3rt/R9C4Ca6k3/OnkxnKwnogcsmdgs2l8liT3qVHP04Oc7Uymq2v09bGb6nPu +fOrkXS9F6mSClxHG/q59AGOWsXK1xzIRV1eu8W2SNdyeFVU1JHiQe444xLoPul5t +InWasKayFsPlJfWNc8EoU8COjNhfo/GovFTHVjh9oUR/gwEFVwifIHihRE0Hazn2 +EQSLaOr2LM0TsRsQroFjmwSGgI+X2bfbMTqWOQIDAQABAoIBAFWZwDTeESBdrLcT +zHZe++cJLxE4AObn2LrWANEv5AeySYsyzjRBYObIN9IzrgTb8uJ900N/zVr5VkxH +xUa5PKbOcowd2NMfBTw5EEnaNbILLm+coHdanrNzVu59I9TFpAFoPavrNt/e2hNo +NMGPSdOkFi81LLl4xoadz/WR6O/7N2famM+0u7C2uBe+TrVwHyuqboYoidJDhO8M +w4WlY9QgAUhkPyzZqrl+VfF1aDTGVf4LJgaVevfFCas8Ws6DQX5q4QdIoV6/0vXi +B1M+aTnWjHuiIzjBMWhcYW2+I5zfwNWRXaxdlrYXRukGSdnyO+DH/FhHePJgmlkj +NInADDkCgYEA6MEQFOFSCc/ELXYWgStsrtIlJUcsLdLBsy1ocyQa2lkVUw58TouW +RciE6TjW9rp31pfQUnO2l6zOUC6LT9Jvlb9PSsyW+rvjtKB5PjJI6W0hjX41wEO6 +fshFELMJd9W+Ezao2AsP2hZJ8McCF8no9e00+G4xTAyxHsNI2AFTCQcCgYEA5cWZ +JwNb4t7YeEajPt9xuYNUOQpjvQn1aGOV7KcwTx5ELP/Hzi723BxHs7GSdrLkkDmi +Gpb+mfL4wxCt0fK0i8GFQsRn5eusyq9hLqP/bmjpHoXe/1uajFbE1fZQR+2LX05N +3ATlKaH2hdfCJedFa4wf43+cl6Yhp6ZA0Yet1r8CgYEAwiu1j8W9G+RRA5/8/DtO +yrUTOfsbFws4fpLGDTA0mq0whf6Soy/96C90+d9qLaC3srUpnG9eB0CpSOjbXXbv +kdxseLkexwOR3bD2FHX8r4dUM2bzznZyEaxfOaQypN8SV5ME3l60Fbr8ajqLO288 +wlTmGM5Mn+YCqOg/T7wjGmcCgYBpzNfdl/VafOROVbBbhgXWtzsz3K3aYNiIjbp+ +MunStIwN8GUvcn6nEbqOaoiXcX4/TtpuxfJMLw4OvAJdtxUdeSmEee2heCijV6g3 +ErrOOy6EqH3rNWHvlxChuP50cFQJuYOueO6QggyCyruSOnDDuc0BM0SGq6+5g5s7 +H++S/wKBgQDIkqBtFr9UEf8d6JpkxS0RXDlhSMjkXmkQeKGFzdoJcYVFIwq8jTNB +nJrVIGs3GcBkqGic+i7rTO1YPkquv4dUuiIn+vKZVoO6b54f+oPBXd4S0BnuEqFE +rdKNuCZhiaE2XD9L/O9KP1fh5bfEcKwazQ23EvpJHBMm8BGC+/YZNw== +-----END RSA PRIVATE KEY-----`)) + k, _ := x509.ParsePKCS1PrivateKey(b.Bytes) + return k +}() + +var cert = func() *x509.Certificate { + b, _ := pem.Decode([]byte(`-----BEGIN CERTIFICATE----- +MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV +BAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5 +NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8A +hs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+a +ucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWx +m+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6 +D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURN +B2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0O +BBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56 +zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5 +pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uv +NONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEf +y/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL +/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsb +GFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTL +UzreO96WzlBBMtY= +-----END CERTIFICATE-----`)) + c, _ := x509.ParseCertificate(b.Bytes) + return c +}() + +func getIDP(zitadelBaseURL string, idpIDs []string, user1, user2 string) (*saml.IdentityProvider, error) { + baseURL, err := url.Parse("http://localhost:8000") + if err != nil { + return nil, err + } + + store := &samlidp.MemoryStore{} + hashedPassword1, _ := bcrypt.GenerateFromPassword([]byte("test"), bcrypt.DefaultCost) + err = store.Put("/users/"+user1, samlidp.User{ + Name: user1, + HashedPassword: hashedPassword1, + Groups: []string{"Administrators", "Users"}, + Email: "test@example.com", + CommonName: "Test Test", + Surname: "Test", + GivenName: "Test", + }) + if err != nil { + return nil, err + } + hashedPassword2, _ := bcrypt.GenerateFromPassword([]byte("test"), bcrypt.DefaultCost) + err = store.Put("/users/"+user2, samlidp.User{ + Name: user2, + HashedPassword: hashedPassword2, + Groups: []string{"Administrators", "Users"}, + Email: "test@example.com", + CommonName: "Test Test", + Surname: "Test", + GivenName: "Test", + }) + if err != nil { + return nil, err + } + for _, idpID := range idpIDs { + metadata, err := saml_xml.ReadMetadataFromURL(http.DefaultClient, zitadelBaseURL+"/idps/"+idpID+"/saml/metadata") + if err != nil { + return nil, err + } + entity := new(saml.EntityDescriptor) + if err := xml.Unmarshal(metadata, entity); err != nil { + return nil, err + } + + if err := store.Put("/services/"+idpID, samlidp.Service{ + Name: idpID, + Metadata: *entity, + }); err != nil { + return nil, err + } + } + + idpServer, err := samlidp.New(samlidp.Options{ + URL: *baseURL, + Key: key, + Certificate: cert, + Store: store, + }) + if err != nil { + return nil, err + } + if idpServer.IDP.AssertionMaker == nil { + idpServer.IDP.AssertionMaker = &saml.DefaultAssertionMaker{} + } + return &idpServer.IDP, nil +} + +func createResponse(t *testing.T, idp *saml.IdentityProvider, req *http.Request, username string) string { + authnReq, err := saml.NewIdpAuthnRequest(idp, req) + assert.NoError(t, authnReq.Validate()) + + err = idp.AssertionMaker.MakeAssertion(authnReq, &saml.Session{ + CreateTime: time.Now().UTC(), + Index: "", + NameID: username, + }) + assert.NoError(t, err) + err = authnReq.MakeResponse() + assert.NoError(t, err) + + doc := etree.NewDocument() + doc.SetRoot(authnReq.ResponseEl) + responseBuf, err := doc.WriteToBytes() + assert.NoError(t, err) + responseBuf = append([]byte(""), responseBuf...) + + return base64.StdEncoding.EncodeToString(responseBuf) +} + +func httpGETRequest(t *testing.T, callbackURL string, relayState, response, sig, sigAlg string) *http.Request { + req, err := http.NewRequest("GET", callbackURL, nil) + require.NoError(t, err) + + q := req.URL.Query() + q.Add("RelayState", relayState) + q.Add("SAMLResponse", response) + if sig != "" { + q.Add("Sig", sig) + } + if sigAlg != "" { + q.Add("SigAlg", sigAlg) + } + req.URL.RawQuery = q.Encode() + return req +} + +func httpPostFormRequest(t *testing.T, callbackURL, relayState, response string) *http.Request { + body := url.Values{ + "SAMLResponse": {response}, + "RelayState": {relayState}, + } + + req, err := http.NewRequest("POST", callbackURL, strings.NewReader(body.Encode())) + assert.NoError(t, err) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.ParseForm() + return req +} diff --git a/internal/api/oidc/op.go b/internal/api/oidc/op.go index ea630482c7..da96302e60 100644 --- a/internal/api/oidc/op.go +++ b/internal/api/oidc/op.go @@ -109,7 +109,7 @@ func NewProvider( if err != nil { return nil, caos_errs.ThrowInternal(err, "OIDC-D3gq1", "cannot create options: %w") } - provider, err := op.NewDynamicOpenIDProvider( + provider, err := op.NewForwardedOpenIDProvider( "", opConfig, storage, diff --git a/internal/api/ui/login/external_provider_handler.go b/internal/api/ui/login/external_provider_handler.go index 475798e83e..c9cc94203f 100644 --- a/internal/api/ui/login/external_provider_handler.go +++ b/internal/api/ui/login/external_provider_handler.go @@ -5,6 +5,7 @@ import ( "net/http" "strings" + "github.com/crewjam/saml/samlsp" "github.com/zitadel/logging" "github.com/zitadel/oidc/v2/pkg/client/rp" "github.com/zitadel/oidc/v2/pkg/oidc" @@ -12,6 +13,7 @@ import ( "golang.org/x/text/language" "github.com/zitadel/zitadel/internal/api/authz" + http_utils "github.com/zitadel/zitadel/internal/api/http" http_mw "github.com/zitadel/zitadel/internal/api/http/middleware" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" @@ -27,6 +29,8 @@ import ( "github.com/zitadel/zitadel/internal/idp/providers/ldap" "github.com/zitadel/zitadel/internal/idp/providers/oauth" openid "github.com/zitadel/zitadel/internal/idp/providers/oidc" + "github.com/zitadel/zitadel/internal/idp/providers/saml" + "github.com/zitadel/zitadel/internal/idp/providers/saml/requesttracker" "github.com/zitadel/zitadel/internal/query" ) @@ -43,6 +47,9 @@ type externalIDPCallbackData struct { State string `schema:"state"` Code string `schema:"code"` + RelayState string `schema:"RelayState"` + Method string `schema:"Method"` + // Apple returns a user on first registration User string `schema:"user"` } @@ -167,6 +174,8 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai provider, err = l.appleProvider(r.Context(), identityProvider) case domain.IDPTypeLDAP: provider, err = l.ldapProvider(r.Context(), identityProvider) + case domain.IDPTypeSAML: + provider, err = l.samlProvider(r.Context(), identityProvider) case domain.IDPTypeUnspecified: fallthrough default: @@ -183,7 +192,17 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai l.renderLogin(w, r, authReq, err) return } - http.Redirect(w, r, session.GetAuthURL(), http.StatusFound) + + content, redirect := session.GetAuth(r.Context()) + if redirect { + http.Redirect(w, r, content, http.StatusFound) + return + } + _, err = w.Write([]byte(content)) + if err != nil { + l.renderError(w, r, authReq, err) + return + } } // handleExternalLoginCallbackForm handles the callback from a IDP with form_post. @@ -195,6 +214,7 @@ func (l *Login) handleExternalLoginCallbackForm(w http.ResponseWriter, r *http.R l.renderLogin(w, r, nil, err) return } + r.Form.Add("Method", http.MethodPost) http.Redirect(w, r, HandlerPrefix+EndpointExternalLoginCallback+"?"+r.Form.Encode(), 302) } @@ -207,6 +227,15 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque l.renderLogin(w, r, nil, err) return } + if data.State == "" { + data.State = data.RelayState + } + // workaround because of CSRF on external identity provider flows + if data.Method == http.MethodPost { + r.Method = http.MethodPost + r.PostForm = r.Form + } + userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) authReq, err := l.authRepo.AuthRequestByID(r.Context(), data.State, userAgentID) if err != nil { @@ -284,6 +313,18 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque return } session = &apple.Session{Session: &openid.Session{Provider: provider.(*apple.Provider).Provider, Code: data.Code}, UserFormValue: data.User} + case domain.IDPTypeSAML: + provider, err = l.samlProvider(r.Context(), identityProvider) + if err != nil { + l.externalAuthFailed(w, r, authReq, nil, nil, err) + return + } + sp, err := provider.(*saml.Provider).GetSP() + if err != nil { + l.externalAuthFailed(w, r, authReq, nil, nil, err) + return + } + session = &saml.Session{ServiceProvider: sp, RequestID: authReq.SAMLRequestID, Request: r} case domain.IDPTypeJWT, domain.IDPTypeLDAP, domain.IDPTypeUnspecified: @@ -881,6 +922,49 @@ func (l *Login) oauthProvider(ctx context.Context, identityProvider *query.IDPTe ) } +func (l *Login) samlProvider(ctx context.Context, identityProvider *query.IDPTemplate) (*saml.Provider, error) { + key, err := crypto.Decrypt(identityProvider.SAMLIDPTemplate.Key, l.idpConfigAlg) + if err != nil { + return nil, err + } + opts := make([]saml.ProviderOpts, 0, 2) + if identityProvider.SAMLIDPTemplate.WithSignedRequest { + opts = append(opts, saml.WithSignedRequest()) + } + if identityProvider.SAMLIDPTemplate.Binding != "" { + opts = append(opts, saml.WithBinding(identityProvider.SAMLIDPTemplate.Binding)) + } + opts = append(opts, + saml.WithEntityID(http_utils.BuildOrigin(authz.GetInstance(ctx).RequestedHost(), l.externalSecure)+"/idps/"+identityProvider.ID+"/saml/metadata"), + saml.WithCustomRequestTracker( + requesttracker.New( + func(ctx context.Context, authRequestID, samlRequestID string) error { + useragent, _ := http_mw.UserAgentIDFromCtx(ctx) + return l.authRepo.SaveSAMLRequestID(ctx, authRequestID, samlRequestID, useragent) + }, + func(ctx context.Context, authRequestID string) (*samlsp.TrackedRequest, error) { + useragent, _ := http_mw.UserAgentIDFromCtx(ctx) + auhRequest, err := l.authRepo.AuthRequestByID(ctx, authRequestID, useragent) + if err != nil { + return nil, err + } + return &samlsp.TrackedRequest{ + SAMLRequestID: auhRequest.SAMLRequestID, + Index: authRequestID, + }, nil + }, + ), + )) + return saml.New( + identityProvider.Name, + l.baseURL(ctx)+EndpointExternalLogin+"/", + identityProvider.SAMLIDPTemplate.Metadata, + identityProvider.SAMLIDPTemplate.Certificate, + key, + opts..., + ) +} + func (l *Login) azureProvider(ctx context.Context, identityProvider *query.IDPTemplate) (*azuread.Provider, error) { secret, err := crypto.DecryptString(identityProvider.AzureADIDPTemplate.ClientSecret, l.idpConfigAlg) if err != nil { diff --git a/internal/api/ui/login/login.go b/internal/api/ui/login/login.go index c98fbbff46..06a7787b59 100644 --- a/internal/api/ui/login/login.go +++ b/internal/api/ui/login/login.go @@ -11,6 +11,7 @@ import ( "github.com/gorilla/mux" "github.com/rakyll/statik/fs" + "github.com/zitadel/zitadel/feature" "github.com/zitadel/zitadel/internal/api/authz" http_utils "github.com/zitadel/zitadel/internal/api/http" "github.com/zitadel/zitadel/internal/api/http/middleware" @@ -40,6 +41,7 @@ type Login struct { samlAuthCallbackURL func(context.Context, string) string idpConfigAlg crypto.EncryptionAlgorithm userCodeAlg crypto.EncryptionAlgorithm + featureCheck feature.Checker } type Config struct { @@ -76,6 +78,7 @@ func CreateLogin(config Config, userCodeAlg crypto.EncryptionAlgorithm, idpConfigAlg crypto.EncryptionAlgorithm, csrfCookieKey []byte, + featureCheck feature.Checker, ) (*Login, error) { login := &Login{ oidcAuthCallbackURL: oidcAuthCallbackURL, @@ -88,6 +91,7 @@ func CreateLogin(config Config, authRepo: authRepo, idpConfigAlg: idpConfigAlg, userCodeAlg: userCodeAlg, + featureCheck: featureCheck, } statikFS, err := fs.NewWithNamespace("login") if err != nil { @@ -122,7 +126,7 @@ func createCSRFInterceptor(cookieName string, csrfCookieKey []byte, externalSecu } // ignore form post callback // it will redirect to the "normal" callback, where the cookie is set again - if r.URL.Path == EndpointExternalLoginCallbackFormPost && r.Method == http.MethodPost { + if (r.URL.Path == EndpointExternalLoginCallbackFormPost || r.URL.Path == EndpointSAMLACS) && r.Method == http.MethodPost { handler.ServeHTTP(w, r) return } diff --git a/internal/api/ui/login/renderer.go b/internal/api/ui/login/renderer.go index 29fed4eda6..85d54cd015 100644 --- a/internal/api/ui/login/renderer.go +++ b/internal/api/ui/login/renderer.go @@ -506,25 +506,19 @@ func (l *Login) getOrgID(r *http.Request, authReq *domain.AuthRequest) string { } func (l *Login) getPrivateLabelingID(r *http.Request, authReq *domain.AuthRequest) string { - privateLabelingOrgID := authz.GetInstance(r.Context()).InstanceID() - if authReq == nil { - if id := r.FormValue(queryOrgID); id != "" { - return id - } - return privateLabelingOrgID + defaultID := authz.GetInstance(r.Context()).DefaultOrganisationID() + f, err := l.featureCheck.CheckInstanceBooleanFeature(r.Context(), domain.FeatureLoginDefaultOrg) + logging.OnError(err).Warnf("could not check feature %s", domain.FeatureLoginDefaultOrg) + if !f.Boolean { + defaultID = authz.GetInstance(r.Context()).InstanceID() } - if authReq.PrivateLabelingSetting != domain.PrivateLabelingSettingUnspecified { - privateLabelingOrgID = authReq.ApplicationResourceOwner + if authReq != nil { + return authReq.PrivateLabelingOrgID(defaultID) } - if authReq.PrivateLabelingSetting == domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy || authReq.PrivateLabelingSetting == domain.PrivateLabelingSettingUnspecified { - if authReq.UserOrgID != "" { - privateLabelingOrgID = authReq.UserOrgID - } + if id := r.FormValue(queryOrgID); id != "" { + return id } - if authReq.RequestedOrgID != "" { - privateLabelingOrgID = authReq.RequestedOrgID - } - return privateLabelingOrgID + return defaultID } func (l *Login) getOrgName(authReq *domain.AuthRequest) string { diff --git a/internal/api/ui/login/router.go b/internal/api/ui/login/router.go index 1bef0debd5..34a0092607 100644 --- a/internal/api/ui/login/router.go +++ b/internal/api/ui/login/router.go @@ -14,6 +14,7 @@ const ( EndpointExternalLogin = "/login/externalidp" EndpointExternalLoginCallback = "/login/externalidp/callback" EndpointExternalLoginCallbackFormPost = "/login/externalidp/callback/form" + EndpointSAMLACS = "/login/externalidp/saml/acs" EndpointJWTAuthorize = "/login/jwt/authorize" EndpointJWTCallback = "/login/jwt/callback" EndpointLDAPLogin = "/login/ldap" @@ -73,6 +74,8 @@ func CreateRouter(login *Login, staticDir http.FileSystem, interceptors ...mux.M router.HandleFunc(EndpointExternalLogin, login.handleExternalLogin).Methods(http.MethodGet) router.HandleFunc(EndpointExternalLoginCallback, login.handleExternalLoginCallback).Methods(http.MethodGet) router.HandleFunc(EndpointExternalLoginCallbackFormPost, login.handleExternalLoginCallbackForm).Methods(http.MethodPost) + router.HandleFunc(EndpointSAMLACS, login.handleExternalLoginCallback).Methods(http.MethodGet) + router.HandleFunc(EndpointSAMLACS, login.handleExternalLoginCallbackForm).Methods(http.MethodPost) router.HandleFunc(EndpointJWTAuthorize, login.handleJWTRequest).Methods(http.MethodGet) router.HandleFunc(EndpointJWTCallback, login.handleJWTCallback).Methods(http.MethodGet) router.HandleFunc(EndpointPasswordlessLogin, login.handlePasswordlessVerification).Methods(http.MethodPost) diff --git a/internal/api/ui/login/static/i18n/bg.yaml b/internal/api/ui/login/static/i18n/bg.yaml index 8e834db180..7aafdc7499 100644 --- a/internal/api/ui/login/static/i18n/bg.yaml +++ b/internal/api/ui/login/static/i18n/bg.yaml @@ -415,7 +415,7 @@ Errors: InvalidAndLocked: >- Паролата е невалидна и потребителят е заключен, свържете се с вашия администратор. - NotChanged: Паролата не е променена + NotChanged: Новата парола не може да съвпада с текущата парола UsernameOrPassword: Invalid: Потребителското име или паролата са невалидни PasswordComplexityPolicy: diff --git a/internal/api/ui/login/static/i18n/de.yaml b/internal/api/ui/login/static/i18n/de.yaml index 921026a1a9..e69c4ab90c 100644 --- a/internal/api/ui/login/static/i18n/de.yaml +++ b/internal/api/ui/login/static/i18n/de.yaml @@ -425,7 +425,7 @@ Errors: Empty: Passwort ist leer Invalid: Passwort ungültig InvalidAndLocked: Passwort ist ungültig und Benutzer wurde gesperrt, wende dich an einen Administrator. - NotChanged: Passwort nicht geändert + NotChanged: Das neue Passwort darf nicht mit deinem aktuellen Passwort übereinstimmen UsernameOrPassword: Invalid: Benutzername oder Passwort ist ungültig PasswordComplexityPolicy: diff --git a/internal/api/ui/login/static/i18n/en.yaml b/internal/api/ui/login/static/i18n/en.yaml index 99958d7563..4c5dc2b0a4 100644 --- a/internal/api/ui/login/static/i18n/en.yaml +++ b/internal/api/ui/login/static/i18n/en.yaml @@ -1,25 +1,25 @@ Login: - Title: Welcome back! + Title: Welcome Back! Description: Enter your login data. TitleLinking: Login for user linking DescriptionLinking: Enter your login data to link your external user. - LoginNameLabel: Loginname + LoginNameLabel: Login Name UsernamePlaceHolder: username LoginnamePlaceHolder: username@domain ExternalUserDescription: Login with an external user. MustBeMemberOfOrg: The user must be member of the {{.OrgName}} organization. - RegisterButtonText: register - NextButtonText: next + RegisterButtonText: Register + NextButtonText: Next LDAP: Title: Login Description: Enter your login data. - LoginNameLabel: Loginname + LoginNameLabel: Login Name PasswordLabel: Password - NextButtonText: next + NextButtonText: Next SelectAccount: - Title: Select account + Title: Select Account Description: Use your account TitleLinking: Select account for user linking DescriptionLinking: Select your account to link with your external user. @@ -38,21 +38,21 @@ Password: HasNumber: Number HasSymbol: Symbol Confirmation: Confirmation match - ResetLinkText: reset password - BackButtonText: back - NextButtonText: next + ResetLinkText: Reset Password + BackButtonText: Back + NextButtonText: Next UsernameChange: Title: Change Username Description: Set your new username UsernameLabel: Username - CancelButtonText: cancel - NextButtonText: next + CancelButtonText: Cancel + NextButtonText: Next UsernameChangeDone: - Title: Username changed + Title: Username Changed Description: Your username was changed successfully. - NextButtonText: next + NextButtonText: Next InitPassword: Title: Set Password @@ -60,14 +60,14 @@ InitPassword: CodeLabel: Code NewPasswordLabel: New Password NewPasswordConfirmLabel: Confirm Password - ResendButtonText: resend code - NextButtonText: next + ResendButtonText: Resend Code + NextButtonText: Next InitPasswordDone: - Title: Password set + Title: Password Set Description: Password successfully set - NextButtonText: next - CancelButtonText: cancel + NextButtonText: Next + CancelButtonText: Cancel InitUser: Title: Activate User @@ -75,14 +75,14 @@ InitUser: CodeLabel: Code NewPasswordLabel: New Password NewPasswordConfirm: Confirm Password - NextButtonText: next - ResendButtonText: resend code + NextButtonText: Next + ResendButtonText: Resend Code InitUserDone: - Title: User activated + Title: User Activated Description: Email verified and Password successfully set - NextButtonText: next - CancelButtonText: cancel + NextButtonText: Next + CancelButtonText: Cancel InitMFAPrompt: Title: 2-Factor Setup @@ -91,8 +91,8 @@ InitMFAPrompt: Provider1: Device dependent (e.g FaceID, Windows Hello, Fingerprint) Provider3: OTP SMS Provider4: OTP Email - NextButtonText: next - SkipButtonText: skip + NextButtonText: Next + SkipButtonText: Skip InitMFAOTP: Title: 2-Factor Verification @@ -100,8 +100,8 @@ InitMFAOTP: OTPDescription: Scan the code with your authenticator app (e.g Google/Microsoft Authenticator, Authy) or copy the secret and insert the generated code below. SecretLabel: Secret CodeLabel: Code - NextButtonText: next - CancelButtonText: cancel + NextButtonText: Next + CancelButtonText: Cancel InitMFAOTPSMS: Title: 2-Factor Verification @@ -109,12 +109,12 @@ InitMFAOTPSMS: DescriptionCode: Create your 2-factor. Enter the received code to verify your phone number. PhoneLabel: Phone CodeLabel: Code - EditButtonText: edit - ResendButtonText: resend code - NextButtonText: next + EditButtonText: Edit + ResendButtonText: Resend Code + NextButtonText: Next InitMFAU2F: - Title: Add security key + Title: Add Security Key Description: A security key is a verification method that can be built into your phone, use Bluetooth, or plug directly into your computer's USB port. TokenNameLabel: Name of the security key / device NotSupported: WebAuthN is not supported by your browser. Please ensure it is up to date or use a different one (e.g. Chrome, Safari, Firefox) @@ -122,10 +122,10 @@ InitMFAU2F: ErrorRetry: Retry, create a new challenge or choose a different method. InitMFADone: - Title: 2-factor verified + Title: 2-factor Verified Description: Awesome! You just successfully set up your 2-factor and made your account way more secure. The Factor has to be entered on each login. - NextButtonText: next - CancelButtonText: cancel + NextButtonText: Next + CancelButtonText: Cancel MFAProvider: Provider0: Authenticator App (e.g Google/Microsoft Authenticator, Authy) @@ -138,14 +138,14 @@ VerifyMFAOTP: Title: Verify 2-Factor Description: Verify your second factor CodeLabel: Code - NextButtonText: next + NextButtonText: Next VerifyOTP: Title: Verify 2-Factor Description: Verify your second factor CodeLabel: Code - ResendButtonText: resend code - NextButtonText: next + ResendButtonText: Resend Code + NextButtonText: Next VerifyMFAU2F: Title: 2-Factor Verification @@ -155,7 +155,7 @@ VerifyMFAU2F: ValidateTokenButtonText: Verify 2-Factor Passwordless: - Title: Login passwordless + Title: Login Passwordless Description: Login with authentication methods provided by your device like FaceID, Windows Hello or Fingerprint. NotSupported: WebAuthN is not supported by your browser. Please ensure it is up to date or use a different one (e.g. Chrome, Safari, Firefox) ErrorRetry: Retry, create a new challenge or choose a different method. @@ -163,15 +163,15 @@ Passwordless: ValidateTokenButtonText: Login with passwordless PasswordlessPrompt: - Title: Passwordless setup + Title: Passwordless Setup Description: Would you like to setup passwordless login? (Authentication methods of your device like FaceID, Windows Hello or Fingerprint) DescriptionInit: You need to set up passwordless login. Use the link you were given to register your device. PasswordlessButtonText: Go passwordless - NextButtonText: next - SkipButtonText: skip + NextButtonText: Next + SkipButtonText: Skip PasswordlessRegistration: - Title: Passwordless setup + Title: Passwordless Setup Description: Add your authentication by providing a name (e.g MyMobilePhone, MacBook, etc) and then clicking on the 'Register passwordless' button below. TokenNameLabel: Name of the device NotSupported: WebAuthN is not supported by your browser. Please ensure it is up to date or use a different one (e.g. Chrome, Safari, Firefox) @@ -179,11 +179,11 @@ PasswordlessRegistration: ErrorRetry: Retry, create a new challenge or choose a different method. PasswordlessRegistrationDone: - Title: Passwordless set up + Title: Passwordless Set Up Description: Device for passwordless successfully added. - DescriptionClose: You can now close this window. - NextButtonText: next - CancelButtonText: cancel + DescriptionClose: You may now close this window. + NextButtonText: Next + CancelButtonText: Cancel PasswordChange: Title: Change Password @@ -191,44 +191,44 @@ PasswordChange: OldPasswordLabel: Old Password NewPasswordLabel: New Password NewPasswordConfirmLabel: Password confirmation - CancelButtonText: cancel - NextButtonText: next + CancelButtonText: Cancel + NextButtonText: Next Footer: Footer PasswordChangeDone: Title: Change Password Description: Your password was changed successfully. - NextButtonText: next + NextButtonText: Next PasswordResetDone: - Title: Password reset link sent + Title: Password Reset Link Sent Description: Check your email to reset your password. - NextButtonText: next + NextButtonText: Next EmailVerification: Title: E-Mail Verification Description: We have sent you an email to verify your address. Please enter the code in the form below. CodeLabel: Code - NextButtonText: next - ResendButtonText: resend code + NextButtonText: Next + ResendButtonText: Resend Code EmailVerificationDone: Title: E-Mail Verification Description: Your email address has been successfully verified. - NextButtonText: next - CancelButtonText: cancel - LoginButtonText: login + NextButtonText: Next + CancelButtonText: Cancel + LoginButtonText: Login RegisterOption: Title: Registration Options Description: Choose how you'd like to register - RegisterUsernamePasswordButtonText: With username password + RegisterUsernamePasswordButtonText: With username and password ExternalLoginDescription: or register with an external user - LoginButtonText: login + LoginButtonText: Login RegistrationUser: Title: Registration - Description: Enter your Userdata. Your email address will be used as loginname. + Description: Enter your Userdata. Your email address will be used as your login name. DescriptionOrgRegister: Enter your Userdata. EmailLabel: E-Mail UsernameLabel: Username @@ -258,18 +258,18 @@ RegistrationUser: PrivacyConfirm: I accept the PrivacyLinkText: privacy policy ExternalLogin: or register with an external user - BackButtonText: login - NextButtonText: next + BackButtonText: Login + NextButtonText: Next ExternalRegistrationUserOverview: Title: External User Registration - Description: We have taken your user details from the selected provider. You can now change or complete them. + Description: We have taken your user details from the selected provider. You may now change or complete them. EmailLabel: E-Mail UsernameLabel: Username FirstnameLabel: Given name LastnameLabel: Family name NicknameLabel: Nickname - PhoneLabel: Phonenumber + PhoneLabel: Phone number LanguageLabel: Language German: Deutsch English: English @@ -288,8 +288,8 @@ ExternalRegistrationUserOverview: PrivacyConfirm: I accept the PrivacyLinkText: privacy policy ExternalLogin: or register with an external user - BackButtonText: back - NextButtonText: save + BackButtonText: Nack + NextButtonText: Save RegistrationOrg: Title: Organization Registration @@ -309,27 +309,27 @@ RegistrationOrg: SaveButtonText: Create organization LoginSuccess: - Title: Login successful - AutoRedirectDescription: You will be directed back to your application automatically. If not, click on the button below. You can close the window afterwards. - RedirectedDescription: You can now close this window. - NextButtonText: next + Title: Login Successful + AutoRedirectDescription: You will be directed back to your application automatically. If not, click on the button below. You may close the window afterwards. + RedirectedDescription: You may now close this window. + NextButtonText: Next LogoutDone: - Title: Logged out + Title: Logged Out Description: You have logged out successfully. - LoginButtonText: login + LoginButtonText: Login LinkingUsersDone: - Title: Userlinking - Description: Userlinking done. - CancelButtonText: cancel - NextButtonText: next + Title: Linking User + Description: User linked. + CancelButtonText: Cancel + NextButtonText: Next ExternalNotFound: - Title: External User not found + Title: External User Not Found Description: External user not found. Do you want to link your user or auto register a new one. LinkButtonText: Link - AutoRegisterButtonText: register + AutoRegisterButtonText: Register TosAndPrivacyLabel: Terms and conditions TosConfirm: I accept the TosLinkText: TOS @@ -352,18 +352,18 @@ DeviceAuth: UserCode: Label: User Code Description: Enter the user code presented on the device. - ButtonNext: next + ButtonNext: Next Action: Description: Grant device access. GrantDevice: you are about to grant device AccessToScopes: access to the following scopes Button: - Allow: allow - Deny: deny + Allow: Allow + Deny: Deny Done: Description: Done. - Approved: Device authorization approved. You can now return to the device. - Denied: Device authorization denied. You can now return to the device. + Approved: Device authorization approved. You may now return to the device. + Denied: Device authorization denied. You may now return to the device. Footer: PoweredBy: Powered By @@ -426,7 +426,7 @@ Errors: Empty: Password is empty Invalid: Password is invalid InvalidAndLocked: Password is invalid and user is locked, contact your administrator. - NotChanged: Password not changed + NotChanged: New password cannot be the same as your current password UsernameOrPassword: Invalid: Username or Password is invalid PasswordComplexityPolicy: diff --git a/internal/api/ui/login/static/i18n/es.yaml b/internal/api/ui/login/static/i18n/es.yaml index 98847b67a8..0e9ea71e46 100644 --- a/internal/api/ui/login/static/i18n/es.yaml +++ b/internal/api/ui/login/static/i18n/es.yaml @@ -408,7 +408,7 @@ Errors: Empty: La contraseña está vacía Invalid: La contraseña no es válida InvalidAndLocked: La contraseña no es válida y el usuario está bloqueado, contacta con tu administrador. - NotChanged: Contraseña no modificada + NotChanged: La nueva contraseña no puede coincidir con la contraseña actual UsernameOrPassword: Invalid: El nombre de usuario o la contraseña no son válidos PasswordComplexityPolicy: diff --git a/internal/api/ui/login/static/i18n/fr.yaml b/internal/api/ui/login/static/i18n/fr.yaml index ebc87dfebe..bc3d02d4e7 100644 --- a/internal/api/ui/login/static/i18n/fr.yaml +++ b/internal/api/ui/login/static/i18n/fr.yaml @@ -426,7 +426,7 @@ Errors: Empty: Le mot de passe est vide Invalid: Le mot de passe n'est pas valide InvalidAndLocked: Le mot de passe n'est pas valide et l'utilisateur est verrouillé, contactez votre administrateur. - NotChanged: Mot de passe non modifié + NotChanged: Le nouveau mot de passe ne peut pas être le même que votre mot de passe actuel UsernameOrPassword: Invalid: Le nom d'utilisateur ou le mot de passe n'est pas valide PasswordComplexityPolicy: diff --git a/internal/api/ui/login/static/i18n/it.yaml b/internal/api/ui/login/static/i18n/it.yaml index 1fffcee7fc..3e668e5497 100644 --- a/internal/api/ui/login/static/i18n/it.yaml +++ b/internal/api/ui/login/static/i18n/it.yaml @@ -426,7 +426,7 @@ Errors: Empty: La password è vuota Invalid: La password non è valida InvalidAndLocked: La password non è valida e l'utente è bloccato, contatta il tuo amministratore. - NotChanged: Password non modificata + NotChanged: La nuova password non può essere uguale alla password attuale UsernameOrPassword: Invalid: Il nome utente o la password non sono validi PasswordComplexityPolicy: diff --git a/internal/api/ui/login/static/i18n/ja.yaml b/internal/api/ui/login/static/i18n/ja.yaml index fe58a191f2..91772bcd86 100644 --- a/internal/api/ui/login/static/i18n/ja.yaml +++ b/internal/api/ui/login/static/i18n/ja.yaml @@ -389,7 +389,7 @@ Errors: Empty: パスワードが空です Invalid: 無効なパスワードです InvalidAndLocked: パスワードが無効かつユーザーがロックされているため、管理者に連絡してください。 - NotChanged: パスワードは変更されていません + NotChanged: 新しいパスワードは現在のパスワードと同じにすることはできません UsernameOrPassword: Invalid: ユーザー名またはパスワードは無効です PasswordComplexityPolicy: diff --git a/internal/api/ui/login/static/i18n/mk.yaml b/internal/api/ui/login/static/i18n/mk.yaml index 6db9dea24a..74f9c8ddcb 100644 --- a/internal/api/ui/login/static/i18n/mk.yaml +++ b/internal/api/ui/login/static/i18n/mk.yaml @@ -426,7 +426,7 @@ Errors: Empty: Лозинката е празна Invalid: Лозинката не е валидна InvalidAndLocked: Лозинката не е валидна и корисникот е заклучен, контактирајте со вашиот администратор. - NotChanged: Лозинката не е променета + NotChanged: Новата лозинка не може да биде иста со вашата тековна лозинка UsernameOrPassword: Invalid: Корисничкото име и/или лозинката не се валидни PasswordComplexityPolicy: diff --git a/internal/api/ui/login/static/i18n/pl.yaml b/internal/api/ui/login/static/i18n/pl.yaml index 4cc7e222f6..747e64ae52 100644 --- a/internal/api/ui/login/static/i18n/pl.yaml +++ b/internal/api/ui/login/static/i18n/pl.yaml @@ -426,7 +426,7 @@ Errors: Empty: Hasło jest puste Invalid: Hasło jest niepoprawne InvalidAndLocked: Hasło jest niepoprawne i użytkownik jest zablokowany, skontaktuj się z administratorem. - NotChanged: Hasło nie zostało zmienione + NotChanged: Nowe hasło nie może być takie samo jak Twoje obecne hasło UsernameOrPassword: Invalid: Nazwa użytkownika lub hasło jest niepoprawne PasswordComplexityPolicy: diff --git a/internal/api/ui/login/static/i18n/pt.yaml b/internal/api/ui/login/static/i18n/pt.yaml index 9b50f7087c..40eb47e63f 100644 --- a/internal/api/ui/login/static/i18n/pt.yaml +++ b/internal/api/ui/login/static/i18n/pt.yaml @@ -420,7 +420,7 @@ Errors: Empty: A senha está vazia Invalid: A senha é inválida InvalidAndLocked: A senha é inválida e o usuário está bloqueado, entre em contato com o administrador. - NotChanged: Senha não alterada + NotChanged: A nova senha não pode ser igual à sua senha atual UsernameOrPassword: Invalid: Nome de usuário ou senha inválidos PasswordComplexityPolicy: diff --git a/internal/api/ui/login/static/i18n/zh.yaml b/internal/api/ui/login/static/i18n/zh.yaml index 1b02b36705..f251bd0c26 100644 --- a/internal/api/ui/login/static/i18n/zh.yaml +++ b/internal/api/ui/login/static/i18n/zh.yaml @@ -426,7 +426,7 @@ Errors: Empty: 密码为空 Invalid: 密码无效 InvalidAndLocked: 密码无效且用户被锁定,请联系您的管理员。 - NotChanged: 密码未更改 + NotChanged: 新密码不能与您当前的密码相同 UsernameOrPassword: Invalid: 用户名或密码无效 PasswordComplexityPolicy: diff --git a/internal/api/ui/login/static/resources/themes/scss/styles/input/input.scss b/internal/api/ui/login/static/resources/themes/scss/styles/input/input.scss index 5ec19e191a..6fe51dd8cc 100644 --- a/internal/api/ui/login/static/resources/themes/scss/styles/input/input.scss +++ b/internal/api/ui/login/static/resources/themes/scss/styles/input/input.scss @@ -20,7 +20,7 @@ select, top: 9px; height: inherit; vertical-align: middle; - max-width: 150px; + max-width: 200px; overflow-x: hidden; text-overflow: ellipsis; white-space: nowrap; diff --git a/internal/api/ui/login/static/resources/themes/scss/styles/mfa/mfa_base.scss b/internal/api/ui/login/static/resources/themes/scss/styles/mfa/mfa_base.scss index b53804641e..2bf2a71aa1 100644 --- a/internal/api/ui/login/static/resources/themes/scss/styles/mfa/mfa_base.scss +++ b/internal/api/ui/login/static/resources/themes/scss/styles/mfa/mfa_base.scss @@ -17,6 +17,8 @@ left: 0; right: 0; bottom: 0; + width: 100%; + height: 100%; } label { diff --git a/internal/auth/repository/auth_request.go b/internal/auth/repository/auth_request.go index db22705050..d101a436ff 100644 --- a/internal/auth/repository/auth_request.go +++ b/internal/auth/repository/auth_request.go @@ -12,6 +12,7 @@ type AuthRequestRepository interface { AuthRequestByIDCheckLoggedIn(ctx context.Context, id, userAgentID string) (*domain.AuthRequest, error) AuthRequestByCode(ctx context.Context, code string) (*domain.AuthRequest, error) SaveAuthCode(ctx context.Context, id, code, userAgentID string) error + SaveSAMLRequestID(ctx context.Context, id, requestID, userAgentID string) error DeleteAuthRequest(ctx context.Context, id string) error CheckLoginName(ctx context.Context, id, loginName, userAgentID string) error diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 950809f0c8..763d36f45f 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -7,6 +7,7 @@ import ( "github.com/zitadel/logging" + "github.com/zitadel/zitadel/feature" "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view" cache "github.com/zitadel/zitadel/internal/auth_request/repository" @@ -52,6 +53,8 @@ type AuthRequestRepo struct { ApplicationProvider applicationProvider CustomTextProvider customTextProvider + FeatureCheck feature.Checker + IdGenerator id.Generator } @@ -190,6 +193,17 @@ func (repo *AuthRequestRepo) SaveAuthCode(ctx context.Context, id, code, userAge return repo.AuthRequests.UpdateAuthRequest(ctx, request) } +func (repo *AuthRequestRepo) SaveSAMLRequestID(ctx context.Context, id, requestID, userAgentID string) (err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + request, err := repo.getAuthRequest(ctx, id, userAgentID) + if err != nil { + return err + } + request.SAMLRequestID = requestID + return repo.AuthRequests.UpdateAuthRequest(ctx, request) +} + func (repo *AuthRequestRepo) AuthRequestByCode(ctx context.Context, code string) (_ *domain.AuthRequest, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -650,7 +664,12 @@ func (repo *AuthRequestRepo) fillPolicies(ctx context.Context, request *domain.A orgID = request.UserOrgID } if orgID == "" { - orgID = authz.GetInstance(ctx).InstanceID() + orgID = authz.GetInstance(ctx).DefaultOrganisationID() + f, err := repo.FeatureCheck.CheckInstanceBooleanFeature(ctx, domain.FeatureLoginDefaultOrg) + logging.WithFields("authReq", request.ID).OnError(err).Warnf("could not check feature %s", domain.FeatureLoginDefaultOrg) + if !f.Boolean { + orgID = authz.GetInstance(ctx).InstanceID() + } } loginPolicy, idpProviders, err := repo.getLoginPolicyAndIDPProviders(ctx, orgID) @@ -671,19 +690,7 @@ func (repo *AuthRequestRepo) fillPolicies(ctx context.Context, request *domain.A return err } request.PrivacyPolicy = privacyPolicy - privateLabelingOrgID := authz.GetInstance(ctx).InstanceID() - if request.PrivateLabelingSetting != domain.PrivateLabelingSettingUnspecified { - privateLabelingOrgID = request.ApplicationResourceOwner - } - if request.PrivateLabelingSetting == domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy || request.PrivateLabelingSetting == domain.PrivateLabelingSettingUnspecified { - if request.UserOrgID != "" { - privateLabelingOrgID = request.UserOrgID - } - } - if request.RequestedOrgID != "" { - privateLabelingOrgID = request.RequestedOrgID - } - labelPolicy, err := repo.getLabelPolicy(ctx, privateLabelingOrgID) + labelPolicy, err := repo.getLabelPolicy(ctx, request.PrivateLabelingOrgID(orgID)) if err != nil { return err } @@ -749,8 +756,9 @@ func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *domain } // the user was either not found or not active // so check if the loginname suffix matches a verified org domain - if repo.checkDomainDiscovery(ctx, request, loginName) { - return nil + ok, err := repo.checkDomainDiscovery(ctx, request, loginName) + if err != nil || ok { + return err } // let's once again check if the user was just inactive if user != nil && user.State == int32(domain.UserStateInactive) { @@ -782,30 +790,34 @@ func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *domain return errors.ThrowInternal(nil, "AUTH-asf3df", "Errors.Internal") } -func (repo *AuthRequestRepo) checkDomainDiscovery(ctx context.Context, request *domain.AuthRequest, loginName string) bool { +func (repo *AuthRequestRepo) checkDomainDiscovery(ctx context.Context, request *domain.AuthRequest, loginName string) (bool, error) { // check if there's a suffix in the loginname loginName = strings.TrimSpace(strings.ToLower(loginName)) index := strings.LastIndex(loginName, "@") if index < 0 { - return false + return false, nil } // check if the suffix matches a verified domain org, err := repo.Query.OrgByVerifiedDomain(ctx, loginName[index+1:]) if err != nil { - return false + return false, nil } // and if the login policy allows domain discovery policy, err := repo.Query.LoginPolicyByID(ctx, true, org.ID, false) if err != nil || !policy.AllowDomainDiscovery { - return false + return false, nil } // discovery was allowed, so set the org as requested org // and clear all potentially existing user information and only set the loginname as hint (for registration) + // also ensure that the policies are read from the org request.SetOrgInformation(org.ID, org.Name, org.Domain, false) request.SetUserInfo("", "", "", "", "", org.ID) + if err = repo.fillPolicies(ctx, request); err != nil { + return false, err + } request.LoginHint = loginName request.Prompt = append(request.Prompt, domain.PromptCreate) // to trigger registration - return true + return true, nil } func (repo *AuthRequestRepo) checkLoginNameInput(ctx context.Context, request *domain.AuthRequest, loginNameInput string) (*user_view_model.UserView, error) { diff --git a/internal/auth/repository/eventsourcing/repository.go b/internal/auth/repository/eventsourcing/repository.go index 257b49d706..b8d41e009f 100644 --- a/internal/auth/repository/eventsourcing/repository.go +++ b/internal/auth/repository/eventsourcing/repository.go @@ -3,6 +3,7 @@ package eventsourcing import ( "context" + "github.com/zitadel/zitadel/feature" "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/eventstore" "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/spooler" auth_view "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view" @@ -88,6 +89,7 @@ func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, c ProjectProvider: queryView, ApplicationProvider: queries, CustomTextProvider: queries, + FeatureCheck: feature.NewCheck(esV2), IdGenerator: idGenerator, }, eventstore.TokenRepo{ diff --git a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go index 9524958ba1..f8ca3bf76d 100644 --- a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go +++ b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go @@ -45,16 +45,19 @@ func (repo *TokenVerifierRepo) tokenByID(ctx context.Context, tokenID, userID st defer func() { span.EndWithError(err) }() instanceID := authz.GetInstance(ctx).InstanceID() + + // always load the latest sequence first, so in case the token was not found by id, + // the sequence will be equal or lower than the actual projection and no events are lost + sequence, err := repo.View.GetLatestTokenSequence(ctx, instanceID) + logging.WithFields("instanceID", instanceID, "userID", userID, "tokenID", tokenID). + OnError(err). + Errorf("could not get current sequence for token check") + token, viewErr := repo.View.TokenByIDs(tokenID, userID, instanceID) if viewErr != nil && !caos_errs.IsNotFound(viewErr) { return nil, viewErr } if caos_errs.IsNotFound(viewErr) { - sequence, err := repo.View.GetLatestTokenSequence(ctx, instanceID) - logging.WithFields("instanceID", instanceID, "userID", userID, "tokenID", tokenID). - OnError(err). - Errorf("could not get current sequence for token check") - token = new(model.TokenView) token.ID = tokenID token.UserID = userID diff --git a/internal/command/command.go b/internal/command/command.go index aa8c1f3e77..6288bbd234 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -2,7 +2,13 @@ package command import ( "context" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" "net/http" + "strconv" "time" "github.com/zitadel/zitadel/internal/api/authz" @@ -16,6 +22,7 @@ import ( "github.com/zitadel/zitadel/internal/id" "github.com/zitadel/zitadel/internal/repository/action" "github.com/zitadel/zitadel/internal/repository/authrequest" + "github.com/zitadel/zitadel/internal/repository/feature" "github.com/zitadel/zitadel/internal/repository/idpintent" instance_repo "github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/keypair" @@ -73,6 +80,8 @@ type Commands struct { publicKeyLifetime time.Duration certificateLifetime time.Duration defaultSecretGenerators *SecretGenerators + + samlCertificateAndKeyGenerator func(id string) ([]byte, []byte, error) } func StartCommands( @@ -130,6 +139,7 @@ func StartCommands( defaultRefreshTokenLifetime: defaultRefreshTokenLifetime, defaultRefreshTokenIdleLifetime: defaultRefreshTokenIdleLifetime, defaultSecretGenerators: defaultSecretGenerators, + samlCertificateAndKeyGenerator: samlCertificateAndKeyGenerator(defaults.KeyConfig.Size), } instance_repo.RegisterEventMappers(repo.eventstore) @@ -145,6 +155,7 @@ func StartCommands( authrequest.RegisterEventMappers(repo.eventstore) oidcsession.RegisterEventMappers(repo.eventstore) milestone.RegisterEventMappers(repo.eventstore) + feature.RegisterEventMappers(repo.eventstore) repo.codeAlg = crypto.NewBCrypt(defaults.SecretGenerators.PasswordSaltCost) repo.userPasswordHasher, err = defaults.PasswordHasher.PasswordHasher() @@ -209,3 +220,36 @@ func exists(ctx context.Context, filter preparation.FilterToQueryReducer, wm exi } return wm.Exists(), nil } + +func samlCertificateAndKeyGenerator(keySize int) func(id string) ([]byte, []byte, error) { + return func(id string) ([]byte, []byte, error) { + priv, pub, err := crypto.GenerateKeyPair(keySize) + if err != nil { + return nil, nil, err + } + + serial, err := strconv.Atoi(id) + if err != nil { + return nil, nil, err + } + template := x509.Certificate{ + SerialNumber: big.NewInt(int64(serial)), + Subject: pkix.Name{ + Organization: []string{"ZITADEL"}, + SerialNumber: id, + }, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, pub, priv) + if err != nil { + return nil, nil, errors.ThrowInternalf(err, "COMMAND-x92u101j", "failed to create certificate") + } + + keyBlock := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)} + certBlock := &pem.Block{Type: "CERTIFICATE", Bytes: derBytes} + return pem.EncodeToMemory(keyBlock), pem.EncodeToMemory(certBlock), nil + } +} diff --git a/internal/command/idp.go b/internal/command/idp.go index 5a21596a29..3dc8f1ad50 100644 --- a/internal/command/idp.go +++ b/internal/command/idp.go @@ -110,6 +110,15 @@ type LDAPProvider struct { IDPOptions idp.Options } +type SAMLProvider struct { + Name string + Metadata []byte + MetadataURL string + Binding string + WithSignedRequest bool + IDPOptions idp.Options +} + type AppleProvider struct { Name string ClientID string diff --git a/internal/command/idp_intent.go b/internal/command/idp_intent.go index 21b4cd7d94..265293a3a8 100644 --- a/internal/command/idp_intent.go +++ b/internal/command/idp_intent.go @@ -4,8 +4,11 @@ import ( "context" "encoding/base64" "encoding/json" + "encoding/xml" "net/url" + "github.com/crewjam/saml" + "github.com/crewjam/saml/samlsp" "github.com/zitadel/oidc/v2/pkg/oidc" "github.com/zitadel/zitadel/internal/command/preparation" @@ -76,12 +79,36 @@ func (c *Commands) CreateIntent(ctx context.Context, idpID, successURL, failureU return writeModel, writeModelToObjectDetails(&writeModel.WriteModel), nil } -func (c *Commands) GetProvider(ctx context.Context, idpID string, callbackURL string) (idp.Provider, error) { +func (c *Commands) GetProvider(ctx context.Context, idpID string, idpCallback string, samlRootURL string) (idp.Provider, error) { writeModel, err := IDPProviderWriteModel(ctx, c.eventstore.Filter, idpID) if err != nil { return nil, err } - return writeModel.ToProvider(callbackURL, c.idpConfigEncryption) + if writeModel.IDPType != domain.IDPTypeSAML { + return writeModel.ToProvider(idpCallback, c.idpConfigEncryption) + } + return writeModel.ToSAMLProvider( + samlRootURL, + c.idpConfigEncryption, + func(ctx context.Context, intentID string) (*samlsp.TrackedRequest, error) { + intent, err := c.GetActiveIntent(ctx, intentID) + if err != nil { + return nil, err + } + return &samlsp.TrackedRequest{ + SAMLRequestID: intent.RequestID, + Index: intentID, + URI: intent.SuccessURL.String(), + }, nil + }, + func(ctx context.Context, intentID, samlRequestID string) error { + intent, err := c.GetActiveIntent(ctx, intentID) + if err != nil { + return err + } + return c.RequestSAMLIDPIntent(ctx, intent, samlRequestID) + }, + ) } func (c *Commands) GetActiveIntent(ctx context.Context, intentID string) (*IDPIntentWriteModel, error) { @@ -98,16 +125,18 @@ func (c *Commands) GetActiveIntent(ctx context.Context, intentID string) (*IDPIn return intent, nil } -func (c *Commands) AuthURLFromProvider(ctx context.Context, idpID, state string, callbackURL string) (string, error) { - provider, err := c.GetProvider(ctx, idpID, callbackURL) +func (c *Commands) AuthFromProvider(ctx context.Context, idpID, state string, idpCallback, samlRootURL string) (string, bool, error) { + provider, err := c.GetProvider(ctx, idpID, idpCallback, samlRootURL) if err != nil { - return "", err + return "", false, err } session, err := provider.BeginAuth(ctx, state) if err != nil { - return "", err + return "", false, err } - return session.GetAuthURL(), nil + + content, redirect := session.GetAuth(ctx) + return content, redirect, nil } func getIDPIntentWriteModel(ctx context.Context, writeModel *IDPIntentWriteModel, filter preparation.FilterToQueryReducer) error { @@ -152,6 +181,47 @@ func (c *Commands) SucceedIDPIntent(ctx context.Context, writeModel *IDPIntentWr return token, nil } +func (c *Commands) SucceedSAMLIDPIntent(ctx context.Context, writeModel *IDPIntentWriteModel, idpUser idp.User, userID string, assertion *saml.Assertion) (string, error) { + token, err := c.generateIntentToken(writeModel.AggregateID) + if err != nil { + return "", err + } + idpInfo, err := json.Marshal(idpUser) + if err != nil { + return "", err + } + assertionData, err := xml.Marshal(assertion) + if err != nil { + return "", err + } + assertionEnc, err := crypto.Encrypt(assertionData, c.idpConfigEncryption) + if err != nil { + return "", err + } + cmd := idpintent.NewSAMLSucceededEvent( + ctx, + &idpintent.NewAggregate(writeModel.AggregateID, writeModel.ResourceOwner).Aggregate, + idpInfo, + idpUser.GetID(), + idpUser.GetPreferredUsername(), + userID, + assertionEnc, + ) + err = c.pushAppendAndReduce(ctx, writeModel, cmd) + if err != nil { + return "", err + } + return token, nil +} + +func (c *Commands) RequestSAMLIDPIntent(ctx context.Context, writeModel *IDPIntentWriteModel, requestID string) error { + return c.pushAppendAndReduce(ctx, writeModel, idpintent.NewSAMLRequestEvent( + ctx, + &idpintent.NewAggregate(writeModel.AggregateID, writeModel.ResourceOwner).Aggregate, + requestID, + )) +} + func (c *Commands) generateIntentToken(intentID string) (string, error) { token, err := c.idpConfigEncryption.Encrypt([]byte(intentID)) if err != nil { diff --git a/internal/command/idp_intent_model.go b/internal/command/idp_intent_model.go index bf70c78a8c..b2242c3832 100644 --- a/internal/command/idp_intent_model.go +++ b/internal/command/idp_intent_model.go @@ -25,6 +25,9 @@ type IDPIntentWriteModel struct { IDPEntryAttributes map[string][]string + RequestID string + Assertion *crypto.CryptoValue + State domain.IDPIntentState aggregate *eventstore.Aggregate } @@ -46,6 +49,10 @@ func (wm *IDPIntentWriteModel) Reduce() error { wm.reduceStartedEvent(e) case *idpintent.SucceededEvent: wm.reduceOAuthSucceededEvent(e) + case *idpintent.SAMLSucceededEvent: + wm.reduceSAMLSucceededEvent(e) + case *idpintent.SAMLRequestEvent: + wm.reduceSAMLRequestEvent(e) case *idpintent.LDAPSucceededEvent: wm.reduceLDAPSucceededEvent(e) case *idpintent.FailedEvent: @@ -64,6 +71,8 @@ func (wm *IDPIntentWriteModel) Query() *eventstore.SearchQueryBuilder { EventTypes( idpintent.StartedEventType, idpintent.SucceededEventType, + idpintent.SAMLSucceededEventType, + idpintent.SAMLRequestEventType, idpintent.LDAPSucceededEventType, idpintent.FailedEventType, ). @@ -77,6 +86,15 @@ func (wm *IDPIntentWriteModel) reduceStartedEvent(e *idpintent.StartedEvent) { wm.State = domain.IDPIntentStateStarted } +func (wm *IDPIntentWriteModel) reduceSAMLSucceededEvent(e *idpintent.SAMLSucceededEvent) { + wm.UserID = e.UserID + wm.IDPUser = e.IDPUser + wm.IDPUserID = e.IDPUserID + wm.IDPUserName = e.IDPUserName + wm.Assertion = e.Assertion + wm.State = domain.IDPIntentStateSucceeded +} + func (wm *IDPIntentWriteModel) reduceLDAPSucceededEvent(e *idpintent.LDAPSucceededEvent) { wm.UserID = e.UserID wm.IDPUser = e.IDPUser @@ -96,6 +114,10 @@ func (wm *IDPIntentWriteModel) reduceOAuthSucceededEvent(e *idpintent.SucceededE wm.State = domain.IDPIntentStateSucceeded } +func (wm *IDPIntentWriteModel) reduceSAMLRequestEvent(e *idpintent.SAMLRequestEvent) { + wm.RequestID = e.RequestID +} + func (wm *IDPIntentWriteModel) reduceFailedEvent(e *idpintent.FailedEvent) { wm.State = domain.IDPIntentStateFailed } diff --git a/internal/command/idp_intent_test.go b/internal/command/idp_intent_test.go index 5591c7d648..aeffe72eb5 100644 --- a/internal/command/idp_intent_test.go +++ b/internal/command/idp_intent_test.go @@ -5,6 +5,7 @@ import ( "net/url" "testing" + "github.com/crewjam/saml" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -212,7 +213,7 @@ func TestCommands_CreateIntent(t *testing.T) { } } -func TestCommands_AuthURLFromProvider(t *testing.T) { +func TestCommands_AuthFromProvider(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore secretCrypto crypto.EncryptionAlgorithm @@ -222,10 +223,12 @@ func TestCommands_AuthURLFromProvider(t *testing.T) { idpID string state string callbackURL string + samlRootURL string } type res struct { - authURL string - err error + content string + redirect bool + err error } tests := []struct { name string @@ -296,7 +299,7 @@ func TestCommands_AuthURLFromProvider(t *testing.T) { }, }, { - "push", + "oauth auth redirect", fields{ secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), eventstore: eventstoreExpect(t, @@ -351,7 +354,8 @@ func TestCommands_AuthURLFromProvider(t *testing.T) { callbackURL: "url", }, res{ - authURL: "auth?client_id=clientID&prompt=select_account&redirect_uri=url&response_type=code&state=state", + content: "auth?client_id=clientID&prompt=select_account&redirect_uri=url&response_type=code&state=state", + redirect: true, }, }, { @@ -440,7 +444,8 @@ func TestCommands_AuthURLFromProvider(t *testing.T) { callbackURL: "url", }, res{ - authURL: "https://login.microsoftonline.com/tenant/oauth2/v2.0/authorize?client_id=clientID&prompt=select_account&redirect_uri=url&response_type=code&scope=openid+profile+User.Read&state=state", + content: "https://login.microsoftonline.com/tenant/oauth2/v2.0/authorize?client_id=clientID&prompt=select_account&redirect_uri=url&response_type=code&scope=openid+profile+User.Read&state=state", + redirect: true, }, }, } @@ -450,9 +455,142 @@ func TestCommands_AuthURLFromProvider(t *testing.T) { eventstore: tt.fields.eventstore, idpConfigEncryption: tt.fields.secretCrypto, } - authURL, err := c.AuthURLFromProvider(tt.args.ctx, tt.args.idpID, tt.args.state, tt.args.callbackURL) + content, redirect, err := c.AuthFromProvider(tt.args.ctx, tt.args.idpID, tt.args.state, tt.args.callbackURL, tt.args.samlRootURL) require.ErrorIs(t, err, tt.res.err) - assert.Equal(t, tt.res.authURL, authURL) + assert.Equal(t, tt.res.redirect, redirect) + assert.Equal(t, tt.res.content, content) + }) + } +} + +func TestCommands_AuthFromProvider_SAML(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + secretCrypto crypto.EncryptionAlgorithm + } + type args struct { + ctx context.Context + idpID string + state string + callbackURL string + samlRootURL string + } + type res struct { + url string + values map[string]string + err error + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + "saml auth default redirect", + fields{ + secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + eventstore: eventstoreExpect(t, + expectFilter( + eventFromEventPusherWithInstanceID( + "instance", + instance.NewSAMLIDPAddedEvent(context.Background(), &instance.NewAggregate("instance").Aggregate, + "idp", + "name", + []byte("\n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n \n urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n \n \n \n"), + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("key"), + }, + []byte("certificate"), + "", + false, + rep_idp.Options{}, + )), + ), + expectFilter( + eventFromEventPusherWithInstanceID( + "instance", + instance.NewSAMLIDPAddedEvent(context.Background(), &instance.NewAggregate("instance").Aggregate, + "idp", + "name", + []byte("\n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n \n urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n \n \n \n"), + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAxHd087RoEm9ywVWZ/H+tDWxQsmVvhfRz4jAq/RfU+OWXNH4J\njMMSHdFs0Q+WP98nNXRyc7fgbMb8NdmlB2yD4qLYapN5SDaBc5dh/3EnyFt53oSs\njTlKnQUPAeJr2qh/NY046CfyUyQMM4JR5OiQFo4TssfWnqdcgamGt0AEnk2lvbMZ\nKQdAqNS9lDzYbjMGavEQPTZE35mFXFQXjaooZXq+TIa7hbaq7/idH7cHNbLcPLgj\nfPQA8q+DYvnvhXlmq0LPQZH3Oiixf+SF2vRwrBzT2mqGD2OiOkUmhuPwyqEiiBHt\nfxklRtRU6WfLa1Gcb1PsV0uoBGpV3KybIl/GlwIDAQABAoIBAEQjDduLgOCL6Gem\n0X3hpdnW6/HC/jed/Sa//9jBECq2LYeWAqff64ON40hqOHi0YvvGA/+gEOSI6mWe\nsv5tIxxRz+6+cLybsq+tG96kluCE4TJMHy/nY7orS/YiWbd+4odnEApr+D3fbZ/b\nnZ1fDsHTyn8hkYx6jLmnWsJpIHDp7zxD76y7k2Bbg6DZrCGiVxngiLJk23dvz79W\np03lHLM7XE92aFwXQmhfxHGxrbuoB/9eY4ai5IHp36H4fw0vL6NXdNQAo/bhe0p9\nAYB7y0ZumF8Hg0Z/BmMeEzLy6HrYB+VE8cO93pNjhSyH+p2yDB/BlUyTiRLQAoM0\nVTmOZXECgYEA7NGlzpKNhyQEJihVqt0MW0LhKIO/xbBn+XgYfX6GpqPa/ucnMx5/\nVezpl3gK8IU4wPUhAyXXAHJiqNBcEeyxrw0MXLujDVMJgYaLysCLJdvMVgoY08mS\nK5IQivpbozpf4+0y3mOnA+Sy1kbfxv2X8xiWLODRQW3f3q/xoklwOR8CgYEA1GEe\nfaibOFTQAYcIVj77KXtBfYZsX3EGAyfAN9O7cKHq5oaxVstwnF47WxpuVtoKZxCZ\nbNm9D5WvQ9b+Ztpioe42tzwE7Bff/Osj868GcDdRPK7nFlh9N2yVn/D514dOYVwR\n4MBr1KrJzgRWt4QqS4H+to1GzudDTSNlG7gnK4kCgYBUi6AbOHzoYzZL/RhgcJwp\ntJ23nhmH1Su5h2OO4e3mbhcP66w19sxU+8iFN+kH5zfUw26utgKk+TE5vXExQQRK\nT2k7bg2PAzcgk80ybD0BHhA8I0yrx4m0nmfjhe/TPVLgh10iwgbtP+eM0i6v1vc5\nZWyvxu9N4ZEL6lpkqr0y1wKBgG/NAIQd8jhhTW7Aav8cAJQBsqQl038avJOEpYe+\nCnpsgoAAf/K0/f8TDCQVceh+t+MxtdK7fO9rWOxZjWsPo8Si5mLnUaAHoX4/OpnZ\nlYYVWMqdOEFnK+O1Yb7k2GFBdV2DXlX2dc1qavntBsls5ecB89id3pyk2aUN8Pf6\npYQhAoGAMGtrHFely9wyaxI0RTCyfmJbWZHGVGkv6ELK8wneJjdjl82XOBUGCg5q\naRCrTZ3dPitKwrUa6ibJCIFCIziiriBmjDvTHzkMvoJEap2TVxYNDR6IfINVsQ57\nlOsiC4A2uGq4Lbfld+gjoplJ5GX6qXtTgZ6m7eo0y7U6zm2tkN0=\n-----END RSA PRIVATE KEY-----\n"), + }, []byte("-----BEGIN CERTIFICATE-----\nMIIC2zCCAcOgAwIBAgIIAy/jm1gAAdEwDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UE\nChMHWklUQURFTDAeFw0yMzA4MzAwNzExMTVaFw0yNDA4MjkwNzExMTVaMBIxEDAO\nBgNVBAoTB1pJVEFERUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDE\nd3TztGgSb3LBVZn8f60NbFCyZW+F9HPiMCr9F9T45Zc0fgmMwxId0WzRD5Y/3yc1\ndHJzt+Bsxvw12aUHbIPiothqk3lINoFzl2H/cSfIW3nehKyNOUqdBQ8B4mvaqH81\njTjoJ/JTJAwzglHk6JAWjhOyx9aep1yBqYa3QASeTaW9sxkpB0Co1L2UPNhuMwZq\n8RA9NkTfmYVcVBeNqihler5MhruFtqrv+J0ftwc1stw8uCN89ADyr4Ni+e+FeWar\nQs9Bkfc6KLF/5IXa9HCsHNPaaoYPY6I6RSaG4/DKoSKIEe1/GSVG1FTpZ8trUZxv\nU+xXS6gEalXcrJsiX8aXAgMBAAGjNTAzMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE\nDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCx\n/dRNIj0N/16zJhZR/ahkc2AkvDXYxyr4JRT5wK9GQDNl/oaX3debRuSi/tfaXFIX\naJA6PxM4J49ZaiEpLrKfxMz5kAhjKchCBEMcH3mGt+iNZH7EOyTvHjpGrP2OZrsh\nO17yrvN3HuQxIU6roJlqtZz2iAADsoPtwOO4D7hupm9XTMkSnAmlMWOo/q46Jz89\n1sMxB+dXmH/zV0wgwh0omZfLV0u89mvdq269VhcjNBpBYSnN1ccqYWd5iwziob3I\nvaavGHGfkbvRUn/tKftYuTK30q03R+e9YbmlWZ0v695owh2e/apCzowQsCKfSVC8\nOxVyt5XkHq1tWwVyBmFp\n-----END CERTIFICATE-----\n"), + "", + false, + rep_idp.Options{}, + )), + ), + expectFilter( + eventFromEventPusherWithInstanceID( + "instance", + func() eventstore.Command { + success, _ := url.Parse("https://success.url") + failure, _ := url.Parse("https://failure.url") + return idpintent.NewStartedEvent( + context.Background(), + &idpintent.NewAggregate("id", "ro").Aggregate, + success, + failure, + "idp", + ) + }(), + ), + ), + expectRandomPush( + eventPusherToEvents( + idpintent.NewSAMLRequestEvent( + context.Background(), + &idpintent.NewAggregate("id", "ro").Aggregate, + "request", + ), + ), + ), + ), + }, + args{ + ctx: authz.SetCtxData(context.Background(), authz.CtxData{OrgID: "ro"}), + idpID: "idp", + state: "id", + callbackURL: "url", + samlRootURL: "samlurl", + }, + res{ + url: "http://localhost:8000/sso", + values: map[string]string{ + "SAMLRequest": "", // generated IDs so not assertable + "RelayState": "id", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + eventstore: tt.fields.eventstore, + idpConfigEncryption: tt.fields.secretCrypto, + } + content, _, err := c.AuthFromProvider(tt.args.ctx, tt.args.idpID, tt.args.state, tt.args.callbackURL, tt.args.samlRootURL) + require.ErrorIs(t, err, tt.res.err) + + authURL, err := url.Parse(content) + require.NoError(t, err) + + assert.Equal(t, tt.res.url, authURL.Scheme+"://"+authURL.Host+authURL.Path) + query := authURL.Query() + for k, v := range tt.res.values { + assert.True(t, query.Has(k)) + if v != "" { + assert.Equal(t, v, query.Get(k)) + } + } }) } } @@ -585,6 +723,193 @@ func TestCommands_SucceedIDPIntent(t *testing.T) { } } +func TestCommands_SucceedSAMLIDPIntent(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + idpConfigEncryption crypto.EncryptionAlgorithm + } + type args struct { + ctx context.Context + writeModel *IDPIntentWriteModel + idpUser idp.User + assertion *saml.Assertion + userID string + } + type res struct { + token string + err error + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + "encryption fails", + fields{ + idpConfigEncryption: func() crypto.EncryptionAlgorithm { + m := crypto.NewMockEncryptionAlgorithm(gomock.NewController(t)) + m.EXPECT().Encrypt(gomock.Any()).Return(nil, z_errors.ThrowInternal(nil, "id", "encryption failed")) + return m + }(), + }, + args{ + ctx: context.Background(), + writeModel: NewIDPIntentWriteModel("id", "ro"), + }, + res{ + err: z_errors.ThrowInternal(nil, "id", "encryption failed"), + }, + }, + { + "push", + fields{ + idpConfigEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + eventstore: eventstoreExpect(t, + expectPush( + eventPusherToEvents( + idpintent.NewSAMLSucceededEvent( + context.Background(), + &idpintent.NewAggregate("id", "ro").Aggregate, + []byte(`{"sub":"id","preferred_username":"username"}`), + "id", + "username", + "", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte(""), + }, + ), + ), + ), + ), + }, + args{ + ctx: context.Background(), + writeModel: NewIDPIntentWriteModel("id", "ro"), + assertion: &saml.Assertion{ID: "id"}, + idpUser: openid.NewUser(&oidc.UserInfo{ + Subject: "id", + UserInfoProfile: oidc.UserInfoProfile{ + PreferredUsername: "username", + }, + }), + }, + res{ + token: "aWQ", + }, + }, + { + "push with userID", + fields{ + idpConfigEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + eventstore: eventstoreExpect(t, + expectPush( + eventPusherToEvents( + idpintent.NewSAMLSucceededEvent( + context.Background(), + &idpintent.NewAggregate("id", "ro").Aggregate, + []byte(`{"sub":"id","preferred_username":"username"}`), + "id", + "username", + "user", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte(""), + }, + ), + ), + ), + ), + }, + args{ + ctx: context.Background(), + writeModel: NewIDPIntentWriteModel("id", "ro"), + assertion: &saml.Assertion{ID: "id"}, + idpUser: openid.NewUser(&oidc.UserInfo{ + Subject: "id", + UserInfoProfile: oidc.UserInfoProfile{ + PreferredUsername: "username", + }, + }), + userID: "user", + }, + res{ + token: "aWQ", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + eventstore: tt.fields.eventstore, + idpConfigEncryption: tt.fields.idpConfigEncryption, + } + got, err := c.SucceedSAMLIDPIntent(tt.args.ctx, tt.args.writeModel, tt.args.idpUser, tt.args.userID, tt.args.assertion) + require.ErrorIs(t, err, tt.res.err) + assert.Equal(t, tt.res.token, got) + }) + } +} + +func TestCommands_RequestSAMLIDPIntent(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + } + type args struct { + ctx context.Context + writeModel *IDPIntentWriteModel + request string + } + type res struct { + err error + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + "push", + fields{ + eventstore: eventstoreExpect(t, + expectPush( + eventPusherToEvents( + idpintent.NewSAMLRequestEvent( + context.Background(), + &idpintent.NewAggregate("id", "ro").Aggregate, + "request", + ), + ), + ), + ), + }, + args{ + ctx: context.Background(), + writeModel: NewIDPIntentWriteModel("id", "ro"), + request: "request", + }, + res{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + eventstore: tt.fields.eventstore, + } + err := c.RequestSAMLIDPIntent(tt.args.ctx, tt.args.writeModel, tt.args.request) + require.ErrorIs(t, err, tt.res.err) + require.Equal(t, tt.args.writeModel.RequestID, tt.args.request) + }) + } +} + func TestCommands_SucceedLDAPIDPIntent(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore diff --git a/internal/command/idp_model.go b/internal/command/idp_model.go index fea291c44c..2736087de9 100644 --- a/internal/command/idp_model.go +++ b/internal/command/idp_model.go @@ -24,6 +24,8 @@ import ( "github.com/zitadel/zitadel/internal/idp/providers/ldap" "github.com/zitadel/zitadel/internal/idp/providers/oauth" "github.com/zitadel/zitadel/internal/idp/providers/oidc" + saml2 "github.com/zitadel/zitadel/internal/idp/providers/saml" + "github.com/zitadel/zitadel/internal/idp/providers/saml/requesttracker" "github.com/zitadel/zitadel/internal/repository/idp" "github.com/zitadel/zitadel/internal/repository/idpconfig" "github.com/zitadel/zitadel/internal/repository/instance" @@ -1721,6 +1723,153 @@ func (wm *AppleIDPWriteModel) GetProviderOptions() idp.Options { return wm.Options } +type SAMLIDPWriteModel struct { + eventstore.WriteModel + + Name string + ID string + Metadata []byte + Key *crypto.CryptoValue + Certificate []byte + Binding string + WithSignedRequest bool + idp.Options + + State domain.IDPState +} + +func (wm *SAMLIDPWriteModel) Reduce() error { + for _, event := range wm.Events { + switch e := event.(type) { + case *idp.SAMLIDPAddedEvent: + wm.reduceAddedEvent(e) + case *idp.SAMLIDPChangedEvent: + wm.reduceChangedEvent(e) + case *idp.RemovedEvent: + wm.State = domain.IDPStateRemoved + } + } + return wm.WriteModel.Reduce() +} + +func (wm *SAMLIDPWriteModel) reduceAddedEvent(e *idp.SAMLIDPAddedEvent) { + wm.Name = e.Name + wm.Metadata = e.Metadata + wm.Key = e.Key + wm.Certificate = e.Certificate + wm.Binding = e.Binding + wm.WithSignedRequest = e.WithSignedRequest + wm.Options = e.Options + wm.State = domain.IDPStateActive +} + +func (wm *SAMLIDPWriteModel) reduceChangedEvent(e *idp.SAMLIDPChangedEvent) { + if e.Key != nil { + wm.Key = e.Key + } + if e.Certificate != nil { + wm.Certificate = e.Certificate + } + if e.Name != nil { + wm.Name = *e.Name + } + if e.Metadata != nil { + wm.Metadata = e.Metadata + } + if e.Binding != nil { + wm.Binding = *e.Binding + } + if e.WithSignedRequest != nil { + wm.WithSignedRequest = *e.WithSignedRequest + } + wm.Options.ReduceChanges(e.OptionChanges) +} + +func (wm *SAMLIDPWriteModel) NewChanges( + name string, + metadata, + key, + certificate []byte, + secretCrypto crypto.Crypto, + binding string, + withSignedRequest bool, + options idp.Options, +) ([]idp.SAMLIDPChanges, error) { + changes := make([]idp.SAMLIDPChanges, 0) + if key != nil { + keyEnc, err := crypto.Crypt(key, secretCrypto) + if err != nil { + return nil, err + } + changes = append(changes, idp.ChangeSAMLKey(keyEnc)) + } + if certificate != nil { + changes = append(changes, idp.ChangeSAMLCertificate(certificate)) + } + if wm.Name != name { + changes = append(changes, idp.ChangeSAMLName(name)) + } + if !reflect.DeepEqual(wm.Metadata, metadata) { + changes = append(changes, idp.ChangeSAMLMetadata(metadata)) + } + if wm.Binding != binding { + changes = append(changes, idp.ChangeSAMLBinding(binding)) + } + if wm.WithSignedRequest != withSignedRequest { + changes = append(changes, idp.ChangeSAMLWithSignedRequest(withSignedRequest)) + } + opts := wm.Options.Changes(options) + if !opts.IsZero() { + changes = append(changes, idp.ChangeSAMLOptions(opts)) + } + return changes, nil +} + +func (wm *SAMLIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.EncryptionAlgorithm, getRequest requesttracker.GetRequest, addRequest requesttracker.AddRequest) (providers.Provider, error) { + key, err := crypto.Decrypt(wm.Key, idpAlg) + if err != nil { + return nil, err + } + + opts := make([]saml2.ProviderOpts, 0, 7) + if wm.IsCreationAllowed { + opts = append(opts, saml2.WithCreationAllowed()) + } + if wm.IsLinkingAllowed { + opts = append(opts, saml2.WithLinkingAllowed()) + } + if wm.IsAutoCreation { + opts = append(opts, saml2.WithAutoCreation()) + } + if wm.IsAutoUpdate { + opts = append(opts, saml2.WithAutoUpdate()) + } + if wm.WithSignedRequest { + opts = append(opts, saml2.WithSignedRequest()) + } + if wm.Binding != "" { + opts = append(opts, saml2.WithBinding(wm.Binding)) + } + opts = append(opts, saml2.WithCustomRequestTracker( + requesttracker.New( + addRequest, + getRequest, + ), + )) + return saml2.New( + wm.Name, + callbackURL, + wm.Metadata, + wm.Certificate, + key, + opts..., + ) +} + +func (wm *SAMLIDPWriteModel) GetProviderOptions() idp.Options { + return wm.Options +} + type IDPRemoveWriteModel struct { eventstore.WriteModel @@ -1753,6 +1902,8 @@ func (wm *IDPRemoveWriteModel) Reduce() error { wm.reduceAdded(e.ID) case *idp.AppleIDPAddedEvent: wm.reduceAdded(e.ID) + case *idp.SAMLIDPAddedEvent: + wm.reduceAdded(e.ID) case *idp.RemovedEvent: wm.reduceRemoved(e.ID) case *idpconfig.IDPConfigAddedEvent: @@ -1839,6 +1990,10 @@ func (wm *IDPTypeWriteModel) Reduce() error { wm.reduceAdded(e.ID, domain.IDPTypeApple, e.Aggregate()) case *org.AppleIDPAddedEvent: wm.reduceAdded(e.ID, domain.IDPTypeApple, e.Aggregate()) + case *instance.SAMLIDPAddedEvent: + wm.reduceAdded(e.ID, domain.IDPTypeSAML, e.Aggregate()) + case *org.SAMLIDPAddedEvent: + wm.reduceAdded(e.ID, domain.IDPTypeSAML, e.Aggregate()) case *instance.OIDCIDPMigratedAzureADEvent: wm.reduceChanged(e.ID, domain.IDPTypeAzureAD) case *org.OIDCIDPMigratedAzureADEvent: @@ -1915,6 +2070,7 @@ func (wm *IDPTypeWriteModel) Query() *eventstore.SearchQueryBuilder { instance.GoogleIDPAddedEventType, instance.LDAPIDPAddedEventType, instance.AppleIDPAddedEventType, + instance.SAMLIDPAddedEventType, instance.OIDCIDPMigratedAzureADEventType, instance.OIDCIDPMigratedGoogleEventType, instance.IDPRemovedEventType, @@ -1934,6 +2090,7 @@ func (wm *IDPTypeWriteModel) Query() *eventstore.SearchQueryBuilder { org.GoogleIDPAddedEventType, org.LDAPIDPAddedEventType, org.AppleIDPAddedEventType, + org.SAMLIDPAddedEventType, org.OIDCIDPMigratedAzureADEventType, org.OIDCIDPMigratedGoogleEventType, org.IDPRemovedEventType, @@ -1962,8 +2119,15 @@ type IDP interface { GetProviderOptions() idp.Options } +type SAMLIDP interface { + eventstore.QueryReducer + ToProvider(string, crypto.EncryptionAlgorithm, requesttracker.GetRequest, requesttracker.AddRequest) (providers.Provider, error) + GetProviderOptions() idp.Options +} + type AllIDPWriteModel struct { - model IDP + model IDP + samlModel SAMLIDP ID string IDPType domain.IDPType @@ -2003,6 +2167,8 @@ func NewAllIDPWriteModel(resourceOwner string, instanceBool bool, id string, idp writeModel.model = NewGoogleInstanceIDPWriteModel(resourceOwner, id) case domain.IDPTypeApple: writeModel.model = NewAppleInstanceIDPWriteModel(resourceOwner, id) + case domain.IDPTypeSAML: + writeModel.samlModel = NewSAMLInstanceIDPWriteModel(resourceOwner, id) case domain.IDPTypeUnspecified: fallthrough default: @@ -2032,6 +2198,8 @@ func NewAllIDPWriteModel(resourceOwner string, instanceBool bool, id string, idp writeModel.model = NewGoogleOrgIDPWriteModel(resourceOwner, id) case domain.IDPTypeApple: writeModel.model = NewAppleOrgIDPWriteModel(resourceOwner, id) + case domain.IDPTypeSAML: + writeModel.samlModel = NewSAMLOrgIDPWriteModel(resourceOwner, id) case domain.IDPTypeUnspecified: fallthrough default: @@ -2042,21 +2210,44 @@ func NewAllIDPWriteModel(resourceOwner string, instanceBool bool, id string, idp } func (wm *AllIDPWriteModel) Reduce() error { - return wm.model.Reduce() + if wm.model != nil { + return wm.model.Reduce() + } + return wm.samlModel.Reduce() } func (wm *AllIDPWriteModel) Query() *eventstore.SearchQueryBuilder { - return wm.model.Query() + if wm.model != nil { + return wm.model.Query() + } + return wm.samlModel.Query() } func (wm *AllIDPWriteModel) AppendEvents(events ...eventstore.Event) { - wm.model.AppendEvents(events...) + if wm.model != nil { + wm.model.AppendEvents(events...) + return + } + wm.samlModel.AppendEvents(events...) } func (wm *AllIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.EncryptionAlgorithm) (providers.Provider, error) { + if wm.model == nil { + return nil, errors.ThrowInternal(nil, "COMMAND-afvf0gc9sa", "ErrorsIDPConfig.NotExisting") + } return wm.model.ToProvider(callbackURL, idpAlg) } func (wm *AllIDPWriteModel) GetProviderOptions() idp.Options { - return wm.model.GetProviderOptions() + if wm.model != nil { + return wm.model.GetProviderOptions() + } + return wm.samlModel.GetProviderOptions() +} + +func (wm *AllIDPWriteModel) ToSAMLProvider(callbackURL string, idpAlg crypto.EncryptionAlgorithm, getRequest requesttracker.GetRequest, addRequest requesttracker.AddRequest) (providers.Provider, error) { + if wm.samlModel == nil { + return nil, errors.ThrowInternal(nil, "COMMAND-csi30hdscv", "ErrorsIDPConfig.NotExisting") + } + return wm.samlModel.ToProvider(callbackURL, idpAlg, getRequest, addRequest) } diff --git a/internal/command/idp_model_test.go b/internal/command/idp_model_test.go index 86626d4b42..42c2112bf9 100644 --- a/internal/command/idp_model_test.go +++ b/internal/command/idp_model_test.go @@ -18,8 +18,9 @@ func TestCommands_AllIDPWriteModel(t *testing.T) { idpType domain.IDPType } type res struct { - writeModelType interface{} - err error + writeModelType interface{} + samlWriteModelType interface{} + err error } tests := []struct { name string @@ -156,6 +157,19 @@ func TestCommands_AllIDPWriteModel(t *testing.T) { err: nil, }, }, + { + name: "writemodel instance saml", + args: args{ + resourceOwner: "owner", + instanceBool: true, + id: "id", + idpType: domain.IDPTypeSAML, + }, + res: res{ + samlWriteModelType: &InstanceSAMLIDPWriteModel{}, + err: nil, + }, + }, { name: "writemodel instance unspecified", args: args{ @@ -298,6 +312,19 @@ func TestCommands_AllIDPWriteModel(t *testing.T) { err: nil, }, }, + { + name: "writemodel org saml", + args: args{ + resourceOwner: "owner", + instanceBool: false, + id: "id", + idpType: domain.IDPTypeSAML, + }, + res: res{ + samlWriteModelType: &OrgSAMLIDPWriteModel{}, + err: nil, + }, + }, { name: "writemodel org unspecified", args: args{ @@ -316,7 +343,12 @@ func TestCommands_AllIDPWriteModel(t *testing.T) { wm, err := NewAllIDPWriteModel(tt.args.resourceOwner, tt.args.instanceBool, tt.args.id, tt.args.idpType) require.ErrorIs(t, err, tt.res.err) if wm != nil { - assert.IsType(t, tt.res.writeModelType, wm.model) + if tt.res.writeModelType != nil { + assert.IsType(t, tt.res.writeModelType, wm.model) + } + if tt.res.samlWriteModelType != nil { + assert.IsType(t, tt.res.samlWriteModelType, wm.samlModel) + } } }) } diff --git a/internal/command/instance.go b/internal/command/instance.go index 1677d930fe..a686452372 100644 --- a/internal/command/instance.go +++ b/internal/command/instance.go @@ -15,6 +15,7 @@ import ( "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/id" "github.com/zitadel/zitadel/internal/notification/channels/smtp" + "github.com/zitadel/zitadel/internal/repository/feature" "github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/org" "github.com/zitadel/zitadel/internal/repository/project" @@ -112,6 +113,7 @@ type InstanceSetup struct { Quotas *struct { Items []*SetQuota } + Features map[domain.Feature]any } type SecretGenerators struct { @@ -432,6 +434,19 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str ) } + for f, value := range setup.Features { + switch v := value.(type) { + case bool: + wm, err := NewInstanceFeatureWriteModel[feature.Boolean](instanceID, f) + if err != nil { + return "", "", nil, nil, err + } + validations = append(validations, prepareSetFeature(wm, feature.Boolean{Boolean: v}, c.idGenerator)) + default: + return "", "", nil, nil, errors.ThrowInvalidArgument(nil, "INST-GE4tg", "Errors.Feature.TypeNotSupported") + } + } + cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validations...) if err != nil { return "", "", nil, nil, err diff --git a/internal/command/instance_feature.go b/internal/command/instance_feature.go new file mode 100644 index 0000000000..6aee52c09b --- /dev/null +++ b/internal/command/instance_feature.go @@ -0,0 +1,63 @@ +package command + +import ( + "context" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/command/preparation" + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/errors" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/id" + "github.com/zitadel/zitadel/internal/repository/feature" +) + +func (c *Commands) SetBooleanInstanceFeature(ctx context.Context, f domain.Feature, value bool) (*domain.ObjectDetails, error) { + instanceID := authz.GetInstance(ctx).InstanceID() + writeModel, err := NewInstanceFeatureWriteModel[feature.Boolean](instanceID, f) + if err != nil { + return nil, err + } + cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, + prepareSetFeature(writeModel, feature.Boolean{Boolean: value}, c.idGenerator)) + if err != nil { + return nil, err + } + if len(cmds) == 0 { + return writeModelToObjectDetails(&writeModel.FeatureWriteModel.WriteModel), nil + } + pushedEvents, err := c.eventstore.Push(ctx, cmds...) + if err != nil { + return nil, err + } + return pushedEventsToObjectDetails(pushedEvents), nil +} + +func prepareSetFeature[T feature.SetEventType](writeModel *InstanceFeatureWriteModel[T], value T, idGenerator id.Generator) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if !writeModel.feature.IsAFeature() || writeModel.feature == domain.FeatureUnspecified { + return nil, errors.ThrowPreconditionFailed(nil, "FEAT-JK3td", "Errors.Feature.NotExisting") + } + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + events, err := filter(ctx, writeModel.Query()) + if err != nil { + return nil, err + } + writeModel.AppendEvents(events...) + if err = writeModel.Reduce(); err != nil { + return nil, err + } + if len(events) == 0 { + writeModel.AggregateID, err = idGenerator.Next() + if err != nil { + return nil, err + } + } + setEvent, err := writeModel.Set(ctx, value) + if err != nil || setEvent == nil { + return nil, err + } + return []eventstore.Command{setEvent}, nil + }, nil + } +} diff --git a/internal/command/instance_feature_model.go b/internal/command/instance_feature_model.go new file mode 100644 index 0000000000..1389735ca5 --- /dev/null +++ b/internal/command/instance_feature_model.go @@ -0,0 +1,81 @@ +package command + +import ( + "context" + + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/errors" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/repository/feature" +) + +type FeatureWriteModel[T feature.SetEventType] struct { + eventstore.WriteModel + + feature domain.Feature + + Value T +} + +func NewFeatureWriteModel[T feature.SetEventType](instanceID, resourceOwner string, feature domain.Feature) (*FeatureWriteModel[T], error) { + wm := &FeatureWriteModel[T]{ + WriteModel: eventstore.WriteModel{ + InstanceID: instanceID, + ResourceOwner: resourceOwner, + }, + feature: feature, + } + if wm.Value.FeatureType() != feature.Type() { + return nil, errors.ThrowPreconditionFailed(nil, "FEAT-AS4k1", "Errors.Feature.InvalidValue") + } + return wm, nil +} +func (wm *FeatureWriteModel[T]) Set(ctx context.Context, value T) (event *feature.SetEvent[T], err error) { + if wm.Value == value { + return nil, nil + } + return feature.NewSetEvent[T]( + ctx, + &feature.NewAggregate(wm.AggregateID, wm.ResourceOwner).Aggregate, + wm.eventType(), + value, + ), nil +} + +func (wm *FeatureWriteModel[T]) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + AddQuery(). + AggregateTypes(feature.AggregateType). + EventTypes(wm.eventType()). + Builder() +} + +func (wm *FeatureWriteModel[T]) Reduce() error { + for _, event := range wm.Events { + switch e := event.(type) { + case *feature.SetEvent[T]: + wm.Value = e.Value + default: + return errors.ThrowPreconditionFailed(nil, "FEAT-SDfjk", "Errors.Feature.TypeNotSupported") + } + } + return wm.WriteModel.Reduce() +} + +func (wm *FeatureWriteModel[T]) eventType() eventstore.EventType { + return feature.EventTypeFromFeature(wm.feature) +} + +type InstanceFeatureWriteModel[T feature.SetEventType] struct { + FeatureWriteModel[T] +} + +func NewInstanceFeatureWriteModel[T feature.SetEventType](instanceID string, feature domain.Feature) (*InstanceFeatureWriteModel[T], error) { + wm, err := NewFeatureWriteModel[T](instanceID, instanceID, feature) + if err != nil { + return nil, err + } + return &InstanceFeatureWriteModel[T]{ + FeatureWriteModel: *wm, + }, nil +} diff --git a/internal/command/instance_feature_test.go b/internal/command/instance_feature_test.go new file mode 100644 index 0000000000..bf07ec2c6e --- /dev/null +++ b/internal/command/instance_feature_test.go @@ -0,0 +1,179 @@ +package command + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/errors" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/eventstore/repository" + "github.com/zitadel/zitadel/internal/id" + "github.com/zitadel/zitadel/internal/id/mock" + "github.com/zitadel/zitadel/internal/repository/feature" + "github.com/zitadel/zitadel/internal/repository/instance" +) + +func TestCommands_SetBooleanInstanceFeature(t *testing.T) { + type fields struct { + eventstore func(t *testing.T) *eventstore.Eventstore + idGenerator id.Generator + } + type args struct { + ctx context.Context + f domain.Feature + value bool + } + type res struct { + details *domain.ObjectDetails + err error + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + "unknown feature", + fields{ + eventstore: expectEventstore(), + }, + args{ + ctx: authz.WithInstanceID(context.Background(), "instanceID"), + f: domain.FeatureUnspecified, + value: true, + }, + res{ + err: errors.ThrowPreconditionFailed(nil, "FEAT-AS4k1", "Errors.Feature.InvalidValue"), + }, + }, + { + "wrong type", + fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusherWithInstanceID("instanceID", + // as there's currently no other [feature.SetEventType] than [feature.Boolean], + // we need to use a completely other event type to demonstrate the behaviour + instance.NewInstanceAddedEvent(context.Background(), &instance.NewAggregate("instanceID").Aggregate, + "instance", + ), + ), + ), + ), + }, + args{ + ctx: authz.WithInstanceID(context.Background(), "instanceID"), + f: domain.FeatureLoginDefaultOrg, + value: true, + }, + res{ + err: errors.ThrowPreconditionFailed(nil, "FEAT-SDfjk", "Errors.Feature.TypeNotSupported"), + }, + }, + { + "first set", + fields{ + eventstore: expectEventstore( + expectFilter(), + expectPush( + []*repository.Event{ + eventFromEventPusherWithInstanceID("instanceID", + feature.NewSetEvent[feature.Boolean](context.Background(), &feature.NewAggregate("featureID", "instanceID").Aggregate, + feature.EventTypeFromFeature(domain.FeatureLoginDefaultOrg), + feature.Boolean{Boolean: true}, + ), + ), + }, + ), + ), + idGenerator: mock.ExpectID(t, "featureID"), + }, + args{ + ctx: authz.WithInstanceID(context.Background(), "instanceID"), + f: domain.FeatureLoginDefaultOrg, + value: true, + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "instanceID", + }, + }, + }, + { + "update flag", + fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusherWithInstanceID("instanceID", + feature.NewSetEvent[feature.Boolean](context.Background(), &feature.NewAggregate("featureID", "instanceID").Aggregate, + feature.EventTypeFromFeature(domain.FeatureLoginDefaultOrg), + feature.Boolean{Boolean: true}, + ), + ), + ), + expectPush( + []*repository.Event{ + eventFromEventPusherWithInstanceID("instanceID", + feature.NewSetEvent[feature.Boolean](context.Background(), &feature.NewAggregate("featureID", "instanceID").Aggregate, + feature.EventTypeFromFeature(domain.FeatureLoginDefaultOrg), + feature.Boolean{Boolean: false}, + ), + ), + }, + ), + ), + }, + args{ + ctx: authz.WithInstanceID(context.Background(), "instanceID"), + f: domain.FeatureLoginDefaultOrg, + value: false, + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "instanceID", + }, + }, + }, + { + "no change", + fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusherWithInstanceID("instanceID", + feature.NewSetEvent[feature.Boolean](context.Background(), &feature.NewAggregate("featureID", "instanceID").Aggregate, + feature.EventTypeFromFeature(domain.FeatureLoginDefaultOrg), + feature.Boolean{Boolean: true}, + ), + ), + ), + ), + }, + args{ + ctx: authz.WithInstanceID(context.Background(), "instanceID"), + f: domain.FeatureLoginDefaultOrg, + value: true, + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "instanceID", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + eventstore: tt.fields.eventstore(t), + idGenerator: tt.fields.idGenerator, + } + got, err := c.SetBooleanInstanceFeature(tt.args.ctx, tt.args.f, tt.args.value) + assert.ErrorIs(t, err, tt.res.err) + assert.Equal(t, tt.res.details, got) + }) + } +} diff --git a/internal/command/instance_idp.go b/internal/command/instance_idp.go index a6d429f6c1..31b284f8c5 100644 --- a/internal/command/instance_idp.go +++ b/internal/command/instance_idp.go @@ -4,6 +4,8 @@ import ( "context" "strings" + "github.com/zitadel/saml/pkg/provider/xml" + "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/command/preparation" "github.com/zitadel/zitadel/internal/crypto" @@ -509,6 +511,71 @@ func (c *Commands) UpdateInstanceAppleProvider(ctx context.Context, id string, p return pushedEventsToObjectDetails(pushedEvents), nil } +func (c *Commands) AddInstanceSAMLProvider(ctx context.Context, provider SAMLProvider) (string, *domain.ObjectDetails, error) { + instanceID := authz.GetInstance(ctx).InstanceID() + instanceAgg := instance.NewAggregate(instanceID) + id, err := c.idGenerator.Next() + if err != nil { + return "", nil, err + } + writeModel := NewSAMLInstanceIDPWriteModel(instanceID, id) + cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddInstanceSAMLProvider(instanceAgg, writeModel, provider)) + if err != nil { + return "", nil, err + } + pushedEvents, err := c.eventstore.Push(ctx, cmds...) + if err != nil { + return "", nil, err + } + return id, pushedEventsToObjectDetails(pushedEvents), nil +} + +func (c *Commands) UpdateInstanceSAMLProvider(ctx context.Context, id string, provider SAMLProvider) (*domain.ObjectDetails, error) { + instanceID := authz.GetInstance(ctx).InstanceID() + instanceAgg := instance.NewAggregate(instanceID) + writeModel := NewSAMLInstanceIDPWriteModel(instanceID, id) + cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateInstanceSAMLProvider(instanceAgg, writeModel, provider)) + if err != nil { + return nil, err + } + if len(cmds) == 0 { + // no change, so return directly + return &domain.ObjectDetails{ + Sequence: writeModel.ProcessedSequence, + EventDate: writeModel.ChangeDate, + ResourceOwner: writeModel.ResourceOwner, + }, nil + } + pushedEvents, err := c.eventstore.Push(ctx, cmds...) + if err != nil { + return nil, err + } + return pushedEventsToObjectDetails(pushedEvents), nil +} + +func (c *Commands) RegenerateInstanceSAMLProviderCertificate(ctx context.Context, id string) (*domain.ObjectDetails, error) { + instanceID := authz.GetInstance(ctx).InstanceID() + instanceAgg := instance.NewAggregate(instanceID) + writeModel := NewSAMLInstanceIDPWriteModel(instanceID, id) + cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareRegenerateInstanceSAMLProviderCertificate(instanceAgg, writeModel)) + if err != nil { + return nil, err + } + if len(cmds) == 0 { + // no change, so return directly + return &domain.ObjectDetails{ + Sequence: writeModel.ProcessedSequence, + EventDate: writeModel.ChangeDate, + ResourceOwner: writeModel.ResourceOwner, + }, nil + } + pushedEvents, err := c.eventstore.Push(ctx, cmds...) + if err != nil { + return nil, err + } + return pushedEventsToObjectDetails(pushedEvents), nil +} + func (c *Commands) DeleteInstanceProvider(ctx context.Context, id string) (*domain.ObjectDetails, error) { instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID()) cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareDeleteInstanceProvider(instanceAgg, id)) @@ -1652,6 +1719,151 @@ func (c *Commands) prepareUpdateInstanceAppleProvider(a *instance.Aggregate, wri } } +func (c *Commands) prepareAddInstanceSAMLProvider(a *instance.Aggregate, writeModel *InstanceSAMLIDPWriteModel, provider SAMLProvider) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" { + return nil, caos_errs.ThrowInvalidArgument(nil, "INST-o07zjotgnd", "Errors.Invalid.Argument") + } + if provider.Metadata == nil && provider.MetadataURL != "" { + data, err := xml.ReadMetadataFromURL(c.httpClient, provider.MetadataURL) + if err != nil { + return nil, caos_errs.ThrowInvalidArgument(err, "INST-8vam1khq22", "Errors.Project.App.SAMLMetadataMissing") + } + provider.Metadata = data + } + if provider.Metadata == nil { + return nil, caos_errs.ThrowInvalidArgument(nil, "INST-3bi3esi16t", "Errors.Invalid.Argument") + } + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + events, err := filter(ctx, writeModel.Query()) + if err != nil { + return nil, err + } + writeModel.AppendEvents(events...) + if err = writeModel.Reduce(); err != nil { + return nil, err + } + + key, cert, err := c.samlCertificateAndKeyGenerator(writeModel.ID) + if err != nil { + return nil, err + } + keyEnc, err := crypto.Encrypt(key, c.idpConfigEncryption) + if err != nil { + return nil, err + } + return []eventstore.Command{ + instance.NewSAMLIDPAddedEvent( + ctx, + &a.Aggregate, + writeModel.ID, + provider.Name, + provider.Metadata, + keyEnc, + cert, + provider.Binding, + provider.WithSignedRequest, + provider.IDPOptions, + ), + }, nil + }, nil + } +} + +func (c *Commands) prepareUpdateInstanceSAMLProvider(a *instance.Aggregate, writeModel *InstanceSAMLIDPWriteModel, provider SAMLProvider) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if writeModel.ID = strings.TrimSpace(writeModel.ID); writeModel.ID == "" { + return nil, caos_errs.ThrowInvalidArgument(nil, "INST-7o3rq1owpm", "Errors.Invalid.Argument") + } + if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" { + return nil, caos_errs.ThrowInvalidArgument(nil, "INST-q2s9rak7o9", "Errors.Invalid.Argument") + } + if provider.Metadata == nil && provider.MetadataURL == "" { + return nil, caos_errs.ThrowInvalidArgument(nil, "INST-iw1rxnf4sf", "Errors.Invalid.Argument") + } + if provider.Metadata == nil && provider.MetadataURL != "" { + data, err := xml.ReadMetadataFromURL(c.httpClient, provider.MetadataURL) + if err != nil { + return nil, caos_errs.ThrowInvalidArgument(err, "INST-iijz4h01if", "Errors.Project.App.SAMLMetadataMissing") + } + provider.Metadata = data + } + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + events, err := filter(ctx, writeModel.Query()) + if err != nil { + return nil, err + } + writeModel.AppendEvents(events...) + if err = writeModel.Reduce(); err != nil { + return nil, err + } + if !writeModel.State.Exists() { + return nil, caos_errs.ThrowNotFound(nil, "INST-D3r1s", "Errors.IDPConfig.NotExisting") + } + event, err := writeModel.NewChangedEvent( + ctx, + &a.Aggregate, + writeModel.ID, + provider.Name, + provider.Metadata, + nil, + nil, + c.idpConfigEncryption, + provider.Binding, + provider.WithSignedRequest, + provider.IDPOptions, + ) + if err != nil || event == nil { + return nil, err + } + return []eventstore.Command{event}, nil + }, nil + } +} + +func (c *Commands) prepareRegenerateInstanceSAMLProviderCertificate(a *instance.Aggregate, writeModel *InstanceSAMLIDPWriteModel) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if writeModel.ID = strings.TrimSpace(writeModel.ID); writeModel.ID == "" { + return nil, caos_errs.ThrowInvalidArgument(nil, "INST-7de108gqya", "Errors.Invalid.Argument") + } + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + events, err := filter(ctx, writeModel.Query()) + if err != nil { + return nil, err + } + writeModel.AppendEvents(events...) + if err = writeModel.Reduce(); err != nil { + return nil, err + } + if !writeModel.State.Exists() { + return nil, caos_errs.ThrowNotFound(nil, "INST-76dbwsv9vm", "Errors.IDPConfig.NotExisting") + } + + key, cert, err := c.samlCertificateAndKeyGenerator(writeModel.ID) + if err != nil { + return nil, err + } + event, err := writeModel.NewChangedEvent( + ctx, + &a.Aggregate, + writeModel.ID, + writeModel.Name, + writeModel.Metadata, + key, + cert, + c.idpConfigEncryption, + writeModel.Binding, + writeModel.WithSignedRequest, + writeModel.Options, + ) + if err != nil || event == nil { + return nil, err + } + return []eventstore.Command{event}, nil + }, nil + } +} + func (c *Commands) prepareDeleteInstanceProvider(a *instance.Aggregate, id string) preparation.Validation { return func() (preparation.CreateCommands, error) { return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { diff --git a/internal/command/instance_idp_model.go b/internal/command/instance_idp_model.go index 1d1d33d313..87bd8de3c8 100644 --- a/internal/command/instance_idp_model.go +++ b/internal/command/instance_idp_model.go @@ -860,6 +860,79 @@ func (wm *InstanceAppleIDPWriteModel) NewChangedEvent( return instance.NewAppleIDPChangedEvent(ctx, aggregate, id, changes) } +type InstanceSAMLIDPWriteModel struct { + SAMLIDPWriteModel +} + +func NewSAMLInstanceIDPWriteModel(instanceID, id string) *InstanceSAMLIDPWriteModel { + return &InstanceSAMLIDPWriteModel{ + SAMLIDPWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: instanceID, + ResourceOwner: instanceID, + }, + ID: id, + }, + } +} + +func (wm *InstanceSAMLIDPWriteModel) AppendEvents(events ...eventstore.Event) { + for _, event := range events { + switch e := event.(type) { + case *instance.SAMLIDPAddedEvent: + wm.SAMLIDPWriteModel.AppendEvents(&e.SAMLIDPAddedEvent) + case *instance.SAMLIDPChangedEvent: + wm.SAMLIDPWriteModel.AppendEvents(&e.SAMLIDPChangedEvent) + case *instance.IDPRemovedEvent: + wm.SAMLIDPWriteModel.AppendEvents(&e.RemovedEvent) + } + } +} + +func (wm *InstanceSAMLIDPWriteModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + ResourceOwner(wm.ResourceOwner). + AddQuery(). + AggregateTypes(instance.AggregateType). + AggregateIDs(wm.AggregateID). + EventTypes( + instance.SAMLIDPAddedEventType, + instance.SAMLIDPChangedEventType, + instance.IDPRemovedEventType, + ). + EventData(map[string]interface{}{"id": wm.ID}). + Builder() +} + +func (wm *InstanceSAMLIDPWriteModel) NewChangedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + id, + name string, + metadata, + key, + certificate []byte, + secretCrypto crypto.Crypto, + binding string, + withSignedRequest bool, + options idp.Options, +) (*instance.SAMLIDPChangedEvent, error) { + changes, err := wm.SAMLIDPWriteModel.NewChanges( + name, + metadata, + key, + certificate, + secretCrypto, + binding, + withSignedRequest, + options, + ) + if err != nil || len(changes) == 0 { + return nil, err + } + return instance.NewSAMLIDPChangedEvent(ctx, aggregate, id, changes) +} + type InstanceIDPRemoveWriteModel struct { IDPRemoveWriteModel } @@ -897,6 +970,8 @@ func (wm *InstanceIDPRemoveWriteModel) AppendEvents(events ...eventstore.Event) wm.IDPRemoveWriteModel.AppendEvents(&e.GitLabSelfHostedIDPAddedEvent) case *instance.GoogleIDPAddedEvent: wm.IDPRemoveWriteModel.AppendEvents(&e.GoogleIDPAddedEvent) + case *instance.SAMLIDPAddedEvent: + wm.IDPRemoveWriteModel.AppendEvents(&e.SAMLIDPAddedEvent) case *instance.LDAPIDPAddedEvent: wm.IDPRemoveWriteModel.AppendEvents(&e.LDAPIDPAddedEvent) case *instance.AppleIDPAddedEvent: @@ -931,6 +1006,7 @@ func (wm *InstanceIDPRemoveWriteModel) Query() *eventstore.SearchQueryBuilder { instance.GoogleIDPAddedEventType, instance.LDAPIDPAddedEventType, instance.AppleIDPAddedEventType, + instance.SAMLIDPAddedEventType, instance.IDPRemovedEventType, ). EventData(map[string]interface{}{"id": wm.ID}). diff --git a/internal/command/instance_idp_test.go b/internal/command/instance_idp_test.go index 794109cfab..a0be4798c6 100644 --- a/internal/command/instance_idp_test.go +++ b/internal/command/instance_idp_test.go @@ -5318,3 +5318,527 @@ func TestCommandSide_UpdateInstanceAppleIDP(t *testing.T) { }) } } + +func TestCommandSide_AddInstanceSAMLIDP(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + idGenerator id.Generator + secretCrypto crypto.EncryptionAlgorithm + certificateAndKeyGenerator func(id string) ([]byte, []byte, error) + } + type args struct { + ctx context.Context + provider SAMLProvider + } + type res struct { + id string + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + "invalid name", + fields{ + eventstore: eventstoreExpect(t), + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"), + }, + args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + provider: SAMLProvider{}, + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-o07zjotgnd", "")) + }, + }, + }, + { + "invalid metadata", + fields{ + eventstore: eventstoreExpect(t), + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"), + }, + args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + provider: SAMLProvider{ + Name: "name", + }, + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-3bi3esi16t", "Errors.Invalid.Argument")) + }, + }, + }, + { + name: "ok", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter(), + expectPush( + []*repository.Event{ + eventFromEventPusherWithInstanceID( + "instance1", + instance.NewSAMLIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate, + "id1", + "name", + []byte("metadata"), + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("key"), + }, + []byte("certificate"), + "", + false, + idp.Options{}, + )), + }, + ), + ), + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"), + secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + certificateAndKeyGenerator: func(id string) ([]byte, []byte, error) { return []byte("key"), []byte("certificate"), nil }, + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + provider: SAMLProvider{ + Name: "name", + Metadata: []byte("metadata"), + }, + }, + res: res{ + id: "id1", + want: &domain.ObjectDetails{ResourceOwner: "instance1"}, + }, + }, + { + name: "ok all set", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter(), + expectPush( + []*repository.Event{ + eventFromEventPusherWithInstanceID( + "instance1", + instance.NewSAMLIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate, + "id1", + "name", + []byte("metadata"), + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("key"), + }, + []byte("certificate"), + "binding", + true, + idp.Options{ + IsCreationAllowed: true, + IsLinkingAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + }, + )), + }, + ), + ), + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"), + secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + certificateAndKeyGenerator: func(id string) ([]byte, []byte, error) { return []byte("key"), []byte("certificate"), nil }, + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + provider: SAMLProvider{ + Name: "name", + Metadata: []byte("metadata"), + Binding: "binding", + WithSignedRequest: true, + IDPOptions: idp.Options{ + IsCreationAllowed: true, + IsLinkingAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + }, + }, + }, + res: res{ + id: "id1", + want: &domain.ObjectDetails{ResourceOwner: "instance1"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + eventstore: tt.fields.eventstore, + idGenerator: tt.fields.idGenerator, + idpConfigEncryption: tt.fields.secretCrypto, + samlCertificateAndKeyGenerator: tt.fields.certificateAndKeyGenerator, + } + id, got, err := c.AddInstanceSAMLProvider(tt.args.ctx, tt.args.provider) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if tt.res.err == nil { + assert.Equal(t, tt.res.id, id) + assert.Equal(t, tt.res.want, got) + } + }) + } +} + +func TestCommandSide_UpdateInstanceGenericSAMLIDP(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + secretCrypto crypto.EncryptionAlgorithm + } + type args struct { + ctx context.Context + id string + provider SAMLProvider + } + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + "invalid id", + fields{ + eventstore: eventstoreExpect(t), + }, + args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + provider: SAMLProvider{}, + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-7o3rq1owpm", "")) + }, + }, + }, + { + "invalid name", + fields{ + eventstore: eventstoreExpect(t), + }, + args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + id: "id1", + provider: SAMLProvider{}, + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-q2s9rak7o9", "")) + }, + }, + }, + { + "invalid metadata", + fields{ + eventstore: eventstoreExpect(t), + }, + args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + id: "id1", + provider: SAMLProvider{ + Name: "name", + }, + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-iw1rxnf4sf", "")) + }, + }, + }, + { + name: "not found", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter(), + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + id: "id1", + provider: SAMLProvider{ + Name: "name", + Metadata: []byte("metadata"), + }, + }, + res: res{ + err: caos_errors.IsNotFound, + }, + }, + { + name: "no changes", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter( + eventFromEventPusher( + instance.NewSAMLIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate, + "id1", + "name", + []byte("metadata"), + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("key"), + }, + []byte("certificate"), + "", + false, + idp.Options{}, + )), + ), + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + id: "id1", + provider: SAMLProvider{ + Name: "name", + Metadata: []byte("metadata"), + }, + }, + res: res{ + want: &domain.ObjectDetails{ResourceOwner: "instance1"}, + }, + }, + { + name: "change ok", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter( + eventFromEventPusher( + instance.NewSAMLIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate, + "id1", + "name", + []byte("metadata"), + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("key"), + }, + []byte("certificate"), + "binding", + false, + idp.Options{}, + )), + ), + expectPush( + []*repository.Event{ + eventFromEventPusherWithInstanceID( + "instance1", + func() eventstore.Command { + t := true + event, _ := instance.NewSAMLIDPChangedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate, + "id1", + []idp.SAMLIDPChanges{ + idp.ChangeSAMLName("new name"), + idp.ChangeSAMLMetadata([]byte("new metadata")), + idp.ChangeSAMLBinding("new binding"), + idp.ChangeSAMLWithSignedRequest(true), + idp.ChangeSAMLOptions(idp.OptionChanges{ + IsCreationAllowed: &t, + IsLinkingAllowed: &t, + IsAutoCreation: &t, + IsAutoUpdate: &t, + }), + }, + ) + return event + }(), + ), + }, + ), + ), + secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + id: "id1", + provider: SAMLProvider{ + Name: "new name", + Metadata: []byte("new metadata"), + Binding: "new binding", + WithSignedRequest: true, + IDPOptions: idp.Options{ + IsCreationAllowed: true, + IsLinkingAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + }, + }, + }, + res: res{ + want: &domain.ObjectDetails{ResourceOwner: "instance1"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + eventstore: tt.fields.eventstore, + idpConfigEncryption: tt.fields.secretCrypto, + } + got, err := c.UpdateInstanceSAMLProvider(tt.args.ctx, tt.args.id, tt.args.provider) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if tt.res.err == nil { + assert.Equal(t, tt.res.want, got) + } + }) + } +} + +func TestCommandSide_RegenerateInstanceSAMLProviderCertificate(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + secretCrypto crypto.EncryptionAlgorithm + certificateAndKeyGenerator func(id string) ([]byte, []byte, error) + } + type args struct { + ctx context.Context + id string + } + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + "invalid id", + fields{ + eventstore: eventstoreExpect(t), + }, + args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-7de108gqya", "")) + }, + }, + }, + { + name: "not found", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter(), + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + id: "id1", + }, + res: res{ + err: caos_errors.IsNotFound, + }, + }, + { + name: "change ok", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter( + eventFromEventPusher( + instance.NewSAMLIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate, + "id1", + "name", + []byte("metadata"), + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("key"), + }, + []byte("certificate"), + "binding", + false, + idp.Options{}, + )), + ), + expectPush( + []*repository.Event{ + eventFromEventPusherWithInstanceID( + "instance1", + func() eventstore.Command { + event, _ := instance.NewSAMLIDPChangedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate, + "id1", + []idp.SAMLIDPChanges{ + idp.ChangeSAMLKey(&crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("new key"), + }), + idp.ChangeSAMLCertificate([]byte("new certificate")), + }, + ) + return event + }(), + ), + }, + ), + ), + secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + certificateAndKeyGenerator: func(id string) ([]byte, []byte, error) { + return []byte("new key"), []byte("new certificate"), nil + }, + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + id: "id1", + }, + res: res{ + want: &domain.ObjectDetails{ResourceOwner: "instance1"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + eventstore: tt.fields.eventstore, + idpConfigEncryption: tt.fields.secretCrypto, + samlCertificateAndKeyGenerator: tt.fields.certificateAndKeyGenerator, + } + got, err := c.RegenerateInstanceSAMLProviderCertificate(tt.args.ctx, tt.args.id) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if tt.res.err == nil { + assert.Equal(t, tt.res.want, got) + } + }) + } +} diff --git a/internal/command/main_test.go b/internal/command/main_test.go index 2d798a40e8..ba886e5c4b 100644 --- a/internal/command/main_test.go +++ b/internal/command/main_test.go @@ -20,6 +20,7 @@ import ( "github.com/zitadel/zitadel/internal/eventstore/repository/mock" action_repo "github.com/zitadel/zitadel/internal/repository/action" "github.com/zitadel/zitadel/internal/repository/authrequest" + "github.com/zitadel/zitadel/internal/repository/feature" "github.com/zitadel/zitadel/internal/repository/idpintent" iam_repo "github.com/zitadel/zitadel/internal/repository/instance" key_repo "github.com/zitadel/zitadel/internal/repository/keypair" @@ -52,6 +53,7 @@ func eventstoreExpect(t *testing.T, expects ...expect) *eventstore.Eventstore { authrequest.RegisterEventMappers(es) oidcsession.RegisterEventMappers(es) quota_repo.RegisterEventMappers(es) + feature.RegisterEventMappers(es) return es } diff --git a/internal/command/org_idp.go b/internal/command/org_idp.go index 66afee2ec0..22479465c1 100644 --- a/internal/command/org_idp.go +++ b/internal/command/org_idp.go @@ -4,6 +4,8 @@ import ( "context" "strings" + "github.com/zitadel/saml/pkg/provider/xml" + "github.com/zitadel/zitadel/internal/command/preparation" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" @@ -444,6 +446,68 @@ func (c *Commands) UpdateOrgLDAPProvider(ctx context.Context, resourceOwner, id return pushedEventsToObjectDetails(pushedEvents), nil } +func (c *Commands) AddOrgSAMLProvider(ctx context.Context, resourceOwner string, provider SAMLProvider) (string, *domain.ObjectDetails, error) { + orgAgg := org.NewAggregate(resourceOwner) + id, err := c.idGenerator.Next() + if err != nil { + return "", nil, err + } + writeModel := NewSAMLOrgIDPWriteModel(resourceOwner, id) + cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddOrgSAMLProvider(orgAgg, writeModel, provider)) + if err != nil { + return "", nil, err + } + pushedEvents, err := c.eventstore.Push(ctx, cmds...) + if err != nil { + return "", nil, err + } + return id, pushedEventsToObjectDetails(pushedEvents), nil +} + +func (c *Commands) UpdateOrgSAMLProvider(ctx context.Context, resourceOwner, id string, provider SAMLProvider) (*domain.ObjectDetails, error) { + orgAgg := org.NewAggregate(resourceOwner) + writeModel := NewSAMLOrgIDPWriteModel(resourceOwner, id) + cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateOrgSAMLProvider(orgAgg, writeModel, provider)) + if err != nil { + return nil, err + } + if len(cmds) == 0 { + // no change, so return directly + return &domain.ObjectDetails{ + Sequence: writeModel.ProcessedSequence, + EventDate: writeModel.ChangeDate, + ResourceOwner: writeModel.ResourceOwner, + }, nil + } + pushedEvents, err := c.eventstore.Push(ctx, cmds...) + if err != nil { + return nil, err + } + return pushedEventsToObjectDetails(pushedEvents), nil +} + +func (c *Commands) RegenerateOrgSAMLProviderCertificate(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) { + orgAgg := org.NewAggregate(resourceOwner) + writeModel := NewSAMLOrgIDPWriteModel(resourceOwner, id) + cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareRegenerateOrgSAMLProviderCertificate(orgAgg, writeModel)) + if err != nil { + return nil, err + } + if len(cmds) == 0 { + // no change, so return directly + return &domain.ObjectDetails{ + Sequence: writeModel.ProcessedSequence, + EventDate: writeModel.ChangeDate, + ResourceOwner: writeModel.ResourceOwner, + }, nil + } + pushedEvents, err := c.eventstore.Push(ctx, cmds...) + if err != nil { + return nil, err + } + return pushedEventsToObjectDetails(pushedEvents), nil +} + func (c *Commands) AddOrgAppleProvider(ctx context.Context, resourceOwner string, provider AppleProvider) (string, *domain.ObjectDetails, error) { orgAgg := org.NewAggregate(resourceOwner) id, err := c.idGenerator.Next() @@ -1639,6 +1703,150 @@ func (c *Commands) prepareUpdateOrgAppleProvider(a *org.Aggregate, writeModel *O } } +func (c *Commands) prepareAddOrgSAMLProvider(a *org.Aggregate, writeModel *OrgSAMLIDPWriteModel, provider SAMLProvider) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" { + return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-957lr0f8u3", "Errors.Invalid.Argument") + } + if provider.Metadata == nil && provider.MetadataURL == "" { + return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-78isv6m53a", "Errors.Invalid.Argument") + } + if provider.Metadata == nil && provider.MetadataURL != "" { + data, err := xml.ReadMetadataFromURL(c.httpClient, provider.MetadataURL) + if err != nil { + return nil, caos_errs.ThrowInvalidArgument(err, "ORG-ipzxvf3cv2", "Errors.Project.App.SAMLMetadataMissing") + } + provider.Metadata = data + } + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + events, err := filter(ctx, writeModel.Query()) + if err != nil { + return nil, err + } + writeModel.AppendEvents(events...) + if err = writeModel.Reduce(); err != nil { + return nil, err + } + key, cert, err := c.samlCertificateAndKeyGenerator(writeModel.ID) + if err != nil { + return nil, err + } + keyEnc, err := crypto.Encrypt(key, c.idpConfigEncryption) + if err != nil { + return nil, err + } + return []eventstore.Command{ + org.NewSAMLIDPAddedEvent( + ctx, + &a.Aggregate, + writeModel.ID, + provider.Name, + provider.Metadata, + keyEnc, + cert, + provider.Binding, + provider.WithSignedRequest, + provider.IDPOptions, + ), + }, nil + }, nil + } +} + +func (c *Commands) prepareUpdateOrgSAMLProvider(a *org.Aggregate, writeModel *OrgSAMLIDPWriteModel, provider SAMLProvider) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if writeModel.ID = strings.TrimSpace(writeModel.ID); writeModel.ID == "" { + return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-wwdwdlaya0", "Errors.Invalid.Argument") + } + if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" { + return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-egixaofgyl", "Errors.Invalid.Argument") + } + if provider.Metadata == nil && provider.MetadataURL != "" { + data, err := xml.ReadMetadataFromURL(c.httpClient, provider.MetadataURL) + if err != nil { + return nil, caos_errs.ThrowInvalidArgument(err, "ORG-bkaiyd3rfo", "Errors.Project.App.SAMLMetadataMissing") + } + provider.Metadata = data + } + if provider.Metadata == nil { + return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-j6spncd74m", "Errors.Invalid.Argument") + } + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + events, err := filter(ctx, writeModel.Query()) + if err != nil { + return nil, err + } + writeModel.AppendEvents(events...) + if err = writeModel.Reduce(); err != nil { + return nil, err + } + if !writeModel.State.Exists() { + return nil, caos_errs.ThrowNotFound(nil, "ORG-z82dddndql", "Errors.Org.IDPConfig.NotExisting") + } + event, err := writeModel.NewChangedEvent( + ctx, + &a.Aggregate, + writeModel.ID, + provider.Name, + provider.Metadata, + nil, + nil, + c.idpConfigEncryption, + provider.Binding, + provider.WithSignedRequest, + provider.IDPOptions, + ) + if err != nil || event == nil { + return nil, err + } + return []eventstore.Command{event}, nil + }, nil + } +} + +func (c *Commands) prepareRegenerateOrgSAMLProviderCertificate(a *org.Aggregate, writeModel *OrgSAMLIDPWriteModel) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if writeModel.ID = strings.TrimSpace(writeModel.ID); writeModel.ID == "" { + return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-arv4vdrb6c", "Errors.Invalid.Argument") + } + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + events, err := filter(ctx, writeModel.Query()) + if err != nil { + return nil, err + } + writeModel.AppendEvents(events...) + if err = writeModel.Reduce(); err != nil { + return nil, err + } + if !writeModel.State.Exists() { + return nil, caos_errs.ThrowNotFound(nil, "ORG-4dw21ch9o9", "Errors.Org.IDPConfig.NotExisting") + } + + key, cert, err := c.samlCertificateAndKeyGenerator(writeModel.ID) + if err != nil { + return nil, err + } + event, err := writeModel.NewChangedEvent( + ctx, + &a.Aggregate, + writeModel.ID, + writeModel.Name, + writeModel.Metadata, + key, + cert, + c.idpConfigEncryption, + writeModel.Binding, + writeModel.WithSignedRequest, + writeModel.Options, + ) + if err != nil || event == nil { + return nil, err + } + return []eventstore.Command{event}, nil + }, nil + } +} + func (c *Commands) prepareDeleteOrgProvider(a *org.Aggregate, resourceOwner, id string) preparation.Validation { return func() (preparation.CreateCommands, error) { return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { diff --git a/internal/command/org_idp_model.go b/internal/command/org_idp_model.go index a543de5079..04eeeb8ac2 100644 --- a/internal/command/org_idp_model.go +++ b/internal/command/org_idp_model.go @@ -870,6 +870,81 @@ func (wm *OrgAppleIDPWriteModel) NewChangedEvent( return org.NewAppleIDPChangedEvent(ctx, aggregate, id, changes) } +type OrgSAMLIDPWriteModel struct { + SAMLIDPWriteModel +} + +func NewSAMLOrgIDPWriteModel(orgID, id string) *OrgSAMLIDPWriteModel { + return &OrgSAMLIDPWriteModel{ + SAMLIDPWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: orgID, + ResourceOwner: orgID, + }, + ID: id, + }, + } +} + +func (wm *OrgSAMLIDPWriteModel) AppendEvents(events ...eventstore.Event) { + for _, event := range events { + switch e := event.(type) { + case *org.SAMLIDPAddedEvent: + wm.SAMLIDPWriteModel.AppendEvents(&e.SAMLIDPAddedEvent) + case *org.SAMLIDPChangedEvent: + wm.SAMLIDPWriteModel.AppendEvents(&e.SAMLIDPChangedEvent) + case *org.IDPRemovedEvent: + wm.SAMLIDPWriteModel.AppendEvents(&e.RemovedEvent) + default: + wm.SAMLIDPWriteModel.AppendEvents(e) + } + } +} + +func (wm *OrgSAMLIDPWriteModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + ResourceOwner(wm.ResourceOwner). + AddQuery(). + AggregateTypes(org.AggregateType). + AggregateIDs(wm.AggregateID). + EventTypes( + org.SAMLIDPAddedEventType, + org.SAMLIDPChangedEventType, + org.IDPRemovedEventType, + ). + EventData(map[string]interface{}{"id": wm.ID}). + Builder() +} + +func (wm *OrgSAMLIDPWriteModel) NewChangedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + id, + name string, + metadata, + key, + certificate []byte, + secretCrypto crypto.Crypto, + binding string, + withSignedRequest bool, + options idp.Options, +) (*org.SAMLIDPChangedEvent, error) { + changes, err := wm.SAMLIDPWriteModel.NewChanges( + name, + metadata, + key, + certificate, + secretCrypto, + binding, + withSignedRequest, + options, + ) + if err != nil || len(changes) == 0 { + return nil, err + } + return org.NewSAMLIDPChangedEvent(ctx, aggregate, id, changes) +} + type OrgIDPRemoveWriteModel struct { IDPRemoveWriteModel } @@ -911,6 +986,8 @@ func (wm *OrgIDPRemoveWriteModel) AppendEvents(events ...eventstore.Event) { wm.IDPRemoveWriteModel.AppendEvents(&e.LDAPIDPAddedEvent) case *org.AppleIDPAddedEvent: wm.IDPRemoveWriteModel.AppendEvents(&e.AppleIDPAddedEvent) + case *org.SAMLIDPAddedEvent: + wm.IDPRemoveWriteModel.AppendEvents(&e.SAMLIDPAddedEvent) case *org.IDPRemovedEvent: wm.IDPRemoveWriteModel.AppendEvents(&e.RemovedEvent) case *org.IDPConfigAddedEvent: @@ -941,6 +1018,7 @@ func (wm *OrgIDPRemoveWriteModel) Query() *eventstore.SearchQueryBuilder { org.GoogleIDPAddedEventType, org.LDAPIDPAddedEventType, org.AppleIDPAddedEventType, + org.SAMLIDPAddedEventType, org.IDPRemovedEventType, ). EventData(map[string]interface{}{"id": wm.ID}). diff --git a/internal/command/org_idp_test.go b/internal/command/org_idp_test.go index 641abb90b2..f85fb1c216 100644 --- a/internal/command/org_idp_test.go +++ b/internal/command/org_idp_test.go @@ -5396,3 +5396,534 @@ func TestCommandSide_UpdateOrgAppleIDP(t *testing.T) { func stringPointer(s string) *string { return &s } + +func TestCommandSide_AddOrgSAMLIDP(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + idGenerator id.Generator + secretCrypto crypto.EncryptionAlgorithm + certificateAndKeyGenerator func(id string) ([]byte, []byte, error) + } + type args struct { + ctx context.Context + resourceOwner string + provider SAMLProvider + } + type res struct { + id string + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + "invalid name", + fields{ + eventstore: eventstoreExpect(t), + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"), + }, + args{ + ctx: context.Background(), + resourceOwner: "org1", + provider: SAMLProvider{}, + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-957lr0f8u3", "")) + }, + }, + }, + { + "invalid metadata", + fields{ + eventstore: eventstoreExpect(t), + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"), + }, + args{ + ctx: context.Background(), + resourceOwner: "org1", + provider: SAMLProvider{ + Name: "name", + }, + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-78isv6m53a", "")) + }, + }, + }, + { + name: "ok", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter(), + expectPush( + eventPusherToEvents( + org.NewSAMLIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, + "id1", + "name", + []byte("metadata"), + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("key"), + }, + []byte("certificate"), + "", + false, + idp.Options{}, + )), + ), + ), + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"), + secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), certificateAndKeyGenerator: func(id string) ([]byte, []byte, error) { return []byte("key"), []byte("certificate"), nil }, + }, + args: args{ + ctx: context.Background(), + resourceOwner: "org1", + provider: SAMLProvider{ + Name: "name", + Metadata: []byte("metadata"), + }, + }, + res: res{ + id: "id1", + want: &domain.ObjectDetails{ResourceOwner: "org1"}, + }, + }, + { + name: "ok all set", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter(), + expectPush( + eventPusherToEvents( + org.NewSAMLIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, + "id1", + "name", + []byte("metadata"), + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("key"), + }, + []byte("certificate"), + "binding", + true, + idp.Options{ + IsCreationAllowed: true, + IsLinkingAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + }, + )), + ), + ), + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"), + secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + certificateAndKeyGenerator: func(id string) ([]byte, []byte, error) { return []byte("key"), []byte("certificate"), nil }, + }, + args: args{ + ctx: context.Background(), + resourceOwner: "org1", + provider: SAMLProvider{ + Name: "name", + Metadata: []byte("metadata"), + Binding: "binding", + WithSignedRequest: true, + IDPOptions: idp.Options{ + IsCreationAllowed: true, + IsLinkingAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + }, + }, + }, + res: res{ + id: "id1", + want: &domain.ObjectDetails{ResourceOwner: "org1"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + eventstore: tt.fields.eventstore, + idGenerator: tt.fields.idGenerator, + idpConfigEncryption: tt.fields.secretCrypto, + samlCertificateAndKeyGenerator: tt.fields.certificateAndKeyGenerator, + } + id, got, err := c.AddOrgSAMLProvider(tt.args.ctx, tt.args.resourceOwner, tt.args.provider) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if tt.res.err == nil { + assert.Equal(t, tt.res.id, id) + assert.Equal(t, tt.res.want, got) + } + }) + } +} + +func TestCommandSide_UpdateOrgSAMLIDP(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + secretCrypto crypto.EncryptionAlgorithm + } + type args struct { + ctx context.Context + resourceOwner string + id string + provider SAMLProvider + } + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + "invalid id", + fields{ + eventstore: eventstoreExpect(t), + }, + args{ + ctx: context.Background(), + resourceOwner: "org1", + provider: SAMLProvider{}, + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-wwdwdlaya0", "")) + }, + }, + }, + { + "invalid name", + fields{ + eventstore: eventstoreExpect(t), + }, + args{ + ctx: context.Background(), + resourceOwner: "org1", + id: "id1", + provider: SAMLProvider{}, + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-egixaofgyl", "")) + }, + }, + }, + { + "invalid metadata", + fields{ + eventstore: eventstoreExpect(t), + }, + args{ + ctx: context.Background(), + resourceOwner: "org1", + id: "id1", + provider: SAMLProvider{ + Name: "name", + }, + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-j6spncd74m", "")) + }, + }, + }, + { + name: "not found", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter(), + ), + }, + args: args{ + ctx: context.Background(), + resourceOwner: "org1", + id: "id1", + provider: SAMLProvider{ + Name: "name", + Metadata: []byte("metadata"), + }, + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowNotFound(nil, "ORG-z82dddndql", "")) + }, + }, + }, + { + name: "no changes", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter( + eventFromEventPusher( + org.NewSAMLIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, + "id1", + "name", + []byte("metadata"), + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("key"), + }, + []byte("certificate"), + "", + false, + idp.Options{}, + )), + ), + ), + }, + args: args{ + ctx: context.Background(), + resourceOwner: "org1", + id: "id1", + provider: SAMLProvider{ + Name: "name", + Metadata: []byte("metadata"), + }, + }, + res: res{ + want: &domain.ObjectDetails{ResourceOwner: "org1"}, + }, + }, + { + name: "change ok", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter( + eventFromEventPusher( + org.NewSAMLIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, + "id1", + "name", + []byte("metadata"), + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("key"), + }, + []byte("certificate"), + "binding", + false, + idp.Options{}, + )), + ), + expectPush( + eventPusherToEvents( + func() eventstore.Command { + t := true + event, _ := org.NewSAMLIDPChangedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, + "id1", + []idp.SAMLIDPChanges{ + idp.ChangeSAMLName("new name"), + idp.ChangeSAMLMetadata([]byte("new metadata")), + idp.ChangeSAMLBinding("new binding"), + idp.ChangeSAMLWithSignedRequest(true), + idp.ChangeSAMLOptions(idp.OptionChanges{ + IsCreationAllowed: &t, + IsLinkingAllowed: &t, + IsAutoCreation: &t, + IsAutoUpdate: &t, + }), + }, + ) + return event + }(), + ), + ), + ), + secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + args: args{ + ctx: context.Background(), + resourceOwner: "org1", + id: "id1", + provider: SAMLProvider{ + Name: "new name", + Metadata: []byte("new metadata"), + Binding: "new binding", + WithSignedRequest: true, + IDPOptions: idp.Options{ + IsCreationAllowed: true, + IsLinkingAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + }, + }, + }, + res: res{ + want: &domain.ObjectDetails{ResourceOwner: "org1"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + eventstore: tt.fields.eventstore, + idpConfigEncryption: tt.fields.secretCrypto, + } + got, err := c.UpdateOrgSAMLProvider(tt.args.ctx, tt.args.resourceOwner, tt.args.id, tt.args.provider) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if tt.res.err == nil { + assert.Equal(t, tt.res.want, got) + } + }) + } +} + +func TestCommandSide_RegenerateOrgSAMLProviderCertificate(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + secretCrypto crypto.EncryptionAlgorithm + certificateAndKeyGenerator func(id string) ([]byte, []byte, error) + } + type args struct { + ctx context.Context + resourceOwner string + id string + } + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + "invalid id", + fields{ + eventstore: eventstoreExpect(t), + }, + args{ + ctx: context.Background(), + resourceOwner: "org1", + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-arv4vdrb6c", "")) + }, + }, + }, + { + name: "not found", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter(), + ), + }, + args: args{ + ctx: context.Background(), + resourceOwner: "org1", + id: "id1", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowNotFound(nil, "ORG-4dw21ch9o9", "")) + }, + }, + }, + { + name: "change ok", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter( + eventFromEventPusher( + org.NewSAMLIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, + "id1", + "name", + []byte("metadata"), + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("key"), + }, + []byte("certificate"), + "binding", + false, + idp.Options{}, + )), + ), + expectPush( + eventPusherToEvents( + func() eventstore.Command { + event, _ := org.NewSAMLIDPChangedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, + "id1", + []idp.SAMLIDPChanges{ + idp.ChangeSAMLKey(&crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("new key"), + }), + idp.ChangeSAMLCertificate([]byte("new certificate")), + }, + ) + return event + }(), + ), + ), + ), + secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + certificateAndKeyGenerator: func(id string) ([]byte, []byte, error) { + return []byte("new key"), []byte("new certificate"), nil + }, + }, + args: args{ + ctx: context.Background(), + resourceOwner: "org1", + id: "id1", + }, + res: res{ + want: &domain.ObjectDetails{ResourceOwner: "org1"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + eventstore: tt.fields.eventstore, + idpConfigEncryption: tt.fields.secretCrypto, + samlCertificateAndKeyGenerator: tt.fields.certificateAndKeyGenerator, + } + got, err := c.RegenerateOrgSAMLProviderCertificate(tt.args.ctx, tt.args.resourceOwner, tt.args.id) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if tt.res.err == nil { + assert.Equal(t, tt.res.want, got) + } + }) + } +} diff --git a/internal/command/quota.go b/internal/command/quota.go index 442772ae57..d3b1ab7e89 100644 --- a/internal/command/quota.go +++ b/internal/command/quota.go @@ -167,9 +167,6 @@ func (q *SetQuota) validate() error { if q.Unit.Enum() == quota.Unimplemented { return errors.ThrowInvalidArgument(nil, "QUOTA-OTeSh", "Errors.Quota.Invalid.Unimplemented") } - if q.Amount < 0 { - return errors.ThrowInvalidArgument(nil, "QUOTA-hOKSJ", "Errors.Quota.Invalid.Amount") - } if q.ResetInterval < time.Minute { return errors.ThrowInvalidArgument(nil, "QUOTA-R5otd", "Errors.Quota.Invalid.ResetInterval") } diff --git a/internal/command/quota_model.go b/internal/command/quota_model.go index 7336be41a6..23893ea6a2 100644 --- a/internal/command/quota_model.go +++ b/internal/command/quota_model.go @@ -178,7 +178,7 @@ func sortSetEventNotifications(notifications []*quota.SetEventNotification) (err } if i.Percent < j.Percent || i.Percent == j.Percent && i.CallURL < j.CallURL || - i.Percent == j.Percent && i.CallURL == j.CallURL && i.Repeat == false && j.Repeat == true { + i.Percent == j.Percent && i.CallURL == j.CallURL && !i.Repeat && j.Repeat { return -1 } return +1 diff --git a/internal/command/quota_model_test.go b/internal/command/quota_model_test.go index 799c156fa1..3ba9847200 100644 --- a/internal/command/quota_model_test.go +++ b/internal/command/quota_model_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" + zitadel_errors "github.com/zitadel/zitadel/internal/errors" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/id" id_mock "github.com/zitadel/zitadel/internal/id/mock" @@ -33,11 +34,12 @@ func TestQuotaWriteModel_NewChanges(t *testing.T) { notifications []*QuotaNotification } tests := []struct { - name string - fields fields - args args - wantEvent quota.SetEvent - wantErr assert.ErrorAssertionFunc + name string + fields fields + args args + wantEvent quota.SetEvent + wantChanges int + wantErr assert.ErrorAssertionFunc }{{ name: "change reset interval", fields: fields{ @@ -54,10 +56,10 @@ func TestQuotaWriteModel_NewChanges(t *testing.T) { limit: true, notifications: make([]*QuotaNotification, 0), }, + wantChanges: 1, wantEvent: quota.SetEvent{ ResetInterval: durationPtr(time.Minute), }, - wantErr: assert.NoError, }, { name: "change reset interval and amount", fields: fields{ @@ -74,11 +76,11 @@ func TestQuotaWriteModel_NewChanges(t *testing.T) { limit: true, notifications: make([]*QuotaNotification, 0), }, + wantChanges: 2, wantEvent: quota.SetEvent{ ResetInterval: durationPtr(time.Minute), Amount: uint64Ptr(10), }, - wantErr: assert.NoError, }, { name: "change nothing", fields: fields{ @@ -95,8 +97,6 @@ func TestQuotaWriteModel_NewChanges(t *testing.T) { limit: true, notifications: []*QuotaNotification{}, }, - wantEvent: quota.SetEvent{}, - wantErr: assert.NoError, }, { name: "change limit to zero value", fields: fields{ @@ -113,8 +113,8 @@ func TestQuotaWriteModel_NewChanges(t *testing.T) { limit: false, notifications: make([]*QuotaNotification, 0), }, - wantEvent: quota.SetEvent{Limit: boolPtr(false)}, - wantErr: assert.NoError, + wantChanges: 1, + wantEvent: quota.SetEvent{Limit: boolPtr(false)}, }, { name: "change amount to zero value", fields: fields{ @@ -131,8 +131,8 @@ func TestQuotaWriteModel_NewChanges(t *testing.T) { limit: true, notifications: make([]*QuotaNotification, 0), }, - wantEvent: quota.SetEvent{Amount: uint64Ptr(0)}, - wantErr: assert.NoError, + wantChanges: 1, + wantEvent: quota.SetEvent{Amount: uint64Ptr(0)}, }, { name: "change from to zero value", fields: fields{ @@ -149,8 +149,8 @@ func TestQuotaWriteModel_NewChanges(t *testing.T) { limit: true, notifications: make([]*QuotaNotification, 0), }, - wantEvent: quota.SetEvent{From: &time.Time{}}, - wantErr: assert.NoError, + wantChanges: 1, + wantEvent: quota.SetEvent{From: &time.Time{}}, }, { name: "add notification", fields: fields{ @@ -158,12 +158,6 @@ func TestQuotaWriteModel_NewChanges(t *testing.T) { from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), resetInterval: time.Hour, limit: true, - notifications: []*quota.SetEventNotification{{ - ID: "notification1", - Percent: 10, - Repeat: true, - CallURL: "https://call.url", - }}, }, args: args{ amount: 5, @@ -171,19 +165,19 @@ func TestQuotaWriteModel_NewChanges(t *testing.T) { resetInterval: time.Hour, limit: true, notifications: []*QuotaNotification{{ - Percent: 20, - Repeat: true, + Percent: 10, + Repeat: false, CallURL: "https://call.url", }}, idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "notification1"), }, + wantChanges: 1, wantEvent: quota.SetEvent{Notifications: &[]*quota.SetEventNotification{{ ID: "notification1", - Percent: 20, - Repeat: true, + Percent: 10, + Repeat: false, CallURL: "https://call.url", }}}, - wantErr: assert.NoError, }, { name: "change nothing with notification", fields: fields{ @@ -210,10 +204,8 @@ func TestQuotaWriteModel_NewChanges(t *testing.T) { }}, idGenerator: id_mock.NewIDGenerator(t), }, - wantEvent: quota.SetEvent{}, - wantErr: assert.NoError, }, { - name: "change nothing but notification order", + name: "don't change notification order", fields: fields{ amount: 5, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), @@ -247,8 +239,6 @@ func TestQuotaWriteModel_NewChanges(t *testing.T) { }}, idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "newnotification1", "newnotification2"), }, - wantEvent: quota.SetEvent{}, - wantErr: assert.NoError, }, { name: "change notification to zero value", fields: fields{ @@ -270,33 +260,72 @@ func TestQuotaWriteModel_NewChanges(t *testing.T) { limit: true, notifications: []*QuotaNotification{}, }, - wantEvent: quota.SetEvent{Notifications: &[]*quota.SetEventNotification{}}, - wantErr: assert.NoError, + wantChanges: 1, + wantEvent: quota.SetEvent{Notifications: &[]*quota.SetEventNotification{}}, }, { - name: "create new without notification", + name: "validate no duplicate notifications", + args: args{ + notifications: []*QuotaNotification{{ + Percent: 10, + Repeat: false, + CallURL: "https://call.url", + }, { + Percent: 10, + Repeat: false, + CallURL: "https://call.url", + }}, + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "notification1", "notification2"), + }, + wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { + return zitadel_errors.IsErrorInvalidArgument(err) + }, + }, { + name: "deduplicate existing notifications", fields: fields{ - amount: 5, - from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - resetInterval: time.Hour, - limit: true, notifications: []*quota.SetEventNotification{{ - ID: "notification1", + ID: "existingnotification1", + Percent: 10, + Repeat: false, + CallURL: "https://call.url", + }, { + ID: "existingnotification2", + Percent: 10, + Repeat: true, + CallURL: "https://call.url", + }, { + ID: "existingnotification3", Percent: 10, Repeat: true, CallURL: "https://call.url", }}, }, args: args{ - amount: 5, - from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - resetInterval: time.Hour, - limit: true, - notifications: []*QuotaNotification{}, + notifications: []*QuotaNotification{{ + Percent: 10, + Repeat: false, + CallURL: "https://call.url", + }, { + Percent: 10, + Repeat: true, + CallURL: "https://call.url", + }}, + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "notification1", "notification2"), }, - wantEvent: quota.SetEvent{Notifications: &[]*quota.SetEventNotification{}}, - wantErr: assert.NoError, + wantChanges: 1, + wantEvent: quota.SetEvent{ + Notifications: &[]*quota.SetEventNotification{{ + ID: "notification1", + Percent: 10, + Repeat: false, + CallURL: "https://call.url", + }, { + ID: "notification2", + Percent: 10, + Repeat: true, + CallURL: "https://call.url", + }}}, }, { - name: "create new with all values values", + name: "create new with all values", args: args{ amount: 5, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), @@ -310,6 +339,7 @@ func TestQuotaWriteModel_NewChanges(t *testing.T) { idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "notification1"), createNew: true, }, + wantChanges: 5, wantEvent: quota.SetEvent{ From: timePtr(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)), ResetInterval: durationPtr(time.Hour), @@ -322,10 +352,10 @@ func TestQuotaWriteModel_NewChanges(t *testing.T) { CallURL: "https://call.url", }}, }, - wantErr: assert.NoError, }, { - name: "create new with zero values", - args: args{createNew: true}, + name: "create new with zero values", + args: args{createNew: true}, + wantChanges: 5, wantEvent: quota.SetEvent{ From: &time.Time{}, ResetInterval: durationPtr(0), @@ -333,10 +363,7 @@ func TestQuotaWriteModel_NewChanges(t *testing.T) { Limit: boolPtr(false), Notifications: &[]*quota.SetEventNotification{}, }, - wantErr: assert.NoError, - }, - } - + }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { wm := "aWriteModel{ @@ -347,9 +374,12 @@ func TestQuotaWriteModel_NewChanges(t *testing.T) { notifications: tt.fields.notifications, } gotChanges, err := wm.NewChanges(tt.args.idGenerator, tt.args.createNew, tt.args.amount, tt.args.from, tt.args.resetInterval, tt.args.limit, tt.args.notifications...) - if !tt.wantErr(t, err, fmt.Sprintf("NewChanges(%v, %v, %v, %v, %v, %v)", tt.args.createNew, tt.args.amount, tt.args.from, tt.args.resetInterval, tt.args.limit, tt.args.notifications)) { + assert.Len(t, gotChanges, tt.wantChanges) + if tt.wantErr != nil { + tt.wantErr(t, err, fmt.Sprintf("NewChanges(%v, %v, %v, %v, %v, %v)", tt.args.createNew, tt.args.amount, tt.args.from, tt.args.resetInterval, tt.args.limit, tt.args.notifications)) return } + assert.NoError(t, err) marshalled, err := json.Marshal(quota.NewSetEvent( eventstore.NewBaseEventForPush( context.Background(), diff --git a/internal/command/user.go b/internal/command/user.go index 77b9acbcc9..15631a9609 100644 --- a/internal/command/user.go +++ b/internal/command/user.go @@ -42,9 +42,17 @@ func (c *Commands) ChangeUsername(ctx context.Context, orgID, userID, userName s if err != nil { return nil, errors.ThrowPreconditionFailed(err, "COMMAND-38fnu", "Errors.Org.DomainPolicy.NotExisting") } - - if err := CheckDomainPolicyForUserName(userName, domainPolicy); err != nil { - return nil, err + if !domainPolicy.UserLoginMustBeDomain { + index := strings.LastIndex(userName, "@") + if index > 1 { + domainCheck := NewOrgDomainVerifiedWriteModel(userName[index+1:]) + if err := c.eventstore.FilterToQueryReducer(ctx, domainCheck); err != nil { + return nil, err + } + if domainCheck.Verified && domainCheck.ResourceOwner != orgID { + return nil, errors.ThrowInvalidArgument(nil, "COMMAND-Di2ei", "Errors.User.DomainNotAllowedAsUsername") + } + } } userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) diff --git a/internal/command/user_model.go b/internal/command/user_model.go index d8f671b462..7edfacfebf 100644 --- a/internal/command/user_model.go +++ b/internal/command/user_model.go @@ -1,12 +1,9 @@ package command import ( - "strings" - "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/domain" - caos_errors "github.com/zitadel/zitadel/internal/errors" "github.com/zitadel/zitadel/internal/repository/user" ) @@ -126,16 +123,6 @@ func UserAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregat return eventstore.AggregateFromWriteModel(wm, user.AggregateType, user.AggregateVersion) } -func CheckDomainPolicyForUserName(userName string, policy *domain.DomainPolicy) error { - if policy == nil { - return caos_errors.ThrowPreconditionFailed(nil, "COMMAND-3Mb9s", "Errors.Users.DomainPolicyNil") - } - if policy.UserLoginMustBeDomain && strings.Contains(userName, "@") { - return caos_errors.ThrowPreconditionFailed(nil, "COMMAND-2k9fD", "Errors.User.EmailAsUsernameNotAllowed") - } - return nil -} - func isUserStateExists(state domain.UserState) bool { return !hasUserState(state, domain.UserStateDeleted, domain.UserStateUnspecified) } diff --git a/internal/command/user_test.go b/internal/command/user_test.go index 23dae95062..a6f16ee32e 100644 --- a/internal/command/user_test.go +++ b/internal/command/user_test.go @@ -23,7 +23,7 @@ import ( func TestCommandSide_UsernameChange(t *testing.T) { type fields struct { - eventstore *eventstore.Eventstore + eventstore func(*testing.T) *eventstore.Eventstore } type ( args struct { @@ -46,9 +46,7 @@ func TestCommandSide_UsernameChange(t *testing.T) { { name: "userid missing, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), @@ -63,9 +61,7 @@ func TestCommandSide_UsernameChange(t *testing.T) { { name: "orgid missing, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), @@ -80,9 +76,7 @@ func TestCommandSide_UsernameChange(t *testing.T) { { name: "username missing, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), @@ -97,9 +91,7 @@ func TestCommandSide_UsernameChange(t *testing.T) { { name: "username only spaces, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), @@ -114,8 +106,7 @@ func TestCommandSide_UsernameChange(t *testing.T) { { name: "user removed, not found error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter(), ), }, @@ -132,8 +123,7 @@ func TestCommandSide_UsernameChange(t *testing.T) { { name: "username not changed, precondition error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( user.NewHumanAddedEvent(context.Background(), @@ -165,8 +155,7 @@ func TestCommandSide_UsernameChange(t *testing.T) { { name: "username not changed (spaces), precondition error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( user.NewHumanAddedEvent(context.Background(), @@ -198,8 +187,7 @@ func TestCommandSide_UsernameChange(t *testing.T) { { name: "org iam policy not found, precondition error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( user.NewHumanAddedEvent(context.Background(), @@ -229,10 +217,60 @@ func TestCommandSide_UsernameChange(t *testing.T) { }, }, { - name: "invalid username, precondition error", + name: "domain verified, wrong org", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + user.NewHumanAddedEvent(context.Background(), + &user.NewAggregate("user1", "org1").Aggregate, + "username", + "firstname", + "lastname", + "nickname", + "displayname", + language.German, + domain.GenderUnspecified, + "email@test.ch", + true, + ), + ), + ), + expectFilter(), + expectFilter( + eventFromEventPusher( + instance.NewDomainPolicyAddedEvent(context.Background(), + &user.NewAggregate("user1", "org1").Aggregate, + false, + true, + true, + ), + ), + ), + expectFilter( + eventFromEventPusher( + org.NewDomainVerifiedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "test.ch", + ), + ), + ), + ), + }, + args: args{ + ctx: context.Background(), + orgID: "wrong", + userID: "user1", + username: "test@test.ch", + }, + res: res{ + err: errors.IsErrorInvalidArgument, + }, + }, + { + name: "email as username, ok", + fields: fields{ + eventstore: expectEventstore( expectFilter( eventFromEventPusher( user.NewHumanAddedEvent(context.Background(), @@ -260,6 +298,20 @@ func TestCommandSide_UsernameChange(t *testing.T) { ), ), ), + expectPush( + []*repository.Event{ + eventFromEventPusher( + user.NewUsernameChangedEvent(context.Background(), + &user.NewAggregate("user1", "org1").Aggregate, + "username", + "test@test.ch", + true, + ), + ), + }, + uniqueConstraintsFromEventConstraint(user.NewRemoveUsernameUniqueConstraint("username", "org1", true)), + uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("test@test.ch", "org1", true)), + ), ), }, args: args{ @@ -269,14 +321,82 @@ func TestCommandSide_UsernameChange(t *testing.T) { username: "test@test.ch", }, res: res{ - err: errors.IsPreconditionFailed, + want: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, + { + name: "email as username, verified domain, ok", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + user.NewHumanAddedEvent(context.Background(), + &user.NewAggregate("user1", "org1").Aggregate, + "username", + "firstname", + "lastname", + "nickname", + "displayname", + language.German, + domain.GenderUnspecified, + "email@test.ch", + true, + ), + ), + ), + expectFilter(), + expectFilter( + eventFromEventPusher( + instance.NewDomainPolicyAddedEvent(context.Background(), + &user.NewAggregate("user1", "org1").Aggregate, + false, + true, + true, + ), + ), + ), + expectFilter( + eventFromEventPusher( + org.NewDomainVerifiedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "test.ch", + ), + ), + ), + expectPush( + []*repository.Event{ + eventFromEventPusher( + user.NewUsernameChangedEvent(context.Background(), + &user.NewAggregate("user1", "org1").Aggregate, + "username", + "test@test.ch", + true, + ), + ), + }, + uniqueConstraintsFromEventConstraint(user.NewRemoveUsernameUniqueConstraint("username", "org1", false)), + uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("test@test.ch", "org1", false)), + ), + ), + }, + args: args{ + ctx: context.Background(), + orgID: "org1", + userID: "user1", + username: "test@test.ch", + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, }, }, { name: "change username, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( user.NewHumanAddedEvent(context.Background(), @@ -335,8 +455,7 @@ func TestCommandSide_UsernameChange(t *testing.T) { { name: "change username (remove spaces), ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( user.NewHumanAddedEvent(context.Background(), @@ -396,7 +515,7 @@ func TestCommandSide_UsernameChange(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore, + eventstore: tt.fields.eventstore(t), } got, err := r.ChangeUsername(tt.args.ctx, tt.args.orgID, tt.args.userID, tt.args.username) if tt.res.err == nil { diff --git a/internal/config/hook/feature.go b/internal/config/hook/feature.go new file mode 100644 index 0000000000..5eccaa5706 --- /dev/null +++ b/internal/config/hook/feature.go @@ -0,0 +1,27 @@ +package hook + +import ( + "reflect" + + "github.com/mitchellh/mapstructure" + + "github.com/zitadel/zitadel/internal/domain" +) + +func StringToFeatureHookFunc() mapstructure.DecodeHookFuncType { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + + if t != reflect.TypeOf(domain.FeatureUnspecified) { + return data, nil + } + + return domain.FeatureString(data.(string)) + } +} diff --git a/internal/domain/auth_request.go b/internal/domain/auth_request.go index d5b1bf4fe9..7d776b6d30 100644 --- a/internal/domain/auth_request.go +++ b/internal/domain/auth_request.go @@ -55,6 +55,7 @@ type AuthRequest struct { LockoutPolicy *LockoutPolicy DefaultTranslations []*CustomText OrgTranslations []*CustomText + SAMLRequestID string } type ExternalUser struct { @@ -208,3 +209,17 @@ func (a *AuthRequest) Done() bool { } return false } + +func (a *AuthRequest) PrivateLabelingOrgID(defaultID string) string { + if a.RequestedOrgID != "" { + return a.RequestedOrgID + } + if (a.PrivateLabelingSetting == PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy || a.PrivateLabelingSetting == PrivateLabelingSettingUnspecified) && + a.UserOrgID != "" { + return a.UserOrgID + } + if a.PrivateLabelingSetting != PrivateLabelingSettingUnspecified { + return a.ApplicationResourceOwner + } + return defaultID +} diff --git a/internal/domain/feature.go b/internal/domain/feature.go new file mode 100644 index 0000000000..47f58c9912 --- /dev/null +++ b/internal/domain/feature.go @@ -0,0 +1,28 @@ +//go:generate enumer -type Feature + +package domain + +type Feature int + +func (f Feature) Type() FeatureType { + switch f { + case FeatureUnspecified: + return FeatureTypeUnspecified + case FeatureLoginDefaultOrg: + return FeatureTypeBoolean + default: + return FeatureTypeUnspecified + } +} + +const ( + FeatureTypeUnspecified FeatureType = iota + FeatureTypeBoolean +) + +type FeatureType int + +const ( + FeatureUnspecified Feature = iota + FeatureLoginDefaultOrg +) diff --git a/internal/domain/feature_enumer.go b/internal/domain/feature_enumer.go new file mode 100644 index 0000000000..008ec8720d --- /dev/null +++ b/internal/domain/feature_enumer.go @@ -0,0 +1,78 @@ +// Code generated by "enumer -type Feature"; DO NOT EDIT. + +package domain + +import ( + "fmt" + "strings" +) + +const _FeatureName = "FeatureUnspecifiedFeatureLoginDefaultOrg" + +var _FeatureIndex = [...]uint8{0, 18, 40} + +const _FeatureLowerName = "featureunspecifiedfeaturelogindefaultorg" + +func (i Feature) String() string { + if i < 0 || i >= Feature(len(_FeatureIndex)-1) { + return fmt.Sprintf("Feature(%d)", i) + } + return _FeatureName[_FeatureIndex[i]:_FeatureIndex[i+1]] +} + +// An "invalid array index" compiler error signifies that the constant values have changed. +// Re-run the stringer command to generate them again. +func _FeatureNoOp() { + var x [1]struct{} + _ = x[FeatureUnspecified-(0)] + _ = x[FeatureLoginDefaultOrg-(1)] +} + +var _FeatureValues = []Feature{FeatureUnspecified, FeatureLoginDefaultOrg} + +var _FeatureNameToValueMap = map[string]Feature{ + _FeatureName[0:18]: FeatureUnspecified, + _FeatureLowerName[0:18]: FeatureUnspecified, + _FeatureName[18:40]: FeatureLoginDefaultOrg, + _FeatureLowerName[18:40]: FeatureLoginDefaultOrg, +} + +var _FeatureNames = []string{ + _FeatureName[0:18], + _FeatureName[18:40], +} + +// FeatureString retrieves an enum value from the enum constants string name. +// Throws an error if the param is not part of the enum. +func FeatureString(s string) (Feature, error) { + if val, ok := _FeatureNameToValueMap[s]; ok { + return val, nil + } + + if val, ok := _FeatureNameToValueMap[strings.ToLower(s)]; ok { + return val, nil + } + return 0, fmt.Errorf("%s does not belong to Feature values", s) +} + +// FeatureValues returns all values of the enum +func FeatureValues() []Feature { + return _FeatureValues +} + +// FeatureStrings returns a slice of all String values of the enum +func FeatureStrings() []string { + strs := make([]string, len(_FeatureNames)) + copy(strs, _FeatureNames) + return strs +} + +// IsAFeature returns "true" if the value is listed in the enum definition. "false" otherwise +func (i Feature) IsAFeature() bool { + for _, v := range _FeatureValues { + if i == v { + return true + } + } + return false +} diff --git a/internal/domain/idp.go b/internal/domain/idp.go index 1378b6ac02..76c2e38cf9 100644 --- a/internal/domain/idp.go +++ b/internal/domain/idp.go @@ -37,6 +37,7 @@ const ( IDPTypeGitLabSelfHosted IDPTypeGoogle IDPTypeApple + IDPTypeSAML ) func (t IDPType) GetCSSClass() string { @@ -57,7 +58,8 @@ func (t IDPType) GetCSSClass() string { IDPTypeOIDC, IDPTypeJWT, IDPTypeOAuth, - IDPTypeLDAP: + IDPTypeLDAP, + IDPTypeSAML: fallthrough default: return "" @@ -90,7 +92,8 @@ func (t IDPType) DisplayName() string { IDPTypeLDAP, IDPTypeAzureAD, IDPTypeGitHubEnterprise, - IDPTypeGitLabSelfHosted: + IDPTypeGitLabSelfHosted, + IDPTypeSAML: fallthrough default: // we should never get here, so log it diff --git a/internal/idp/providers/apple/apple_test.go b/internal/idp/providers/apple/apple_test.go index 6356cca2ef..f3b7e81a1a 100644 --- a/internal/idp/providers/apple/apple_test.go +++ b/internal/idp/providers/apple/apple_test.go @@ -59,11 +59,13 @@ func TestProvider_BeginAuth(t *testing.T) { provider, err := New(tt.fields.clientID, tt.fields.teamID, tt.fields.keyID, tt.fields.redirectURI, tt.fields.privateKey, tt.fields.scopes) r.NoError(err) - - session, err := provider.BeginAuth(context.Background(), "testState") + ctx := context.Background() + session, err := provider.BeginAuth(ctx, "testState") r.NoError(err) - - a.Equal(tt.want.GetAuthURL(), session.GetAuthURL()) + content, redirect := session.GetAuth(ctx) + contentExpected, redirectExpected := tt.want.GetAuth(ctx) + a.Equal(redirectExpected, redirect) + a.Equal(contentExpected, content) }) } } diff --git a/internal/idp/providers/azuread/azuread_test.go b/internal/idp/providers/azuread/azuread_test.go index 87b048ae9b..3febb43f95 100644 --- a/internal/idp/providers/azuread/azuread_test.go +++ b/internal/idp/providers/azuread/azuread_test.go @@ -77,10 +77,14 @@ func TestProvider_BeginAuth(t *testing.T) { provider, err := New(tt.fields.name, tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.scopes, tt.fields.options...) r.NoError(err) - session, err := provider.BeginAuth(context.Background(), "testState") + ctx := context.Background() + session, err := provider.BeginAuth(ctx, "testState") r.NoError(err) - a.Equal(tt.want.GetAuthURL(), session.GetAuthURL()) + wantHeaders, wantContent := tt.want.GetAuth(ctx) + gotHeaders, gotContent := session.GetAuth(ctx) + a.Equal(wantHeaders, gotHeaders) + a.Equal(wantContent, gotContent) }) } } diff --git a/internal/idp/providers/github/github_test.go b/internal/idp/providers/github/github_test.go index 63244f68d5..6274b51841 100644 --- a/internal/idp/providers/github/github_test.go +++ b/internal/idp/providers/github/github_test.go @@ -44,10 +44,14 @@ func TestProvider_BeginAuth(t *testing.T) { provider, err := New(tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.scopes, tt.fields.options...) r.NoError(err) - session, err := provider.BeginAuth(context.Background(), "testState") + ctx := context.Background() + session, err := provider.BeginAuth(ctx, "testState") r.NoError(err) - a.Equal(tt.want.GetAuthURL(), session.GetAuthURL()) + wantHeaders, wantContent := tt.want.GetAuth(ctx) + gotHeaders, gotContent := session.GetAuth(ctx) + a.Equal(wantHeaders, gotHeaders) + a.Equal(wantContent, gotContent) }) } } diff --git a/internal/idp/providers/gitlab/gitlab_test.go b/internal/idp/providers/gitlab/gitlab_test.go index 74fd66ccbc..24b813bc81 100644 --- a/internal/idp/providers/gitlab/gitlab_test.go +++ b/internal/idp/providers/gitlab/gitlab_test.go @@ -55,11 +55,14 @@ func TestProvider_BeginAuth(t *testing.T) { provider, err := New(tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.scopes, tt.fields.opts...) r.NoError(err) - - session, err := provider.BeginAuth(context.Background(), "testState") + ctx := context.Background() + session, err := provider.BeginAuth(ctx, "testState") r.NoError(err) - a.Equal(tt.want.GetAuthURL(), session.GetAuthURL()) + wantHeaders, wantContent := tt.want.GetAuth(ctx) + gotHeaders, gotContent := session.GetAuth(ctx) + a.Equal(wantHeaders, gotHeaders) + a.Equal(wantContent, gotContent) }) } } diff --git a/internal/idp/providers/google/google_test.go b/internal/idp/providers/google/google_test.go index d7fb3ecb81..b95f8eaf9f 100644 --- a/internal/idp/providers/google/google_test.go +++ b/internal/idp/providers/google/google_test.go @@ -44,10 +44,14 @@ func TestProvider_BeginAuth(t *testing.T) { provider, err := New(tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.scopes) r.NoError(err) - session, err := provider.BeginAuth(context.Background(), "testState") + ctx := context.Background() + session, err := provider.BeginAuth(ctx, "testState") r.NoError(err) - a.Equal(tt.want.GetAuthURL(), session.GetAuthURL()) + wantHeaders, wantContent := tt.want.GetAuth(ctx) + gotHeaders, gotContent := session.GetAuth(ctx) + a.Equal(wantHeaders, gotHeaders) + a.Equal(wantContent, gotContent) }) } } diff --git a/internal/idp/providers/jwt/jwt_test.go b/internal/idp/providers/jwt/jwt_test.go index 2c8725fc97..7f9f8b0fc9 100644 --- a/internal/idp/providers/jwt/jwt_test.go +++ b/internal/idp/providers/jwt/jwt_test.go @@ -112,13 +112,17 @@ func TestProvider_BeginAuth(t *testing.T) { ) require.NoError(t, err) - session, err := provider.BeginAuth(context.Background(), "testState", tt.args.params...) + ctx := context.Background() + session, err := provider.BeginAuth(ctx, "testState", tt.args.params...) if tt.want.err != nil && !tt.want.err(err) { a.Fail("invalid error", err) } if tt.want.err == nil { a.NoError(err) - a.Equal(tt.want.session.GetAuthURL(), session.GetAuthURL()) + wantHeaders, wantContent := tt.want.session.GetAuth(ctx) + gotHeaders, gotContent := session.GetAuth(ctx) + a.Equal(wantHeaders, gotHeaders) + a.Equal(wantContent, gotContent) } }) } diff --git a/internal/idp/providers/jwt/session.go b/internal/idp/providers/jwt/session.go index b0b69293e8..be3ffc0531 100644 --- a/internal/idp/providers/jwt/session.go +++ b/internal/idp/providers/jwt/session.go @@ -30,9 +30,9 @@ type Session struct { Tokens *oidc.Tokens[*oidc.IDTokenClaims] } -// GetAuthURL implements the [idp.Session] interface -func (s *Session) GetAuthURL() string { - return s.AuthURL +// GetAuth implements the [idp.Session] interface. +func (s *Session) GetAuth(ctx context.Context) (string, bool) { + return idp.Redirect(s.AuthURL) } // FetchUser implements the [idp.Session] interface. diff --git a/internal/idp/providers/ldap/session.go b/internal/idp/providers/ldap/session.go index e6422b5d26..6bd32525dd 100644 --- a/internal/idp/providers/ldap/session.go +++ b/internal/idp/providers/ldap/session.go @@ -29,8 +29,9 @@ type Session struct { Entry *ldap.Entry } -func (s *Session) GetAuthURL() string { - return s.loginUrl +// GetAuth implements the [idp.Session] interface. +func (s *Session) GetAuth(ctx context.Context) (string, bool) { + return idp.Redirect(s.loginUrl) } func (s *Session) FetchUser(_ context.Context) (_ idp.User, err error) { diff --git a/internal/idp/providers/oauth/oauth2_test.go b/internal/idp/providers/oauth/oauth2_test.go index d145745918..5fdfbc2185 100644 --- a/internal/idp/providers/oauth/oauth2_test.go +++ b/internal/idp/providers/oauth/oauth2_test.go @@ -49,10 +49,14 @@ func TestProvider_BeginAuth(t *testing.T) { provider, err := New(tt.fields.config, tt.fields.name, tt.fields.userEndpoint, tt.fields.userMapper) r.NoError(err) - session, err := provider.BeginAuth(context.Background(), "testState") + ctx := context.Background() + session, err := provider.BeginAuth(ctx, "testState") r.NoError(err) - a.Equal(tt.want.GetAuthURL(), session.GetAuthURL()) + wantHeaders, wantContent := tt.want.GetAuth(ctx) + gotHeaders, gotContent := session.GetAuth(ctx) + a.Equal(wantHeaders, gotHeaders) + a.Equal(wantContent, gotContent) }) } } diff --git a/internal/idp/providers/oauth/session.go b/internal/idp/providers/oauth/session.go index 760fcefcfa..e85116afac 100644 --- a/internal/idp/providers/oauth/session.go +++ b/internal/idp/providers/oauth/session.go @@ -25,9 +25,9 @@ type Session struct { Provider *Provider } -// GetAuthURL implements the [idp.Session] interface. -func (s *Session) GetAuthURL() string { - return s.AuthURL +// GetAuth implements the [idp.Session] interface. +func (s *Session) GetAuth(ctx context.Context) (string, bool) { + return idp.Redirect(s.AuthURL) } // FetchUser implements the [idp.Session] interface. diff --git a/internal/idp/providers/oidc/oidc_test.go b/internal/idp/providers/oidc/oidc_test.go index c2310ac0d8..bbe08155c8 100644 --- a/internal/idp/providers/oidc/oidc_test.go +++ b/internal/idp/providers/oidc/oidc_test.go @@ -66,10 +66,14 @@ func TestProvider_BeginAuth(t *testing.T) { provider, err := New(tt.fields.name, tt.fields.issuer, tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.scopes, tt.fields.userMapper, tt.fields.opts...) r.NoError(err) - session, err := provider.BeginAuth(context.Background(), "testState") + ctx := context.Background() + session, err := provider.BeginAuth(ctx, "testState") r.NoError(err) - a.Equal(tt.want.GetAuthURL(), session.GetAuthURL()) + wantHeaders, wantContent := tt.want.GetAuth(ctx) + gotHeaders, gotContent := session.GetAuth(ctx) + a.Equal(wantHeaders, gotHeaders) + a.Equal(wantContent, gotContent) }) } } diff --git a/internal/idp/providers/oidc/session.go b/internal/idp/providers/oidc/session.go index 215c24dab6..366e42643a 100644 --- a/internal/idp/providers/oidc/session.go +++ b/internal/idp/providers/oidc/session.go @@ -24,9 +24,9 @@ type Session struct { Tokens *oidc.Tokens[*oidc.IDTokenClaims] } -// GetAuthURL implements the [idp.Session] interface. -func (s *Session) GetAuthURL() string { - return s.AuthURL +// GetAuth implements the [idp.Session] interface. +func (s *Session) GetAuth(ctx context.Context) (string, bool) { + return idp.Redirect(s.AuthURL) } // FetchUser implements the [idp.Session] interface. diff --git a/internal/idp/providers/saml/mapper.go b/internal/idp/providers/saml/mapper.go new file mode 100644 index 0000000000..506d9b3a03 --- /dev/null +++ b/internal/idp/providers/saml/mapper.go @@ -0,0 +1,90 @@ +package saml + +import ( + "github.com/crewjam/saml" + "golang.org/x/text/language" + + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/idp" +) + +var _ idp.User = (*UserMapper)(nil) + +// UserMapper is an implementation of [idp.User]. +type UserMapper struct { + ID string `json:"id,omitempty"` + Attributes map[string][]string `json:"attributes,omitempty"` +} + +func NewUser() *UserMapper { + return &UserMapper{Attributes: map[string][]string{}} +} + +func (u *UserMapper) SetID(id *saml.NameID) { + u.ID = id.Value +} + +// GetID is an implementation of the [idp.User] interface. +func (u *UserMapper) GetID() string { + return u.ID +} + +// GetFirstName is an implementation of the [idp.User] interface. +func (u *UserMapper) GetFirstName() string { + return "" +} + +// GetLastName is an implementation of the [idp.User] interface. +func (u *UserMapper) GetLastName() string { + return "" +} + +// GetDisplayName is an implementation of the [idp.User] interface. +func (u *UserMapper) GetDisplayName() string { + return "" +} + +// GetNickname is an implementation of the [idp.User] interface. +func (u *UserMapper) GetNickname() string { + return "" +} + +// GetPreferredUsername is an implementation of the [idp.User] interface. +func (u *UserMapper) GetPreferredUsername() string { + return "" +} + +// GetEmail is an implementation of the [idp.User] interface. +func (u *UserMapper) GetEmail() domain.EmailAddress { + return "" +} + +// IsEmailVerified is an implementation of the [idp.User] interface. +func (u *UserMapper) IsEmailVerified() bool { + return false +} + +// GetPhone is an implementation of the [idp.User] interface. +func (u *UserMapper) GetPhone() domain.PhoneNumber { + return "" +} + +// IsPhoneVerified is an implementation of the [idp.User] interface. +func (u *UserMapper) IsPhoneVerified() bool { + return false +} + +// GetPreferredLanguage is an implementation of the [idp.User] interface. +func (u *UserMapper) GetPreferredLanguage() language.Tag { + return language.Und +} + +// GetAvatarURL is an implementation of the [idp.User] interface. +func (u *UserMapper) GetAvatarURL() string { + return "" +} + +// GetProfile is an implementation of the [idp.User] interface. +func (u *UserMapper) GetProfile() string { + return "" +} diff --git a/internal/idp/providers/saml/requesttracker/request_tracker.go b/internal/idp/providers/saml/requesttracker/request_tracker.go new file mode 100644 index 0000000000..6c57386d61 --- /dev/null +++ b/internal/idp/providers/saml/requesttracker/request_tracker.go @@ -0,0 +1,58 @@ +package requesttracker + +import ( + "context" + "net/http" + + "github.com/crewjam/saml/samlsp" +) + +type GetRequest func(ctx context.Context, intentID string) (*samlsp.TrackedRequest, error) +type AddRequest func(ctx context.Context, intentID, requestID string) error + +type RequestTracker struct { + addRequest AddRequest + getRequest GetRequest +} + +func New(addRequestF AddRequest, getRequestF GetRequest) samlsp.RequestTracker { + return &RequestTracker{ + addRequest: addRequestF, + getRequest: getRequestF, + } +} + +func (rt *RequestTracker) TrackRequest(w http.ResponseWriter, r *http.Request, samlRequestID string) (index string, err error) { + // intentID is stored in r.URL + intentID := r.URL.String() + if err := rt.addRequest(r.Context(), intentID, samlRequestID); err != nil { + return "", err + } + return intentID, nil +} + +func (rt *RequestTracker) StopTrackingRequest(w http.ResponseWriter, r *http.Request, index string) error { + // error is not handled in SP logic + return nil +} + +func (rt *RequestTracker) GetTrackedRequests(r *http.Request) []samlsp.TrackedRequest { + // RelayState is the context of the auth flow and as such contains the intentID + intentID := r.FormValue("RelayState") + + request, err := rt.getRequest(r.Context(), intentID) + if err != nil { + return nil + } + return []samlsp.TrackedRequest{ + { + Index: request.Index, + SAMLRequestID: request.SAMLRequestID, + URI: request.URI, + }, + } +} + +func (rt *RequestTracker) GetTrackedRequest(r *http.Request, index string) (*samlsp.TrackedRequest, error) { + return rt.getRequest(r.Context(), index) +} diff --git a/internal/idp/providers/saml/saml.go b/internal/idp/providers/saml/saml.go new file mode 100644 index 0000000000..9e6623df7f --- /dev/null +++ b/internal/idp/providers/saml/saml.go @@ -0,0 +1,175 @@ +package saml + +import ( + "context" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/xml" + "net/url" + + "github.com/crewjam/saml" + "github.com/crewjam/saml/samlsp" + + "github.com/zitadel/zitadel/internal/errors" + "github.com/zitadel/zitadel/internal/idp" +) + +var _ idp.Provider = (*Provider)(nil) + +// Provider is the [idp.Provider] implementation for a generic SAML provider +type Provider struct { + name string + + requestTracker samlsp.RequestTracker + Certificate []byte + + spOptions *samlsp.Options + + binding string + + isLinkingAllowed bool + isCreationAllowed bool + isAutoCreation bool + isAutoUpdate bool +} + +type ProviderOpts func(provider *Provider) + +// WithLinkingAllowed allows end users to link the federated user to an existing one. +func WithLinkingAllowed() ProviderOpts { + return func(p *Provider) { + p.isLinkingAllowed = true + } +} + +// WithCreationAllowed allows end users to create a new user using the federated information. +func WithCreationAllowed() ProviderOpts { + return func(p *Provider) { + p.isCreationAllowed = true + } +} + +// WithAutoCreation enables that federated users are automatically created if not already existing. +func WithAutoCreation() ProviderOpts { + return func(p *Provider) { + p.isAutoCreation = true + } +} + +// WithAutoUpdate enables that information retrieved from the provider is automatically used to update +// the existing user on each authentication. +func WithAutoUpdate() ProviderOpts { + return func(p *Provider) { + p.isAutoUpdate = true + } +} + +func WithSignedRequest() ProviderOpts { + return func(p *Provider) { + p.spOptions.SignRequest = true + } +} + +func WithBinding(binding string) ProviderOpts { + return func(p *Provider) { + p.binding = binding + } +} + +func WithCustomRequestTracker(tracker samlsp.RequestTracker) ProviderOpts { + return func(p *Provider) { + p.requestTracker = tracker + } +} + +func WithEntityID(entityID string) ProviderOpts { + return func(p *Provider) { + p.spOptions.EntityID = entityID + } +} + +func New( + name string, + rootURLStr string, + metadata []byte, + certificate []byte, + key []byte, + options ...ProviderOpts, +) (*Provider, error) { + entityDescriptor := new(saml.EntityDescriptor) + if err := xml.Unmarshal(metadata, entityDescriptor); err != nil { + return nil, err + } + keyPair, err := tls.X509KeyPair(certificate, key) + if err != nil { + return nil, err + } + keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0]) + if err != nil { + return nil, err + } + rootURL, err := url.Parse(rootURLStr) + if err != nil { + return nil, err + } + opts := samlsp.Options{ + URL: *rootURL, + Key: keyPair.PrivateKey.(*rsa.PrivateKey), + Certificate: keyPair.Leaf, + IDPMetadata: entityDescriptor, + SignRequest: false, + } + provider := &Provider{ + name: name, + spOptions: &opts, + Certificate: certificate, + } + for _, option := range options { + option(provider) + } + return provider, nil +} + +func (p *Provider) Name() string { + return p.name +} + +func (p *Provider) IsLinkingAllowed() bool { + return p.isLinkingAllowed +} + +func (p *Provider) IsCreationAllowed() bool { + return p.isCreationAllowed +} + +func (p *Provider) IsAutoCreation() bool { + return p.isAutoCreation +} + +func (p *Provider) IsAutoUpdate() bool { + return p.isAutoUpdate +} + +func (p *Provider) GetSP() (*samlsp.Middleware, error) { + sp, err := samlsp.New(*p.spOptions) + if err != nil { + return nil, errors.ThrowInternal(err, "SAML-qee09ffuq5", "Errors.Intent.IDPInvalid") + } + if p.requestTracker != nil { + sp.RequestTracker = p.requestTracker + } + return sp, nil +} + +func (p *Provider) BeginAuth(ctx context.Context, state string, params ...any) (idp.Session, error) { + m, err := p.GetSP() + if err != nil { + return nil, err + } + + return &Session{ + ServiceProvider: m, + state: state, + }, nil +} diff --git a/internal/idp/providers/saml/saml_test.go b/internal/idp/providers/saml/saml_test.go new file mode 100644 index 0000000000..28dc921681 --- /dev/null +++ b/internal/idp/providers/saml/saml_test.go @@ -0,0 +1,161 @@ +package saml + +import ( + "testing" + + "github.com/crewjam/saml/samlsp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/zitadel/zitadel/internal/idp/providers/saml/requesttracker" +) + +func TestProvider_Options(t *testing.T) { + type fields struct { + name string + rootURL string + metadata []byte + key []byte + certificate []byte + options []ProviderOpts + } + type want struct { + err bool + name string + linkingAllowed bool + creationAllowed bool + autoCreation bool + autoUpdate bool + binding string + withSignedRequest bool + requesttracker samlsp.RequestTracker + entityID string + } + tests := []struct { + name string + fields fields + want want + }{{ + name: "failed metadata", + fields: fields{ + name: "saml", + rootURL: "https://localhost:8080", + metadata: []byte(">xml<"), + options: nil, + }, + want: want{ + err: true, + }, + }, + { + name: "failed keypair cert", + fields: fields{ + name: "saml", + rootURL: "https://localhost:8080", + key: []byte("-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAxHd087RoEm9ywVWZ/H+tDWxQsmVvhfRz4jAq/RfU+OWXNH4J\njMMSHdFs0Q+WP98nNXRyc7fgbMb8NdmlB2yD4qLYapN5SDaBc5dh/3EnyFt53oSs\njTlKnQUPAeJr2qh/NY046CfyUyQMM4JR5OiQFo4TssfWnqdcgamGt0AEnk2lvbMZ\nKQdAqNS9lDzYbjMGavEQPTZE35mFXFQXjaooZXq+TIa7hbaq7/idH7cHNbLcPLgj\nfPQA8q+DYvnvhXlmq0LPQZH3Oiixf+SF2vRwrBzT2mqGD2OiOkUmhuPwyqEiiBHt\nfxklRtRU6WfLa1Gcb1PsV0uoBGpV3KybIl/GlwIDAQABAoIBAEQjDduLgOCL6Gem\n0X3hpdnW6/HC/jed/Sa//9jBECq2LYeWAqff64ON40hqOHi0YvvGA/+gEOSI6mWe\nsv5tIxxRz+6+cLybsq+tG96kluCE4TJMHy/nY7orS/YiWbd+4odnEApr+D3fbZ/b\nnZ1fDsHTyn8hkYx6jLmnWsJpIHDp7zxD76y7k2Bbg6DZrCGiVxngiLJk23dvz79W\np03lHLM7XE92aFwXQmhfxHGxrbuoB/9eY4ai5IHp36H4fw0vL6NXdNQAo/bhe0p9\nAYB7y0ZumF8Hg0Z/BmMeEzLy6HrYB+VE8cO93pNjhSyH+p2yDB/BlUyTiRLQAoM0\nVTmOZXECgYEA7NGlzpKNhyQEJihVqt0MW0LhKIO/xbBn+XgYfX6GpqPa/ucnMx5/\nVezpl3gK8IU4wPUhAyXXAHJiqNBcEeyxrw0MXLujDVMJgYaLysCLJdvMVgoY08mS\nK5IQivpbozpf4+0y3mOnA+Sy1kbfxv2X8xiWLODRQW3f3q/xoklwOR8CgYEA1GEe\nfaibOFTQAYcIVj77KXtBfYZsX3EGAyfAN9O7cKHq5oaxVstwnF47WxpuVtoKZxCZ\nbNm9D5WvQ9b+Ztpioe42tzwE7Bff/Osj868GcDdRPK7nFlh9N2yVn/D514dOYVwR\n4MBr1KrJzgRWt4QqS4H+to1GzudDTSNlG7gnK4kCgYBUi6AbOHzoYzZL/RhgcJwp\ntJ23nhmH1Su5h2OO4e3mbhcP66w19sxU+8iFN+kH5zfUw26utgKk+TE5vXExQQRK\nT2k7bg2PAzcgk80ybD0BHhA8I0yrx4m0nmfjhe/TPVLgh10iwgbtP+eM0i6v1vc5\nZWyvxu9N4ZEL6lpkqr0y1wKBgG/NAIQd8jhhTW7Aav8cAJQBsqQl038avJOEpYe+\nCnpsgoAAf/K0/f8TDCQVceh+t+MxtdK7fO9rWOxZjWsPo8Si5mLnUaAHoX4/OpnZ\nlYYVWMqdOEFnK+O1Yb7k2GFBdV2DXlX2dc1qavntBsls5ecB89id3pyk2aUN8Pf6\npYQhAoGAMGtrHFely9wyaxI0RTCyfmJbWZHGVGkv6ELK8wneJjdjl82XOBUGCg5q\naRCrTZ3dPitKwrUa6ibJCIFCIziiriBmjDvTHzkMvoJEap2TVxYNDR6IfINVsQ57\nlOsiC4A2uGq4Lbfld+gjoplJ5GX6qXtTgZ6m7eo0y7U6zm2tkN0=\n-----END RSA PRIVATE KEY-----\n"), + metadata: []byte("\n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n \n urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n \n \n \n"), + options: nil, + }, + want: want{ + err: true, + }, + }, + { + name: "failed keypair key", + fields: fields{ + name: "saml", + rootURL: "https://localhost:8080", + certificate: []byte("-----BEGIN CERTIFICATE-----\nMIIC2zCCAcOgAwIBAgIIAy/jm1gAAdEwDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UE\nChMHWklUQURFTDAeFw0yMzA4MzAwNzExMTVaFw0yNDA4MjkwNzExMTVaMBIxEDAO\nBgNVBAoTB1pJVEFERUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDE\nd3TztGgSb3LBVZn8f60NbFCyZW+F9HPiMCr9F9T45Zc0fgmMwxId0WzRD5Y/3yc1\ndHJzt+Bsxvw12aUHbIPiothqk3lINoFzl2H/cSfIW3nehKyNOUqdBQ8B4mvaqH81\njTjoJ/JTJAwzglHk6JAWjhOyx9aep1yBqYa3QASeTaW9sxkpB0Co1L2UPNhuMwZq\n8RA9NkTfmYVcVBeNqihler5MhruFtqrv+J0ftwc1stw8uCN89ADyr4Ni+e+FeWar\nQs9Bkfc6KLF/5IXa9HCsHNPaaoYPY6I6RSaG4/DKoSKIEe1/GSVG1FTpZ8trUZxv\nU+xXS6gEalXcrJsiX8aXAgMBAAGjNTAzMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE\nDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCx\n/dRNIj0N/16zJhZR/ahkc2AkvDXYxyr4JRT5wK9GQDNl/oaX3debRuSi/tfaXFIX\naJA6PxM4J49ZaiEpLrKfxMz5kAhjKchCBEMcH3mGt+iNZH7EOyTvHjpGrP2OZrsh\nO17yrvN3HuQxIU6roJlqtZz2iAADsoPtwOO4D7hupm9XTMkSnAmlMWOo/q46Jz89\n1sMxB+dXmH/zV0wgwh0omZfLV0u89mvdq269VhcjNBpBYSnN1ccqYWd5iwziob3I\nvaavGHGfkbvRUn/tKftYuTK30q03R+e9YbmlWZ0v695owh2e/apCzowQsCKfSVC8\nOxVyt5XkHq1tWwVyBmFp\n-----END CERTIFICATE-----\n"), + metadata: []byte("\n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n \n urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n \n \n \n"), + options: nil, + }, + want: want{ + err: true, + }, + }, + { + name: "failed url", + fields: fields{ + name: "saml", + rootURL: "%%", + key: []byte("-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAxHd087RoEm9ywVWZ/H+tDWxQsmVvhfRz4jAq/RfU+OWXNH4J\njMMSHdFs0Q+WP98nNXRyc7fgbMb8NdmlB2yD4qLYapN5SDaBc5dh/3EnyFt53oSs\njTlKnQUPAeJr2qh/NY046CfyUyQMM4JR5OiQFo4TssfWnqdcgamGt0AEnk2lvbMZ\nKQdAqNS9lDzYbjMGavEQPTZE35mFXFQXjaooZXq+TIa7hbaq7/idH7cHNbLcPLgj\nfPQA8q+DYvnvhXlmq0LPQZH3Oiixf+SF2vRwrBzT2mqGD2OiOkUmhuPwyqEiiBHt\nfxklRtRU6WfLa1Gcb1PsV0uoBGpV3KybIl/GlwIDAQABAoIBAEQjDduLgOCL6Gem\n0X3hpdnW6/HC/jed/Sa//9jBECq2LYeWAqff64ON40hqOHi0YvvGA/+gEOSI6mWe\nsv5tIxxRz+6+cLybsq+tG96kluCE4TJMHy/nY7orS/YiWbd+4odnEApr+D3fbZ/b\nnZ1fDsHTyn8hkYx6jLmnWsJpIHDp7zxD76y7k2Bbg6DZrCGiVxngiLJk23dvz79W\np03lHLM7XE92aFwXQmhfxHGxrbuoB/9eY4ai5IHp36H4fw0vL6NXdNQAo/bhe0p9\nAYB7y0ZumF8Hg0Z/BmMeEzLy6HrYB+VE8cO93pNjhSyH+p2yDB/BlUyTiRLQAoM0\nVTmOZXECgYEA7NGlzpKNhyQEJihVqt0MW0LhKIO/xbBn+XgYfX6GpqPa/ucnMx5/\nVezpl3gK8IU4wPUhAyXXAHJiqNBcEeyxrw0MXLujDVMJgYaLysCLJdvMVgoY08mS\nK5IQivpbozpf4+0y3mOnA+Sy1kbfxv2X8xiWLODRQW3f3q/xoklwOR8CgYEA1GEe\nfaibOFTQAYcIVj77KXtBfYZsX3EGAyfAN9O7cKHq5oaxVstwnF47WxpuVtoKZxCZ\nbNm9D5WvQ9b+Ztpioe42tzwE7Bff/Osj868GcDdRPK7nFlh9N2yVn/D514dOYVwR\n4MBr1KrJzgRWt4QqS4H+to1GzudDTSNlG7gnK4kCgYBUi6AbOHzoYzZL/RhgcJwp\ntJ23nhmH1Su5h2OO4e3mbhcP66w19sxU+8iFN+kH5zfUw26utgKk+TE5vXExQQRK\nT2k7bg2PAzcgk80ybD0BHhA8I0yrx4m0nmfjhe/TPVLgh10iwgbtP+eM0i6v1vc5\nZWyvxu9N4ZEL6lpkqr0y1wKBgG/NAIQd8jhhTW7Aav8cAJQBsqQl038avJOEpYe+\nCnpsgoAAf/K0/f8TDCQVceh+t+MxtdK7fO9rWOxZjWsPo8Si5mLnUaAHoX4/OpnZ\nlYYVWMqdOEFnK+O1Yb7k2GFBdV2DXlX2dc1qavntBsls5ecB89id3pyk2aUN8Pf6\npYQhAoGAMGtrHFely9wyaxI0RTCyfmJbWZHGVGkv6ELK8wneJjdjl82XOBUGCg5q\naRCrTZ3dPitKwrUa6ibJCIFCIziiriBmjDvTHzkMvoJEap2TVxYNDR6IfINVsQ57\nlOsiC4A2uGq4Lbfld+gjoplJ5GX6qXtTgZ6m7eo0y7U6zm2tkN0=\n-----END RSA PRIVATE KEY-----\n"), + certificate: []byte("-----BEGIN CERTIFICATE-----\nMIIC2zCCAcOgAwIBAgIIAy/jm1gAAdEwDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UE\nChMHWklUQURFTDAeFw0yMzA4MzAwNzExMTVaFw0yNDA4MjkwNzExMTVaMBIxEDAO\nBgNVBAoTB1pJVEFERUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDE\nd3TztGgSb3LBVZn8f60NbFCyZW+F9HPiMCr9F9T45Zc0fgmMwxId0WzRD5Y/3yc1\ndHJzt+Bsxvw12aUHbIPiothqk3lINoFzl2H/cSfIW3nehKyNOUqdBQ8B4mvaqH81\njTjoJ/JTJAwzglHk6JAWjhOyx9aep1yBqYa3QASeTaW9sxkpB0Co1L2UPNhuMwZq\n8RA9NkTfmYVcVBeNqihler5MhruFtqrv+J0ftwc1stw8uCN89ADyr4Ni+e+FeWar\nQs9Bkfc6KLF/5IXa9HCsHNPaaoYPY6I6RSaG4/DKoSKIEe1/GSVG1FTpZ8trUZxv\nU+xXS6gEalXcrJsiX8aXAgMBAAGjNTAzMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE\nDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCx\n/dRNIj0N/16zJhZR/ahkc2AkvDXYxyr4JRT5wK9GQDNl/oaX3debRuSi/tfaXFIX\naJA6PxM4J49ZaiEpLrKfxMz5kAhjKchCBEMcH3mGt+iNZH7EOyTvHjpGrP2OZrsh\nO17yrvN3HuQxIU6roJlqtZz2iAADsoPtwOO4D7hupm9XTMkSnAmlMWOo/q46Jz89\n1sMxB+dXmH/zV0wgwh0omZfLV0u89mvdq269VhcjNBpBYSnN1ccqYWd5iwziob3I\nvaavGHGfkbvRUn/tKftYuTK30q03R+e9YbmlWZ0v695owh2e/apCzowQsCKfSVC8\nOxVyt5XkHq1tWwVyBmFp\n-----END CERTIFICATE-----\n"), + metadata: []byte("\n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n \n urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n \n \n \n"), + options: nil, + }, + want: want{ + err: true, + }, + }, + { + name: "default", + fields: fields{ + name: "saml", + rootURL: "https://localhost:8080", + key: []byte("-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAxHd087RoEm9ywVWZ/H+tDWxQsmVvhfRz4jAq/RfU+OWXNH4J\njMMSHdFs0Q+WP98nNXRyc7fgbMb8NdmlB2yD4qLYapN5SDaBc5dh/3EnyFt53oSs\njTlKnQUPAeJr2qh/NY046CfyUyQMM4JR5OiQFo4TssfWnqdcgamGt0AEnk2lvbMZ\nKQdAqNS9lDzYbjMGavEQPTZE35mFXFQXjaooZXq+TIa7hbaq7/idH7cHNbLcPLgj\nfPQA8q+DYvnvhXlmq0LPQZH3Oiixf+SF2vRwrBzT2mqGD2OiOkUmhuPwyqEiiBHt\nfxklRtRU6WfLa1Gcb1PsV0uoBGpV3KybIl/GlwIDAQABAoIBAEQjDduLgOCL6Gem\n0X3hpdnW6/HC/jed/Sa//9jBECq2LYeWAqff64ON40hqOHi0YvvGA/+gEOSI6mWe\nsv5tIxxRz+6+cLybsq+tG96kluCE4TJMHy/nY7orS/YiWbd+4odnEApr+D3fbZ/b\nnZ1fDsHTyn8hkYx6jLmnWsJpIHDp7zxD76y7k2Bbg6DZrCGiVxngiLJk23dvz79W\np03lHLM7XE92aFwXQmhfxHGxrbuoB/9eY4ai5IHp36H4fw0vL6NXdNQAo/bhe0p9\nAYB7y0ZumF8Hg0Z/BmMeEzLy6HrYB+VE8cO93pNjhSyH+p2yDB/BlUyTiRLQAoM0\nVTmOZXECgYEA7NGlzpKNhyQEJihVqt0MW0LhKIO/xbBn+XgYfX6GpqPa/ucnMx5/\nVezpl3gK8IU4wPUhAyXXAHJiqNBcEeyxrw0MXLujDVMJgYaLysCLJdvMVgoY08mS\nK5IQivpbozpf4+0y3mOnA+Sy1kbfxv2X8xiWLODRQW3f3q/xoklwOR8CgYEA1GEe\nfaibOFTQAYcIVj77KXtBfYZsX3EGAyfAN9O7cKHq5oaxVstwnF47WxpuVtoKZxCZ\nbNm9D5WvQ9b+Ztpioe42tzwE7Bff/Osj868GcDdRPK7nFlh9N2yVn/D514dOYVwR\n4MBr1KrJzgRWt4QqS4H+to1GzudDTSNlG7gnK4kCgYBUi6AbOHzoYzZL/RhgcJwp\ntJ23nhmH1Su5h2OO4e3mbhcP66w19sxU+8iFN+kH5zfUw26utgKk+TE5vXExQQRK\nT2k7bg2PAzcgk80ybD0BHhA8I0yrx4m0nmfjhe/TPVLgh10iwgbtP+eM0i6v1vc5\nZWyvxu9N4ZEL6lpkqr0y1wKBgG/NAIQd8jhhTW7Aav8cAJQBsqQl038avJOEpYe+\nCnpsgoAAf/K0/f8TDCQVceh+t+MxtdK7fO9rWOxZjWsPo8Si5mLnUaAHoX4/OpnZ\nlYYVWMqdOEFnK+O1Yb7k2GFBdV2DXlX2dc1qavntBsls5ecB89id3pyk2aUN8Pf6\npYQhAoGAMGtrHFely9wyaxI0RTCyfmJbWZHGVGkv6ELK8wneJjdjl82XOBUGCg5q\naRCrTZ3dPitKwrUa6ibJCIFCIziiriBmjDvTHzkMvoJEap2TVxYNDR6IfINVsQ57\nlOsiC4A2uGq4Lbfld+gjoplJ5GX6qXtTgZ6m7eo0y7U6zm2tkN0=\n-----END RSA PRIVATE KEY-----\n"), + certificate: []byte("-----BEGIN CERTIFICATE-----\nMIIC2zCCAcOgAwIBAgIIAy/jm1gAAdEwDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UE\nChMHWklUQURFTDAeFw0yMzA4MzAwNzExMTVaFw0yNDA4MjkwNzExMTVaMBIxEDAO\nBgNVBAoTB1pJVEFERUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDE\nd3TztGgSb3LBVZn8f60NbFCyZW+F9HPiMCr9F9T45Zc0fgmMwxId0WzRD5Y/3yc1\ndHJzt+Bsxvw12aUHbIPiothqk3lINoFzl2H/cSfIW3nehKyNOUqdBQ8B4mvaqH81\njTjoJ/JTJAwzglHk6JAWjhOyx9aep1yBqYa3QASeTaW9sxkpB0Co1L2UPNhuMwZq\n8RA9NkTfmYVcVBeNqihler5MhruFtqrv+J0ftwc1stw8uCN89ADyr4Ni+e+FeWar\nQs9Bkfc6KLF/5IXa9HCsHNPaaoYPY6I6RSaG4/DKoSKIEe1/GSVG1FTpZ8trUZxv\nU+xXS6gEalXcrJsiX8aXAgMBAAGjNTAzMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE\nDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCx\n/dRNIj0N/16zJhZR/ahkc2AkvDXYxyr4JRT5wK9GQDNl/oaX3debRuSi/tfaXFIX\naJA6PxM4J49ZaiEpLrKfxMz5kAhjKchCBEMcH3mGt+iNZH7EOyTvHjpGrP2OZrsh\nO17yrvN3HuQxIU6roJlqtZz2iAADsoPtwOO4D7hupm9XTMkSnAmlMWOo/q46Jz89\n1sMxB+dXmH/zV0wgwh0omZfLV0u89mvdq269VhcjNBpBYSnN1ccqYWd5iwziob3I\nvaavGHGfkbvRUn/tKftYuTK30q03R+e9YbmlWZ0v695owh2e/apCzowQsCKfSVC8\nOxVyt5XkHq1tWwVyBmFp\n-----END CERTIFICATE-----\n"), + metadata: []byte("\n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n \n urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n \n \n \n"), + options: nil, + }, + want: want{ + name: "saml", + linkingAllowed: false, + creationAllowed: false, + autoCreation: false, + autoUpdate: false, + }, + }, + { + name: "all true", + fields: fields{ + name: "saml", + key: []byte("-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAxHd087RoEm9ywVWZ/H+tDWxQsmVvhfRz4jAq/RfU+OWXNH4J\njMMSHdFs0Q+WP98nNXRyc7fgbMb8NdmlB2yD4qLYapN5SDaBc5dh/3EnyFt53oSs\njTlKnQUPAeJr2qh/NY046CfyUyQMM4JR5OiQFo4TssfWnqdcgamGt0AEnk2lvbMZ\nKQdAqNS9lDzYbjMGavEQPTZE35mFXFQXjaooZXq+TIa7hbaq7/idH7cHNbLcPLgj\nfPQA8q+DYvnvhXlmq0LPQZH3Oiixf+SF2vRwrBzT2mqGD2OiOkUmhuPwyqEiiBHt\nfxklRtRU6WfLa1Gcb1PsV0uoBGpV3KybIl/GlwIDAQABAoIBAEQjDduLgOCL6Gem\n0X3hpdnW6/HC/jed/Sa//9jBECq2LYeWAqff64ON40hqOHi0YvvGA/+gEOSI6mWe\nsv5tIxxRz+6+cLybsq+tG96kluCE4TJMHy/nY7orS/YiWbd+4odnEApr+D3fbZ/b\nnZ1fDsHTyn8hkYx6jLmnWsJpIHDp7zxD76y7k2Bbg6DZrCGiVxngiLJk23dvz79W\np03lHLM7XE92aFwXQmhfxHGxrbuoB/9eY4ai5IHp36H4fw0vL6NXdNQAo/bhe0p9\nAYB7y0ZumF8Hg0Z/BmMeEzLy6HrYB+VE8cO93pNjhSyH+p2yDB/BlUyTiRLQAoM0\nVTmOZXECgYEA7NGlzpKNhyQEJihVqt0MW0LhKIO/xbBn+XgYfX6GpqPa/ucnMx5/\nVezpl3gK8IU4wPUhAyXXAHJiqNBcEeyxrw0MXLujDVMJgYaLysCLJdvMVgoY08mS\nK5IQivpbozpf4+0y3mOnA+Sy1kbfxv2X8xiWLODRQW3f3q/xoklwOR8CgYEA1GEe\nfaibOFTQAYcIVj77KXtBfYZsX3EGAyfAN9O7cKHq5oaxVstwnF47WxpuVtoKZxCZ\nbNm9D5WvQ9b+Ztpioe42tzwE7Bff/Osj868GcDdRPK7nFlh9N2yVn/D514dOYVwR\n4MBr1KrJzgRWt4QqS4H+to1GzudDTSNlG7gnK4kCgYBUi6AbOHzoYzZL/RhgcJwp\ntJ23nhmH1Su5h2OO4e3mbhcP66w19sxU+8iFN+kH5zfUw26utgKk+TE5vXExQQRK\nT2k7bg2PAzcgk80ybD0BHhA8I0yrx4m0nmfjhe/TPVLgh10iwgbtP+eM0i6v1vc5\nZWyvxu9N4ZEL6lpkqr0y1wKBgG/NAIQd8jhhTW7Aav8cAJQBsqQl038avJOEpYe+\nCnpsgoAAf/K0/f8TDCQVceh+t+MxtdK7fO9rWOxZjWsPo8Si5mLnUaAHoX4/OpnZ\nlYYVWMqdOEFnK+O1Yb7k2GFBdV2DXlX2dc1qavntBsls5ecB89id3pyk2aUN8Pf6\npYQhAoGAMGtrHFely9wyaxI0RTCyfmJbWZHGVGkv6ELK8wneJjdjl82XOBUGCg5q\naRCrTZ3dPitKwrUa6ibJCIFCIziiriBmjDvTHzkMvoJEap2TVxYNDR6IfINVsQ57\nlOsiC4A2uGq4Lbfld+gjoplJ5GX6qXtTgZ6m7eo0y7U6zm2tkN0=\n-----END RSA PRIVATE KEY-----\n"), + certificate: []byte("-----BEGIN CERTIFICATE-----\nMIIC2zCCAcOgAwIBAgIIAy/jm1gAAdEwDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UE\nChMHWklUQURFTDAeFw0yMzA4MzAwNzExMTVaFw0yNDA4MjkwNzExMTVaMBIxEDAO\nBgNVBAoTB1pJVEFERUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDE\nd3TztGgSb3LBVZn8f60NbFCyZW+F9HPiMCr9F9T45Zc0fgmMwxId0WzRD5Y/3yc1\ndHJzt+Bsxvw12aUHbIPiothqk3lINoFzl2H/cSfIW3nehKyNOUqdBQ8B4mvaqH81\njTjoJ/JTJAwzglHk6JAWjhOyx9aep1yBqYa3QASeTaW9sxkpB0Co1L2UPNhuMwZq\n8RA9NkTfmYVcVBeNqihler5MhruFtqrv+J0ftwc1stw8uCN89ADyr4Ni+e+FeWar\nQs9Bkfc6KLF/5IXa9HCsHNPaaoYPY6I6RSaG4/DKoSKIEe1/GSVG1FTpZ8trUZxv\nU+xXS6gEalXcrJsiX8aXAgMBAAGjNTAzMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE\nDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCx\n/dRNIj0N/16zJhZR/ahkc2AkvDXYxyr4JRT5wK9GQDNl/oaX3debRuSi/tfaXFIX\naJA6PxM4J49ZaiEpLrKfxMz5kAhjKchCBEMcH3mGt+iNZH7EOyTvHjpGrP2OZrsh\nO17yrvN3HuQxIU6roJlqtZz2iAADsoPtwOO4D7hupm9XTMkSnAmlMWOo/q46Jz89\n1sMxB+dXmH/zV0wgwh0omZfLV0u89mvdq269VhcjNBpBYSnN1ccqYWd5iwziob3I\nvaavGHGfkbvRUn/tKftYuTK30q03R+e9YbmlWZ0v695owh2e/apCzowQsCKfSVC8\nOxVyt5XkHq1tWwVyBmFp\n-----END CERTIFICATE-----\n"), + metadata: []byte("\n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n \n urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n \n \n \n"), + options: []ProviderOpts{ + WithLinkingAllowed(), + WithCreationAllowed(), + WithAutoCreation(), + WithAutoUpdate(), + WithBinding("binding"), + WithSignedRequest(), + WithCustomRequestTracker(&requesttracker.RequestTracker{}), + WithEntityID("entityID"), + }, + }, + want: want{ + name: "saml", + linkingAllowed: true, + creationAllowed: true, + autoCreation: true, + autoUpdate: true, + binding: "binding", + withSignedRequest: true, + requesttracker: &requesttracker.RequestTracker{}, + entityID: "entityID", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := assert.New(t) + + provider, err := New(tt.fields.name, tt.fields.rootURL, tt.fields.metadata, tt.fields.certificate, tt.fields.key, tt.fields.options...) + if tt.want.err { + require.Error(t, err) + } else { + require.NoError(t, err) + + a.Equal(tt.want.name, provider.Name()) + a.Equal(tt.want.linkingAllowed, provider.IsLinkingAllowed()) + a.Equal(tt.want.creationAllowed, provider.IsCreationAllowed()) + a.Equal(tt.want.autoCreation, provider.IsAutoCreation()) + a.Equal(tt.want.autoUpdate, provider.IsAutoUpdate()) + a.Equal(tt.want.binding, provider.binding) + a.Equal(tt.want.withSignedRequest, provider.spOptions.SignRequest) + a.Equal(tt.want.requesttracker, provider.requestTracker) + a.Equal(tt.want.entityID, provider.spOptions.EntityID) + } + }) + } +} diff --git a/internal/idp/providers/saml/session.go b/internal/idp/providers/saml/session.go new file mode 100644 index 0000000000..c795493adc --- /dev/null +++ b/internal/idp/providers/saml/session.go @@ -0,0 +1,93 @@ +package saml + +import ( + "bytes" + "context" + "net/http" + "net/url" + + "github.com/crewjam/saml" + "github.com/crewjam/saml/samlsp" + + "github.com/zitadel/zitadel/internal/errors" + "github.com/zitadel/zitadel/internal/idp" +) + +var _ idp.Session = (*Session)(nil) + +// Session is the [idp.Session] implementation for the SAML provider. +type Session struct { + ServiceProvider *samlsp.Middleware + state string + + RequestID string + Request *http.Request + + Assertion *saml.Assertion +} + +// GetAuth implements the [idp.Session] interface. +func (s *Session) GetAuth(ctx context.Context) (string, bool) { + url, _ := url.Parse(s.state) + resp := NewTempResponseWriter() + + request := &http.Request{ + URL: url, + } + s.ServiceProvider.HandleStartAuthFlow( + resp, + request.WithContext(ctx), + ) + + if location := resp.Header().Get("Location"); location != "" { + return idp.Redirect(location) + } + return idp.Form(resp.content.String()) +} + +// FetchUser implements the [idp.Session] interface. +func (s *Session) FetchUser(ctx context.Context) (user idp.User, err error) { + if s.RequestID == "" || s.Request == nil { + return nil, errors.ThrowInvalidArgument(nil, "SAML-d09hy0wkex", "Errors.Intent.ResponseInvalid") + } + + s.Assertion, err = s.ServiceProvider.ServiceProvider.ParseResponse(s.Request, []string{s.RequestID}) + if err != nil { + return nil, errors.ThrowInvalidArgument(err, "SAML-nuo0vphhh9", "Errors.Intent.ResponseInvalid") + } + + userMapper := NewUser() + userMapper.SetID(s.Assertion.Subject.NameID) + for _, statement := range s.Assertion.AttributeStatements { + for _, attribute := range statement.Attributes { + values := make([]string, len(attribute.Values)) + for i := range attribute.Values { + values[i] = attribute.Values[i].Value + } + userMapper.Attributes[attribute.Name] = values + } + } + return userMapper, nil +} + +type TempResponseWriter struct { + header http.Header + content *bytes.Buffer +} + +func (w *TempResponseWriter) Header() http.Header { + return w.header +} + +func (w *TempResponseWriter) Write(content []byte) (int, error) { + return w.content.Write(content) +} + +func (w *TempResponseWriter) WriteHeader(statusCode int) {} + +func NewTempResponseWriter() *TempResponseWriter { + return &TempResponseWriter{ + header: map[string][]string{}, + content: bytes.NewBuffer([]byte{}), + } +} diff --git a/internal/idp/providers/saml/session_test.go b/internal/idp/providers/saml/session_test.go new file mode 100644 index 0000000000..34126ff7ff --- /dev/null +++ b/internal/idp/providers/saml/session_test.go @@ -0,0 +1,214 @@ +package saml + +import ( + "context" + "errors" + "net/http" + "net/url" + "strings" + "testing" + "time" + + "github.com/crewjam/saml" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + caos_errs "github.com/zitadel/zitadel/internal/errors" + "github.com/zitadel/zitadel/internal/idp/providers/saml/requesttracker" +) + +func TestSession_FetchUser(t *testing.T) { + type fields struct { + name string + rootURL string + metadata []byte + key []byte + certificate []byte + options []ProviderOpts + } + type args struct { + intentID string + requestID string + request *http.Request + } + type want struct { + err error + id string + attributes map[string][]string + } + tests := []struct { + name string + fields fields + args args + want want + }{ + { + name: "requestID empty", + fields: fields{ + name: "saml", + key: []byte("-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAxHd087RoEm9ywVWZ/H+tDWxQsmVvhfRz4jAq/RfU+OWXNH4J\njMMSHdFs0Q+WP98nNXRyc7fgbMb8NdmlB2yD4qLYapN5SDaBc5dh/3EnyFt53oSs\njTlKnQUPAeJr2qh/NY046CfyUyQMM4JR5OiQFo4TssfWnqdcgamGt0AEnk2lvbMZ\nKQdAqNS9lDzYbjMGavEQPTZE35mFXFQXjaooZXq+TIa7hbaq7/idH7cHNbLcPLgj\nfPQA8q+DYvnvhXlmq0LPQZH3Oiixf+SF2vRwrBzT2mqGD2OiOkUmhuPwyqEiiBHt\nfxklRtRU6WfLa1Gcb1PsV0uoBGpV3KybIl/GlwIDAQABAoIBAEQjDduLgOCL6Gem\n0X3hpdnW6/HC/jed/Sa//9jBECq2LYeWAqff64ON40hqOHi0YvvGA/+gEOSI6mWe\nsv5tIxxRz+6+cLybsq+tG96kluCE4TJMHy/nY7orS/YiWbd+4odnEApr+D3fbZ/b\nnZ1fDsHTyn8hkYx6jLmnWsJpIHDp7zxD76y7k2Bbg6DZrCGiVxngiLJk23dvz79W\np03lHLM7XE92aFwXQmhfxHGxrbuoB/9eY4ai5IHp36H4fw0vL6NXdNQAo/bhe0p9\nAYB7y0ZumF8Hg0Z/BmMeEzLy6HrYB+VE8cO93pNjhSyH+p2yDB/BlUyTiRLQAoM0\nVTmOZXECgYEA7NGlzpKNhyQEJihVqt0MW0LhKIO/xbBn+XgYfX6GpqPa/ucnMx5/\nVezpl3gK8IU4wPUhAyXXAHJiqNBcEeyxrw0MXLujDVMJgYaLysCLJdvMVgoY08mS\nK5IQivpbozpf4+0y3mOnA+Sy1kbfxv2X8xiWLODRQW3f3q/xoklwOR8CgYEA1GEe\nfaibOFTQAYcIVj77KXtBfYZsX3EGAyfAN9O7cKHq5oaxVstwnF47WxpuVtoKZxCZ\nbNm9D5WvQ9b+Ztpioe42tzwE7Bff/Osj868GcDdRPK7nFlh9N2yVn/D514dOYVwR\n4MBr1KrJzgRWt4QqS4H+to1GzudDTSNlG7gnK4kCgYBUi6AbOHzoYzZL/RhgcJwp\ntJ23nhmH1Su5h2OO4e3mbhcP66w19sxU+8iFN+kH5zfUw26utgKk+TE5vXExQQRK\nT2k7bg2PAzcgk80ybD0BHhA8I0yrx4m0nmfjhe/TPVLgh10iwgbtP+eM0i6v1vc5\nZWyvxu9N4ZEL6lpkqr0y1wKBgG/NAIQd8jhhTW7Aav8cAJQBsqQl038avJOEpYe+\nCnpsgoAAf/K0/f8TDCQVceh+t+MxtdK7fO9rWOxZjWsPo8Si5mLnUaAHoX4/OpnZ\nlYYVWMqdOEFnK+O1Yb7k2GFBdV2DXlX2dc1qavntBsls5ecB89id3pyk2aUN8Pf6\npYQhAoGAMGtrHFely9wyaxI0RTCyfmJbWZHGVGkv6ELK8wneJjdjl82XOBUGCg5q\naRCrTZ3dPitKwrUa6ibJCIFCIziiriBmjDvTHzkMvoJEap2TVxYNDR6IfINVsQ57\nlOsiC4A2uGq4Lbfld+gjoplJ5GX6qXtTgZ6m7eo0y7U6zm2tkN0=\n-----END RSA PRIVATE KEY-----\n"), + certificate: []byte("-----BEGIN CERTIFICATE-----\nMIIC2zCCAcOgAwIBAgIIAy/jm1gAAdEwDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UE\nChMHWklUQURFTDAeFw0yMzA4MzAwNzExMTVaFw0yNDA4MjkwNzExMTVaMBIxEDAO\nBgNVBAoTB1pJVEFERUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDE\nd3TztGgSb3LBVZn8f60NbFCyZW+F9HPiMCr9F9T45Zc0fgmMwxId0WzRD5Y/3yc1\ndHJzt+Bsxvw12aUHbIPiothqk3lINoFzl2H/cSfIW3nehKyNOUqdBQ8B4mvaqH81\njTjoJ/JTJAwzglHk6JAWjhOyx9aep1yBqYa3QASeTaW9sxkpB0Co1L2UPNhuMwZq\n8RA9NkTfmYVcVBeNqihler5MhruFtqrv+J0ftwc1stw8uCN89ADyr4Ni+e+FeWar\nQs9Bkfc6KLF/5IXa9HCsHNPaaoYPY6I6RSaG4/DKoSKIEe1/GSVG1FTpZ8trUZxv\nU+xXS6gEalXcrJsiX8aXAgMBAAGjNTAzMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE\nDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCx\n/dRNIj0N/16zJhZR/ahkc2AkvDXYxyr4JRT5wK9GQDNl/oaX3debRuSi/tfaXFIX\naJA6PxM4J49ZaiEpLrKfxMz5kAhjKchCBEMcH3mGt+iNZH7EOyTvHjpGrP2OZrsh\nO17yrvN3HuQxIU6roJlqtZz2iAADsoPtwOO4D7hupm9XTMkSnAmlMWOo/q46Jz89\n1sMxB+dXmH/zV0wgwh0omZfLV0u89mvdq269VhcjNBpBYSnN1ccqYWd5iwziob3I\nvaavGHGfkbvRUn/tKftYuTK30q03R+e9YbmlWZ0v695owh2e/apCzowQsCKfSVC8\nOxVyt5XkHq1tWwVyBmFp\n-----END CERTIFICATE-----\n"), + metadata: []byte("\n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n \n urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n \n \n \n"), + options: []ProviderOpts{ + WithLinkingAllowed(), + WithCreationAllowed(), + WithAutoCreation(), + WithAutoUpdate(), + WithBinding(saml.HTTPRedirectBinding), + WithSignedRequest(), + WithCustomRequestTracker(&requesttracker.RequestTracker{}), + }, + rootURL: "http://localhost:8080/idps/228968792372281708/", + }, + args: args{ + request: httpPostFormRequest(t, + "http://localhost:8080/idps/228968792372281708/saml/acs", + "232881438356144492", + "PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9ImlkLTA4ZTA3MTFhYzYwZjE2Mzc2MTdhYjZhNDZkZDk0ZTZkMWQ3MDgzMWQiIEluUmVzcG9uc2VUbz0iaWQtYjIyYzkwZGI4OGJmMDFkODJmZmIwYTdiNmZlMjVhYzlmY2IyYzY3OSIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMjMtMDktMjFUMTM6NDk6MjMuOTM4WiIgRGVzdGluYXRpb249Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9pZHBzLzIyODk2ODc5MjM3MjI4MTcwOC9zYW1sL2FjcyI+PHNhbWw6SXNzdWVyIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6ZW50aXR5Ij5odHRwOi8vbG9jYWxob3N0OjgwMDAvbWV0YWRhdGE8L3NhbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPjxkczpSZWZlcmVuY2UgVVJJPSIjaWQtMDhlMDcxMWFjNjBmMTYzNzYxN2FiNmE0NmRkOTRlNmQxZDcwODMxZCI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+OWF2ektOOWhpazE4ZkFRdnZNZzJBZFoyYm9VPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5VMkFJOFNzMng5TDAwV3RkaVFlbldCSVFCcGJLNnU4ZU9XVGE2WExKR0lWMWg4d3VOdURqM2luVGpMVUhITkNUSHhTdTFXSE9OSW1CL3QwWlE1Z3EvYXlkcVVqaEVkNG50LysyaXBKelZjZHVHUG5nYjlMWjJ5R1Rsd1JiQkcyMzd4eCtlRWhUMUcrTUFGa3BtbnUrei9RN09vUjdQWHVZOWt6NTRCb0tVM1htK1UyWm9GVy9pVjhId01kYTJMajVLT0pjcnppSWVtNHF0dHlIZXBqcjI3NUhPM2hybzgvVW0xMm8wdk10OUhwaHJua0RNVzgzM3Q5c0k2aW5GRndiOUJkdm5ORkVxYkhCZ2RsemR5T0NqaWdreVNlTzZQNzhQQlhUTWlhM3RVaGxEL0dlZ2hmbTJ4NVI1Q2QrOXJ5RktjYlBKLzlUaFhwbTlIYUJ4R1RZNEE9PTwvZHM6U2lnbmF0dXJlVmFsdWU+PGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJREJ6Q0NBZStnQXdJQkFnSUpBUHIvTXJsYzhFR2hNQTBHQ1NxR1NJYjNEUUVCQlFVQU1Cb3hHREFXQmdOVkJBTU1EM2QzZHk1bGVHRnRjR3hsTG1OdmJUQWVGdzB4TlRFeU1qZ3hPVEU1TkRWYUZ3MHlOVEV5TWpVeE9URTVORFZhTUJveEdEQVdCZ05WQkFNTUQzZDNkeTVsZUdGdGNHeGxMbU52YlRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTkRvV3pMb3M0TFd4VG44R3l1MmxFYmw0V2NlbFViZ0xONXpZbTRyb244QWhzK3J2Y3N1MnprZEQvczZqZEdKSThXcUpLaFlLMnU2MXlnblhnQVpxQzZnZ3RGUG5CcGl6Y0R6amdORDJnK2F1Y1NvVU9ESHQ2N2YwZlF1QW11cE4venA1TVp5c0o2SUhMSm5ZTE5wZkpZazk2bFJ6OU9Ebk8xTXBxdHI5UFd4bStwejduenE1RjB2UmVwa2dwY1J4djZ1ZlFCamxyRnl0Y2N5RVZkWHJ2RnRralhjbmhWVk5TUjRrSHVPT01TNkQ3cGViU0oxbXJDbXNoYkQ1U1gxalhQQktGUEFqb3pZWDZQeHFMeFV4MVk0ZmFGRWY0TUJCVmNJbnlCNG9VUk5CMnM1OWhFRWkyanE5aXpORTdFYkVLNkJZNXNFaG9DUGw5bTMyekU2bGprQ0F3RUFBYU5RTUU0d0hRWURWUjBPQkJZRUZCOVprbEMxT3JrMnpsNTZ6ZzA4ZWk3c3MvK2lNQjhHQTFVZEl3UVlNQmFBRkI5WmtsQzFPcmsyemw1NnpnMDhlaTdzcy8raU1Bd0dBMVVkRXdRRk1BTUJBZjh3RFFZSktvWklodmNOQVFFRkJRQURnZ0VCQUFWb1RTUTVwQWlydzhPUjlGWjFiUlN1VERoWTl1eHpsL09MN2xVbXN2MmNNTmVDQjNCUlpxbTNtRnQrY3dOOEdzSDZmM3V2Tk9OSWhnRnBUR041TEVjWFF6ODl6SkV6QitxYUhxbWJGcEhRbC9zeDJCOGV6TmdULzg4MkgySUgwMGRYRVNFZnkvKzFnSGcycHhqR25oUkJONmVsL2dTYURpeVNJTUtiaWxEcmZmdXZ4aUNmYnBQTjBOUlJpUEpoZDJheTlLdUwvUnhRUmwxZ2w5Y0hhV2lvdVdXYmExYlNCYjJaUGh2MnJQTVVzRm85OG50a0dDT2JEWDZZMVNwa3Ftb1RicnNiR0ZzVEcyREx4bnZyNEdkTjFCU3IwVXUvS1YzYWRqNDdXa1hWUGVNWVF0aS9iUW14UUI4dFJGaHJ3ODBxYWtUTFV6cmVPOTZXemxCQk10WT08L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT48c2FtbHA6U3RhdHVzPjxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWxwOlN0YXR1cz48c2FtbDpBc3NlcnRpb24geG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9ImlkLTQ1ZTcyMTkyNjU4NmNkZGUzNDQ2NmI0MzQ5NDg4YjA5MDZhNTU3OGIiIElzc3VlSW5zdGFudD0iMjAyMy0wOS0yMVQxMzo0OToyMy45NDFaIiBWZXJzaW9uPSIyLjAiPjxzYW1sOklzc3VlciBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OmVudGl0eSI+aHR0cDovL2xvY2FsaG9zdDo4MDAwL21ldGFkYXRhPC9zYW1sOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz48ZHM6UmVmZXJlbmNlIFVSST0iI2lkLTQ1ZTcyMTkyNjU4NmNkZGUzNDQ2NmI0MzQ5NDg4YjA5MDZhNTU3OGIiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPmVHeTMzcWpjZjYrT3R4YlVpVm5DUjFNdnlaaz08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+cDBEekU3Q0lTVTVNQjNvclpReGtUWHFsQXJRMjlFb0tXOUZuVWx5aUhYWGtmK2lXNGZ5SW9wYjcrTUk1VUk5TjVTQVdiVzV4U0R3NFJMMWNDaVNxdWJyU29pRi83MXdiM29naWlQOHhyYmJiajY3RHZMcUU3OVJDQXdySDlEU0FlZFlReVBOQ0tFQ0U0L3NvdnFEeUg5OTAwZEI4aTQ3VHdQRkhIclBlWE8wUGVoR0NWNTVEeTZJdC92Ull1VFRqU0tiVTczV014c1A5Mk9yVEdqamN1Smx1bFdSclBxNDk0aEpNMFJxV3gwN2RoWHJEbzlJY1JpK2RCOVhRN3UwbGJicEthQ0p6R2RiUG9JanJ1ZDhaWnh2djdiaDFtV1lEeGFodExvd2I1eGtDWlQrNFMybklrSWgwNEoxUURsZm1WSVdlRWljVW5odXpIcVBkU1RldklnPT08L2RzOlNpZ25hdHVyZVZhbHVlPjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSURCekNDQWUrZ0F3SUJBZ0lKQVByL01ybGM4RUdoTUEwR0NTcUdTSWIzRFFFQkJRVUFNQm94R0RBV0JnTlZCQU1NRDNkM2R5NWxlR0Z0Y0d4bExtTnZiVEFlRncweE5URXlNamd4T1RFNU5EVmFGdzB5TlRFeU1qVXhPVEU1TkRWYU1Cb3hHREFXQmdOVkJBTU1EM2QzZHk1bGVHRnRjR3hsTG1OdmJUQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU5Eb1d6TG9zNExXeFRuOEd5dTJsRWJsNFdjZWxVYmdMTjV6WW00cm9uOEFocytydmNzdTJ6a2REL3M2amRHSkk4V3FKS2hZSzJ1NjF5Z25YZ0FacUM2Z2d0RlBuQnBpemNEempnTkQyZythdWNTb1VPREh0NjdmMGZRdUFtdXBOL3pwNU1aeXNKNklITEpuWUxOcGZKWWs5NmxSejlPRG5PMU1wcXRyOVBXeG0rcHo3bnpxNUYwdlJlcGtncGNSeHY2dWZRQmpsckZ5dGNjeUVWZFhydkZ0a2pYY25oVlZOU1I0a0h1T09NUzZEN3BlYlNKMW1yQ21zaGJENVNYMWpYUEJLRlBBam96WVg2UHhxTHhVeDFZNGZhRkVmNE1CQlZjSW55QjRvVVJOQjJzNTloRUVpMmpxOWl6TkU3RWJFSzZCWTVzRWhvQ1BsOW0zMnpFNmxqa0NBd0VBQWFOUU1FNHdIUVlEVlIwT0JCWUVGQjlaa2xDMU9yazJ6bDU2emcwOGVpN3NzLytpTUI4R0ExVWRJd1FZTUJhQUZCOVprbEMxT3JrMnpsNTZ6ZzA4ZWk3c3MvK2lNQXdHQTFVZEV3UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUZCUUFEZ2dFQkFBVm9UU1E1cEFpcnc4T1I5RloxYlJTdVREaFk5dXh6bC9PTDdsVW1zdjJjTU5lQ0IzQlJacW0zbUZ0K2N3TjhHc0g2ZjN1dk5PTkloZ0ZwVEdONUxFY1hRejg5ekpFekIrcWFIcW1iRnBIUWwvc3gyQjhlek5nVC84ODJIMklIMDBkWEVTRWZ5LysxZ0hnMnB4akduaFJCTjZlbC9nU2FEaXlTSU1LYmlsRHJmZnV2eGlDZmJwUE4wTlJSaVBKaGQyYXk5S3VML1J4UVJsMWdsOWNIYVdpb3VXV2JhMWJTQmIyWlBodjJyUE1Vc0ZvOThudGtHQ09iRFg2WTFTcGtxbW9UYnJzYkdGc1RHMkRMeG52cjRHZE4xQlNyMFV1L0tWM2FkajQ3V2tYVlBlTVlRdGkvYlFteFFCOHRSRmhydzgwcWFrVExVenJlTzk2V3psQkJNdFk9PC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWw6U3ViamVjdD48c2FtbDpOYW1lSUQgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6bmFtZWlkLWZvcm1hdDp0cmFuc2llbnQiIE5hbWVRdWFsaWZpZXI9Imh0dHA6Ly9sb2NhbGhvc3Q6ODAwMC9tZXRhZGF0YSIgU1BOYW1lUXVhbGlmaWVyPSJodHRwOi8vbG9jYWxob3N0OjgwODAvaWRwcy8yMjg5Njg3OTIzNzIyODE3MDgvc2FtbC9tZXRhZGF0YSI+YWxpY2VAZXhhbXBsZS5jb208L3NhbWw6TmFtZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBBZGRyZXNzPSJbOjoxXTo1OTMzNCIgSW5SZXNwb25zZVRvPSJpZC1iMjJjOTBkYjg4YmYwMWQ4MmZmYjBhN2I2ZmUyNWFjOWZjYjJjNjc5IiBOb3RPbk9yQWZ0ZXI9IjIwMjMtMDktMjFUMTM6NTA6NTMuOTM4WiIgUmVjaXBpZW50PSJodHRwOi8vbG9jYWxob3N0OjgwODAvaWRwcy8yMjg5Njg3OTIzNzIyODE3MDgvc2FtbC9hY3MiLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWw6U3ViamVjdD48c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAyMy0wOS0yMVQxMzo0OToxNC4yOThaIiBOb3RPbk9yQWZ0ZXI9IjIwMjMtMDktMjFUMTM6NTA6NDQuMjk4WiI+PHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48c2FtbDpBdWRpZW5jZT5odHRwOi8vbG9jYWxob3N0OjgwODAvaWRwcy8yMjg5Njg3OTIzNzIyODE3MDgvc2FtbC9tZXRhZGF0YTwvc2FtbDpBdWRpZW5jZT48L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48L3NhbWw6Q29uZGl0aW9ucz48c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMjMtMDktMjFUMTM6NDc6MzUuMTAzWiIgU2Vzc2lvbkluZGV4PSI0YzM5YjE5NTQyYzdjZTFjMzllOWMwNWJlMTdhNzJhNmQ4OGU1NWE3ZGFiYWRhZWQ3ODYxMDBiOWUzODBmYTA4Ij48c2FtbDpTdWJqZWN0TG9jYWxpdHkgQWRkcmVzcz0iWzo6MV06NTkzMzQiLz48c2FtbDpBdXRobkNvbnRleHQ+PHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmRQcm90ZWN0ZWRUcmFuc3BvcnQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+PC9zYW1sOkF1dGhuQ29udGV4dD48L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+PHNhbWw6QXR0cmlidXRlU3RhdGVtZW50PjxzYW1sOkF0dHJpYnV0ZSBGcmllbmRseU5hbWU9InVpZCIgTmFtZT0idXJuOm9pZDowLjkuMjM0Mi4xOTIwMDMwMC4xMDAuMS4xIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OnVyaSI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+YWxpY2U8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgRnJpZW5kbHlOYW1lPSJlZHVQZXJzb25QcmluY2lwYWxOYW1lIiBOYW1lPSJ1cm46b2lkOjEuMy42LjEuNC4xLjU5MjMuMS4xLjEuNiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDp1cmkiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPmFsaWNlQGV4YW1wbGUuY29tPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIEZyaWVuZGx5TmFtZT0ic24iIE5hbWU9InVybjpvaWQ6Mi41LjQuNCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDp1cmkiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPlNtaXRoPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIEZyaWVuZGx5TmFtZT0iZ2l2ZW5OYW1lIiBOYW1lPSJ1cm46b2lkOjIuNS40LjQyIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OnVyaSI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+QWxpY2U8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgRnJpZW5kbHlOYW1lPSJjbiIgTmFtZT0idXJuOm9pZDoyLjUuNC4zIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OnVyaSI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+QWxpY2UgU21pdGg8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgRnJpZW5kbHlOYW1lPSJlZHVQZXJzb25BZmZpbGlhdGlvbiIgTmFtZT0idXJuOm9pZDoxLjMuNi4xLjQuMS41OTIzLjEuMS4xLjEiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6dXJpIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6c3RyaW5nIj5BZG1pbmlzdHJhdG9yczwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6c3RyaW5nIj5Vc2Vyczwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg==", + ), + requestID: "", + }, + want: want{ + err: caos_errs.ThrowInvalidArgument(nil, "SAML-d09hy0wkex", "Errors.Intent.ResponseInvalid"), + }, + }, + { + name: "request empty", + fields: fields{ + name: "saml", + key: []byte("-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAxHd087RoEm9ywVWZ/H+tDWxQsmVvhfRz4jAq/RfU+OWXNH4J\njMMSHdFs0Q+WP98nNXRyc7fgbMb8NdmlB2yD4qLYapN5SDaBc5dh/3EnyFt53oSs\njTlKnQUPAeJr2qh/NY046CfyUyQMM4JR5OiQFo4TssfWnqdcgamGt0AEnk2lvbMZ\nKQdAqNS9lDzYbjMGavEQPTZE35mFXFQXjaooZXq+TIa7hbaq7/idH7cHNbLcPLgj\nfPQA8q+DYvnvhXlmq0LPQZH3Oiixf+SF2vRwrBzT2mqGD2OiOkUmhuPwyqEiiBHt\nfxklRtRU6WfLa1Gcb1PsV0uoBGpV3KybIl/GlwIDAQABAoIBAEQjDduLgOCL6Gem\n0X3hpdnW6/HC/jed/Sa//9jBECq2LYeWAqff64ON40hqOHi0YvvGA/+gEOSI6mWe\nsv5tIxxRz+6+cLybsq+tG96kluCE4TJMHy/nY7orS/YiWbd+4odnEApr+D3fbZ/b\nnZ1fDsHTyn8hkYx6jLmnWsJpIHDp7zxD76y7k2Bbg6DZrCGiVxngiLJk23dvz79W\np03lHLM7XE92aFwXQmhfxHGxrbuoB/9eY4ai5IHp36H4fw0vL6NXdNQAo/bhe0p9\nAYB7y0ZumF8Hg0Z/BmMeEzLy6HrYB+VE8cO93pNjhSyH+p2yDB/BlUyTiRLQAoM0\nVTmOZXECgYEA7NGlzpKNhyQEJihVqt0MW0LhKIO/xbBn+XgYfX6GpqPa/ucnMx5/\nVezpl3gK8IU4wPUhAyXXAHJiqNBcEeyxrw0MXLujDVMJgYaLysCLJdvMVgoY08mS\nK5IQivpbozpf4+0y3mOnA+Sy1kbfxv2X8xiWLODRQW3f3q/xoklwOR8CgYEA1GEe\nfaibOFTQAYcIVj77KXtBfYZsX3EGAyfAN9O7cKHq5oaxVstwnF47WxpuVtoKZxCZ\nbNm9D5WvQ9b+Ztpioe42tzwE7Bff/Osj868GcDdRPK7nFlh9N2yVn/D514dOYVwR\n4MBr1KrJzgRWt4QqS4H+to1GzudDTSNlG7gnK4kCgYBUi6AbOHzoYzZL/RhgcJwp\ntJ23nhmH1Su5h2OO4e3mbhcP66w19sxU+8iFN+kH5zfUw26utgKk+TE5vXExQQRK\nT2k7bg2PAzcgk80ybD0BHhA8I0yrx4m0nmfjhe/TPVLgh10iwgbtP+eM0i6v1vc5\nZWyvxu9N4ZEL6lpkqr0y1wKBgG/NAIQd8jhhTW7Aav8cAJQBsqQl038avJOEpYe+\nCnpsgoAAf/K0/f8TDCQVceh+t+MxtdK7fO9rWOxZjWsPo8Si5mLnUaAHoX4/OpnZ\nlYYVWMqdOEFnK+O1Yb7k2GFBdV2DXlX2dc1qavntBsls5ecB89id3pyk2aUN8Pf6\npYQhAoGAMGtrHFely9wyaxI0RTCyfmJbWZHGVGkv6ELK8wneJjdjl82XOBUGCg5q\naRCrTZ3dPitKwrUa6ibJCIFCIziiriBmjDvTHzkMvoJEap2TVxYNDR6IfINVsQ57\nlOsiC4A2uGq4Lbfld+gjoplJ5GX6qXtTgZ6m7eo0y7U6zm2tkN0=\n-----END RSA PRIVATE KEY-----\n"), + certificate: []byte("-----BEGIN CERTIFICATE-----\nMIIC2zCCAcOgAwIBAgIIAy/jm1gAAdEwDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UE\nChMHWklUQURFTDAeFw0yMzA4MzAwNzExMTVaFw0yNDA4MjkwNzExMTVaMBIxEDAO\nBgNVBAoTB1pJVEFERUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDE\nd3TztGgSb3LBVZn8f60NbFCyZW+F9HPiMCr9F9T45Zc0fgmMwxId0WzRD5Y/3yc1\ndHJzt+Bsxvw12aUHbIPiothqk3lINoFzl2H/cSfIW3nehKyNOUqdBQ8B4mvaqH81\njTjoJ/JTJAwzglHk6JAWjhOyx9aep1yBqYa3QASeTaW9sxkpB0Co1L2UPNhuMwZq\n8RA9NkTfmYVcVBeNqihler5MhruFtqrv+J0ftwc1stw8uCN89ADyr4Ni+e+FeWar\nQs9Bkfc6KLF/5IXa9HCsHNPaaoYPY6I6RSaG4/DKoSKIEe1/GSVG1FTpZ8trUZxv\nU+xXS6gEalXcrJsiX8aXAgMBAAGjNTAzMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE\nDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCx\n/dRNIj0N/16zJhZR/ahkc2AkvDXYxyr4JRT5wK9GQDNl/oaX3debRuSi/tfaXFIX\naJA6PxM4J49ZaiEpLrKfxMz5kAhjKchCBEMcH3mGt+iNZH7EOyTvHjpGrP2OZrsh\nO17yrvN3HuQxIU6roJlqtZz2iAADsoPtwOO4D7hupm9XTMkSnAmlMWOo/q46Jz89\n1sMxB+dXmH/zV0wgwh0omZfLV0u89mvdq269VhcjNBpBYSnN1ccqYWd5iwziob3I\nvaavGHGfkbvRUn/tKftYuTK30q03R+e9YbmlWZ0v695owh2e/apCzowQsCKfSVC8\nOxVyt5XkHq1tWwVyBmFp\n-----END CERTIFICATE-----\n"), + metadata: []byte("\n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n \n urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n \n \n \n"), + options: []ProviderOpts{ + WithLinkingAllowed(), + WithCreationAllowed(), + WithAutoCreation(), + WithAutoUpdate(), + WithBinding(saml.HTTPRedirectBinding), + WithSignedRequest(), + WithCustomRequestTracker(&requesttracker.RequestTracker{}), + }, + rootURL: "http://localhost:8080/idps/228968792372281708/", + }, + args: args{ + request: nil, + requestID: "id-b22c90db88bf01d82ffb0a7b6fe25ac9fcb2c679", + }, + want: want{ + err: caos_errs.ThrowInvalidArgument(nil, "SAML-d09hy0wkex", "Errors.Intent.ResponseInvalid"), + }, + }, + { + name: "response invalid", + fields: fields{ + name: "saml", + key: []byte("-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAxHd087RoEm9ywVWZ/H+tDWxQsmVvhfRz4jAq/RfU+OWXNH4J\njMMSHdFs0Q+WP98nNXRyc7fgbMb8NdmlB2yD4qLYapN5SDaBc5dh/3EnyFt53oSs\njTlKnQUPAeJr2qh/NY046CfyUyQMM4JR5OiQFo4TssfWnqdcgamGt0AEnk2lvbMZ\nKQdAqNS9lDzYbjMGavEQPTZE35mFXFQXjaooZXq+TIa7hbaq7/idH7cHNbLcPLgj\nfPQA8q+DYvnvhXlmq0LPQZH3Oiixf+SF2vRwrBzT2mqGD2OiOkUmhuPwyqEiiBHt\nfxklRtRU6WfLa1Gcb1PsV0uoBGpV3KybIl/GlwIDAQABAoIBAEQjDduLgOCL6Gem\n0X3hpdnW6/HC/jed/Sa//9jBECq2LYeWAqff64ON40hqOHi0YvvGA/+gEOSI6mWe\nsv5tIxxRz+6+cLybsq+tG96kluCE4TJMHy/nY7orS/YiWbd+4odnEApr+D3fbZ/b\nnZ1fDsHTyn8hkYx6jLmnWsJpIHDp7zxD76y7k2Bbg6DZrCGiVxngiLJk23dvz79W\np03lHLM7XE92aFwXQmhfxHGxrbuoB/9eY4ai5IHp36H4fw0vL6NXdNQAo/bhe0p9\nAYB7y0ZumF8Hg0Z/BmMeEzLy6HrYB+VE8cO93pNjhSyH+p2yDB/BlUyTiRLQAoM0\nVTmOZXECgYEA7NGlzpKNhyQEJihVqt0MW0LhKIO/xbBn+XgYfX6GpqPa/ucnMx5/\nVezpl3gK8IU4wPUhAyXXAHJiqNBcEeyxrw0MXLujDVMJgYaLysCLJdvMVgoY08mS\nK5IQivpbozpf4+0y3mOnA+Sy1kbfxv2X8xiWLODRQW3f3q/xoklwOR8CgYEA1GEe\nfaibOFTQAYcIVj77KXtBfYZsX3EGAyfAN9O7cKHq5oaxVstwnF47WxpuVtoKZxCZ\nbNm9D5WvQ9b+Ztpioe42tzwE7Bff/Osj868GcDdRPK7nFlh9N2yVn/D514dOYVwR\n4MBr1KrJzgRWt4QqS4H+to1GzudDTSNlG7gnK4kCgYBUi6AbOHzoYzZL/RhgcJwp\ntJ23nhmH1Su5h2OO4e3mbhcP66w19sxU+8iFN+kH5zfUw26utgKk+TE5vXExQQRK\nT2k7bg2PAzcgk80ybD0BHhA8I0yrx4m0nmfjhe/TPVLgh10iwgbtP+eM0i6v1vc5\nZWyvxu9N4ZEL6lpkqr0y1wKBgG/NAIQd8jhhTW7Aav8cAJQBsqQl038avJOEpYe+\nCnpsgoAAf/K0/f8TDCQVceh+t+MxtdK7fO9rWOxZjWsPo8Si5mLnUaAHoX4/OpnZ\nlYYVWMqdOEFnK+O1Yb7k2GFBdV2DXlX2dc1qavntBsls5ecB89id3pyk2aUN8Pf6\npYQhAoGAMGtrHFely9wyaxI0RTCyfmJbWZHGVGkv6ELK8wneJjdjl82XOBUGCg5q\naRCrTZ3dPitKwrUa6ibJCIFCIziiriBmjDvTHzkMvoJEap2TVxYNDR6IfINVsQ57\nlOsiC4A2uGq4Lbfld+gjoplJ5GX6qXtTgZ6m7eo0y7U6zm2tkN0=\n-----END RSA PRIVATE KEY-----\n"), + certificate: []byte("-----BEGIN CERTIFICATE-----\nMIIC2zCCAcOgAwIBAgIIAy/jm1gAAdEwDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UE\nChMHWklUQURFTDAeFw0yMzA4MzAwNzExMTVaFw0yNDA4MjkwNzExMTVaMBIxEDAO\nBgNVBAoTB1pJVEFERUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDE\nd3TztGgSb3LBVZn8f60NbFCyZW+F9HPiMCr9F9T45Zc0fgmMwxId0WzRD5Y/3yc1\ndHJzt+Bsxvw12aUHbIPiothqk3lINoFzl2H/cSfIW3nehKyNOUqdBQ8B4mvaqH81\njTjoJ/JTJAwzglHk6JAWjhOyx9aep1yBqYa3QASeTaW9sxkpB0Co1L2UPNhuMwZq\n8RA9NkTfmYVcVBeNqihler5MhruFtqrv+J0ftwc1stw8uCN89ADyr4Ni+e+FeWar\nQs9Bkfc6KLF/5IXa9HCsHNPaaoYPY6I6RSaG4/DKoSKIEe1/GSVG1FTpZ8trUZxv\nU+xXS6gEalXcrJsiX8aXAgMBAAGjNTAzMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE\nDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCx\n/dRNIj0N/16zJhZR/ahkc2AkvDXYxyr4JRT5wK9GQDNl/oaX3debRuSi/tfaXFIX\naJA6PxM4J49ZaiEpLrKfxMz5kAhjKchCBEMcH3mGt+iNZH7EOyTvHjpGrP2OZrsh\nO17yrvN3HuQxIU6roJlqtZz2iAADsoPtwOO4D7hupm9XTMkSnAmlMWOo/q46Jz89\n1sMxB+dXmH/zV0wgwh0omZfLV0u89mvdq269VhcjNBpBYSnN1ccqYWd5iwziob3I\nvaavGHGfkbvRUn/tKftYuTK30q03R+e9YbmlWZ0v695owh2e/apCzowQsCKfSVC8\nOxVyt5XkHq1tWwVyBmFp\n-----END CERTIFICATE-----\n"), + metadata: []byte("\n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n \n urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n \n \n \n"), + options: []ProviderOpts{ + WithLinkingAllowed(), + WithCreationAllowed(), + WithAutoCreation(), + WithAutoUpdate(), + WithBinding(saml.HTTPRedirectBinding), + WithSignedRequest(), + WithCustomRequestTracker(&requesttracker.RequestTracker{}), + }, + rootURL: "http://localhost:8080/idps/228968792372281708/", + }, + args: args{ + request: httpPostFormRequest(t, + "http://localhost:8080/idps/228968792372281708/saml/acs", + "232881438356144492", + "no base64", + ), + requestID: "id-b22c90db88bf01d82ffb0a7b6fe25ac9fcb2c679", + }, + want: want{ + err: caos_errs.ThrowInvalidArgument(nil, "SAML-nuo0vphhh9", "Errors.Intent.ResponseInvalid"), + }, + }, + { + name: "post with user param", + fields: fields{ + name: "saml", + key: []byte("-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAxHd087RoEm9ywVWZ/H+tDWxQsmVvhfRz4jAq/RfU+OWXNH4J\njMMSHdFs0Q+WP98nNXRyc7fgbMb8NdmlB2yD4qLYapN5SDaBc5dh/3EnyFt53oSs\njTlKnQUPAeJr2qh/NY046CfyUyQMM4JR5OiQFo4TssfWnqdcgamGt0AEnk2lvbMZ\nKQdAqNS9lDzYbjMGavEQPTZE35mFXFQXjaooZXq+TIa7hbaq7/idH7cHNbLcPLgj\nfPQA8q+DYvnvhXlmq0LPQZH3Oiixf+SF2vRwrBzT2mqGD2OiOkUmhuPwyqEiiBHt\nfxklRtRU6WfLa1Gcb1PsV0uoBGpV3KybIl/GlwIDAQABAoIBAEQjDduLgOCL6Gem\n0X3hpdnW6/HC/jed/Sa//9jBECq2LYeWAqff64ON40hqOHi0YvvGA/+gEOSI6mWe\nsv5tIxxRz+6+cLybsq+tG96kluCE4TJMHy/nY7orS/YiWbd+4odnEApr+D3fbZ/b\nnZ1fDsHTyn8hkYx6jLmnWsJpIHDp7zxD76y7k2Bbg6DZrCGiVxngiLJk23dvz79W\np03lHLM7XE92aFwXQmhfxHGxrbuoB/9eY4ai5IHp36H4fw0vL6NXdNQAo/bhe0p9\nAYB7y0ZumF8Hg0Z/BmMeEzLy6HrYB+VE8cO93pNjhSyH+p2yDB/BlUyTiRLQAoM0\nVTmOZXECgYEA7NGlzpKNhyQEJihVqt0MW0LhKIO/xbBn+XgYfX6GpqPa/ucnMx5/\nVezpl3gK8IU4wPUhAyXXAHJiqNBcEeyxrw0MXLujDVMJgYaLysCLJdvMVgoY08mS\nK5IQivpbozpf4+0y3mOnA+Sy1kbfxv2X8xiWLODRQW3f3q/xoklwOR8CgYEA1GEe\nfaibOFTQAYcIVj77KXtBfYZsX3EGAyfAN9O7cKHq5oaxVstwnF47WxpuVtoKZxCZ\nbNm9D5WvQ9b+Ztpioe42tzwE7Bff/Osj868GcDdRPK7nFlh9N2yVn/D514dOYVwR\n4MBr1KrJzgRWt4QqS4H+to1GzudDTSNlG7gnK4kCgYBUi6AbOHzoYzZL/RhgcJwp\ntJ23nhmH1Su5h2OO4e3mbhcP66w19sxU+8iFN+kH5zfUw26utgKk+TE5vXExQQRK\nT2k7bg2PAzcgk80ybD0BHhA8I0yrx4m0nmfjhe/TPVLgh10iwgbtP+eM0i6v1vc5\nZWyvxu9N4ZEL6lpkqr0y1wKBgG/NAIQd8jhhTW7Aav8cAJQBsqQl038avJOEpYe+\nCnpsgoAAf/K0/f8TDCQVceh+t+MxtdK7fO9rWOxZjWsPo8Si5mLnUaAHoX4/OpnZ\nlYYVWMqdOEFnK+O1Yb7k2GFBdV2DXlX2dc1qavntBsls5ecB89id3pyk2aUN8Pf6\npYQhAoGAMGtrHFely9wyaxI0RTCyfmJbWZHGVGkv6ELK8wneJjdjl82XOBUGCg5q\naRCrTZ3dPitKwrUa6ibJCIFCIziiriBmjDvTHzkMvoJEap2TVxYNDR6IfINVsQ57\nlOsiC4A2uGq4Lbfld+gjoplJ5GX6qXtTgZ6m7eo0y7U6zm2tkN0=\n-----END RSA PRIVATE KEY-----\n"), + certificate: []byte("-----BEGIN CERTIFICATE-----\nMIIC2zCCAcOgAwIBAgIIAy/jm1gAAdEwDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UE\nChMHWklUQURFTDAeFw0yMzA4MzAwNzExMTVaFw0yNDA4MjkwNzExMTVaMBIxEDAO\nBgNVBAoTB1pJVEFERUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDE\nd3TztGgSb3LBVZn8f60NbFCyZW+F9HPiMCr9F9T45Zc0fgmMwxId0WzRD5Y/3yc1\ndHJzt+Bsxvw12aUHbIPiothqk3lINoFzl2H/cSfIW3nehKyNOUqdBQ8B4mvaqH81\njTjoJ/JTJAwzglHk6JAWjhOyx9aep1yBqYa3QASeTaW9sxkpB0Co1L2UPNhuMwZq\n8RA9NkTfmYVcVBeNqihler5MhruFtqrv+J0ftwc1stw8uCN89ADyr4Ni+e+FeWar\nQs9Bkfc6KLF/5IXa9HCsHNPaaoYPY6I6RSaG4/DKoSKIEe1/GSVG1FTpZ8trUZxv\nU+xXS6gEalXcrJsiX8aXAgMBAAGjNTAzMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE\nDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCx\n/dRNIj0N/16zJhZR/ahkc2AkvDXYxyr4JRT5wK9GQDNl/oaX3debRuSi/tfaXFIX\naJA6PxM4J49ZaiEpLrKfxMz5kAhjKchCBEMcH3mGt+iNZH7EOyTvHjpGrP2OZrsh\nO17yrvN3HuQxIU6roJlqtZz2iAADsoPtwOO4D7hupm9XTMkSnAmlMWOo/q46Jz89\n1sMxB+dXmH/zV0wgwh0omZfLV0u89mvdq269VhcjNBpBYSnN1ccqYWd5iwziob3I\nvaavGHGfkbvRUn/tKftYuTK30q03R+e9YbmlWZ0v695owh2e/apCzowQsCKfSVC8\nOxVyt5XkHq1tWwVyBmFp\n-----END CERTIFICATE-----\n"), + metadata: []byte("\n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n \n urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n \n \n \n"), + options: []ProviderOpts{ + WithLinkingAllowed(), + WithCreationAllowed(), + WithAutoCreation(), + WithAutoUpdate(), + WithBinding(saml.HTTPRedirectBinding), + WithSignedRequest(), + WithCustomRequestTracker(&requesttracker.RequestTracker{}), + }, + rootURL: "http://localhost:8080/idps/228968792372281708/", + }, + args: args{ + request: httpPostFormRequest(t, + "http://localhost:8080/idps/228968792372281708/saml/acs", + "232881438356144492", + "PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9ImlkLTA4ZTA3MTFhYzYwZjE2Mzc2MTdhYjZhNDZkZDk0ZTZkMWQ3MDgzMWQiIEluUmVzcG9uc2VUbz0iaWQtYjIyYzkwZGI4OGJmMDFkODJmZmIwYTdiNmZlMjVhYzlmY2IyYzY3OSIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMjMtMDktMjFUMTM6NDk6MjMuOTM4WiIgRGVzdGluYXRpb249Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9pZHBzLzIyODk2ODc5MjM3MjI4MTcwOC9zYW1sL2FjcyI+PHNhbWw6SXNzdWVyIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6ZW50aXR5Ij5odHRwOi8vbG9jYWxob3N0OjgwMDAvbWV0YWRhdGE8L3NhbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPjxkczpSZWZlcmVuY2UgVVJJPSIjaWQtMDhlMDcxMWFjNjBmMTYzNzYxN2FiNmE0NmRkOTRlNmQxZDcwODMxZCI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+OWF2ektOOWhpazE4ZkFRdnZNZzJBZFoyYm9VPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5VMkFJOFNzMng5TDAwV3RkaVFlbldCSVFCcGJLNnU4ZU9XVGE2WExKR0lWMWg4d3VOdURqM2luVGpMVUhITkNUSHhTdTFXSE9OSW1CL3QwWlE1Z3EvYXlkcVVqaEVkNG50LysyaXBKelZjZHVHUG5nYjlMWjJ5R1Rsd1JiQkcyMzd4eCtlRWhUMUcrTUFGa3BtbnUrei9RN09vUjdQWHVZOWt6NTRCb0tVM1htK1UyWm9GVy9pVjhId01kYTJMajVLT0pjcnppSWVtNHF0dHlIZXBqcjI3NUhPM2hybzgvVW0xMm8wdk10OUhwaHJua0RNVzgzM3Q5c0k2aW5GRndiOUJkdm5ORkVxYkhCZ2RsemR5T0NqaWdreVNlTzZQNzhQQlhUTWlhM3RVaGxEL0dlZ2hmbTJ4NVI1Q2QrOXJ5RktjYlBKLzlUaFhwbTlIYUJ4R1RZNEE9PTwvZHM6U2lnbmF0dXJlVmFsdWU+PGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJREJ6Q0NBZStnQXdJQkFnSUpBUHIvTXJsYzhFR2hNQTBHQ1NxR1NJYjNEUUVCQlFVQU1Cb3hHREFXQmdOVkJBTU1EM2QzZHk1bGVHRnRjR3hsTG1OdmJUQWVGdzB4TlRFeU1qZ3hPVEU1TkRWYUZ3MHlOVEV5TWpVeE9URTVORFZhTUJveEdEQVdCZ05WQkFNTUQzZDNkeTVsZUdGdGNHeGxMbU52YlRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTkRvV3pMb3M0TFd4VG44R3l1MmxFYmw0V2NlbFViZ0xONXpZbTRyb244QWhzK3J2Y3N1MnprZEQvczZqZEdKSThXcUpLaFlLMnU2MXlnblhnQVpxQzZnZ3RGUG5CcGl6Y0R6amdORDJnK2F1Y1NvVU9ESHQ2N2YwZlF1QW11cE4venA1TVp5c0o2SUhMSm5ZTE5wZkpZazk2bFJ6OU9Ebk8xTXBxdHI5UFd4bStwejduenE1RjB2UmVwa2dwY1J4djZ1ZlFCamxyRnl0Y2N5RVZkWHJ2RnRralhjbmhWVk5TUjRrSHVPT01TNkQ3cGViU0oxbXJDbXNoYkQ1U1gxalhQQktGUEFqb3pZWDZQeHFMeFV4MVk0ZmFGRWY0TUJCVmNJbnlCNG9VUk5CMnM1OWhFRWkyanE5aXpORTdFYkVLNkJZNXNFaG9DUGw5bTMyekU2bGprQ0F3RUFBYU5RTUU0d0hRWURWUjBPQkJZRUZCOVprbEMxT3JrMnpsNTZ6ZzA4ZWk3c3MvK2lNQjhHQTFVZEl3UVlNQmFBRkI5WmtsQzFPcmsyemw1NnpnMDhlaTdzcy8raU1Bd0dBMVVkRXdRRk1BTUJBZjh3RFFZSktvWklodmNOQVFFRkJRQURnZ0VCQUFWb1RTUTVwQWlydzhPUjlGWjFiUlN1VERoWTl1eHpsL09MN2xVbXN2MmNNTmVDQjNCUlpxbTNtRnQrY3dOOEdzSDZmM3V2Tk9OSWhnRnBUR041TEVjWFF6ODl6SkV6QitxYUhxbWJGcEhRbC9zeDJCOGV6TmdULzg4MkgySUgwMGRYRVNFZnkvKzFnSGcycHhqR25oUkJONmVsL2dTYURpeVNJTUtiaWxEcmZmdXZ4aUNmYnBQTjBOUlJpUEpoZDJheTlLdUwvUnhRUmwxZ2w5Y0hhV2lvdVdXYmExYlNCYjJaUGh2MnJQTVVzRm85OG50a0dDT2JEWDZZMVNwa3Ftb1RicnNiR0ZzVEcyREx4bnZyNEdkTjFCU3IwVXUvS1YzYWRqNDdXa1hWUGVNWVF0aS9iUW14UUI4dFJGaHJ3ODBxYWtUTFV6cmVPOTZXemxCQk10WT08L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT48c2FtbHA6U3RhdHVzPjxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWxwOlN0YXR1cz48c2FtbDpBc3NlcnRpb24geG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9ImlkLTQ1ZTcyMTkyNjU4NmNkZGUzNDQ2NmI0MzQ5NDg4YjA5MDZhNTU3OGIiIElzc3VlSW5zdGFudD0iMjAyMy0wOS0yMVQxMzo0OToyMy45NDFaIiBWZXJzaW9uPSIyLjAiPjxzYW1sOklzc3VlciBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OmVudGl0eSI+aHR0cDovL2xvY2FsaG9zdDo4MDAwL21ldGFkYXRhPC9zYW1sOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz48ZHM6UmVmZXJlbmNlIFVSST0iI2lkLTQ1ZTcyMTkyNjU4NmNkZGUzNDQ2NmI0MzQ5NDg4YjA5MDZhNTU3OGIiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPmVHeTMzcWpjZjYrT3R4YlVpVm5DUjFNdnlaaz08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+cDBEekU3Q0lTVTVNQjNvclpReGtUWHFsQXJRMjlFb0tXOUZuVWx5aUhYWGtmK2lXNGZ5SW9wYjcrTUk1VUk5TjVTQVdiVzV4U0R3NFJMMWNDaVNxdWJyU29pRi83MXdiM29naWlQOHhyYmJiajY3RHZMcUU3OVJDQXdySDlEU0FlZFlReVBOQ0tFQ0U0L3NvdnFEeUg5OTAwZEI4aTQ3VHdQRkhIclBlWE8wUGVoR0NWNTVEeTZJdC92Ull1VFRqU0tiVTczV014c1A5Mk9yVEdqamN1Smx1bFdSclBxNDk0aEpNMFJxV3gwN2RoWHJEbzlJY1JpK2RCOVhRN3UwbGJicEthQ0p6R2RiUG9JanJ1ZDhaWnh2djdiaDFtV1lEeGFodExvd2I1eGtDWlQrNFMybklrSWgwNEoxUURsZm1WSVdlRWljVW5odXpIcVBkU1RldklnPT08L2RzOlNpZ25hdHVyZVZhbHVlPjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSURCekNDQWUrZ0F3SUJBZ0lKQVByL01ybGM4RUdoTUEwR0NTcUdTSWIzRFFFQkJRVUFNQm94R0RBV0JnTlZCQU1NRDNkM2R5NWxlR0Z0Y0d4bExtTnZiVEFlRncweE5URXlNamd4T1RFNU5EVmFGdzB5TlRFeU1qVXhPVEU1TkRWYU1Cb3hHREFXQmdOVkJBTU1EM2QzZHk1bGVHRnRjR3hsTG1OdmJUQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU5Eb1d6TG9zNExXeFRuOEd5dTJsRWJsNFdjZWxVYmdMTjV6WW00cm9uOEFocytydmNzdTJ6a2REL3M2amRHSkk4V3FKS2hZSzJ1NjF5Z25YZ0FacUM2Z2d0RlBuQnBpemNEempnTkQyZythdWNTb1VPREh0NjdmMGZRdUFtdXBOL3pwNU1aeXNKNklITEpuWUxOcGZKWWs5NmxSejlPRG5PMU1wcXRyOVBXeG0rcHo3bnpxNUYwdlJlcGtncGNSeHY2dWZRQmpsckZ5dGNjeUVWZFhydkZ0a2pYY25oVlZOU1I0a0h1T09NUzZEN3BlYlNKMW1yQ21zaGJENVNYMWpYUEJLRlBBam96WVg2UHhxTHhVeDFZNGZhRkVmNE1CQlZjSW55QjRvVVJOQjJzNTloRUVpMmpxOWl6TkU3RWJFSzZCWTVzRWhvQ1BsOW0zMnpFNmxqa0NBd0VBQWFOUU1FNHdIUVlEVlIwT0JCWUVGQjlaa2xDMU9yazJ6bDU2emcwOGVpN3NzLytpTUI4R0ExVWRJd1FZTUJhQUZCOVprbEMxT3JrMnpsNTZ6ZzA4ZWk3c3MvK2lNQXdHQTFVZEV3UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUZCUUFEZ2dFQkFBVm9UU1E1cEFpcnc4T1I5RloxYlJTdVREaFk5dXh6bC9PTDdsVW1zdjJjTU5lQ0IzQlJacW0zbUZ0K2N3TjhHc0g2ZjN1dk5PTkloZ0ZwVEdONUxFY1hRejg5ekpFekIrcWFIcW1iRnBIUWwvc3gyQjhlek5nVC84ODJIMklIMDBkWEVTRWZ5LysxZ0hnMnB4akduaFJCTjZlbC9nU2FEaXlTSU1LYmlsRHJmZnV2eGlDZmJwUE4wTlJSaVBKaGQyYXk5S3VML1J4UVJsMWdsOWNIYVdpb3VXV2JhMWJTQmIyWlBodjJyUE1Vc0ZvOThudGtHQ09iRFg2WTFTcGtxbW9UYnJzYkdGc1RHMkRMeG52cjRHZE4xQlNyMFV1L0tWM2FkajQ3V2tYVlBlTVlRdGkvYlFteFFCOHRSRmhydzgwcWFrVExVenJlTzk2V3psQkJNdFk9PC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWw6U3ViamVjdD48c2FtbDpOYW1lSUQgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6bmFtZWlkLWZvcm1hdDp0cmFuc2llbnQiIE5hbWVRdWFsaWZpZXI9Imh0dHA6Ly9sb2NhbGhvc3Q6ODAwMC9tZXRhZGF0YSIgU1BOYW1lUXVhbGlmaWVyPSJodHRwOi8vbG9jYWxob3N0OjgwODAvaWRwcy8yMjg5Njg3OTIzNzIyODE3MDgvc2FtbC9tZXRhZGF0YSI+YWxpY2VAZXhhbXBsZS5jb208L3NhbWw6TmFtZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBBZGRyZXNzPSJbOjoxXTo1OTMzNCIgSW5SZXNwb25zZVRvPSJpZC1iMjJjOTBkYjg4YmYwMWQ4MmZmYjBhN2I2ZmUyNWFjOWZjYjJjNjc5IiBOb3RPbk9yQWZ0ZXI9IjIwMjMtMDktMjFUMTM6NTA6NTMuOTM4WiIgUmVjaXBpZW50PSJodHRwOi8vbG9jYWxob3N0OjgwODAvaWRwcy8yMjg5Njg3OTIzNzIyODE3MDgvc2FtbC9hY3MiLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWw6U3ViamVjdD48c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAyMy0wOS0yMVQxMzo0OToxNC4yOThaIiBOb3RPbk9yQWZ0ZXI9IjIwMjMtMDktMjFUMTM6NTA6NDQuMjk4WiI+PHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48c2FtbDpBdWRpZW5jZT5odHRwOi8vbG9jYWxob3N0OjgwODAvaWRwcy8yMjg5Njg3OTIzNzIyODE3MDgvc2FtbC9tZXRhZGF0YTwvc2FtbDpBdWRpZW5jZT48L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48L3NhbWw6Q29uZGl0aW9ucz48c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMjMtMDktMjFUMTM6NDc6MzUuMTAzWiIgU2Vzc2lvbkluZGV4PSI0YzM5YjE5NTQyYzdjZTFjMzllOWMwNWJlMTdhNzJhNmQ4OGU1NWE3ZGFiYWRhZWQ3ODYxMDBiOWUzODBmYTA4Ij48c2FtbDpTdWJqZWN0TG9jYWxpdHkgQWRkcmVzcz0iWzo6MV06NTkzMzQiLz48c2FtbDpBdXRobkNvbnRleHQ+PHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmRQcm90ZWN0ZWRUcmFuc3BvcnQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+PC9zYW1sOkF1dGhuQ29udGV4dD48L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+PHNhbWw6QXR0cmlidXRlU3RhdGVtZW50PjxzYW1sOkF0dHJpYnV0ZSBGcmllbmRseU5hbWU9InVpZCIgTmFtZT0idXJuOm9pZDowLjkuMjM0Mi4xOTIwMDMwMC4xMDAuMS4xIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OnVyaSI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+YWxpY2U8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgRnJpZW5kbHlOYW1lPSJlZHVQZXJzb25QcmluY2lwYWxOYW1lIiBOYW1lPSJ1cm46b2lkOjEuMy42LjEuNC4xLjU5MjMuMS4xLjEuNiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDp1cmkiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPmFsaWNlQGV4YW1wbGUuY29tPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIEZyaWVuZGx5TmFtZT0ic24iIE5hbWU9InVybjpvaWQ6Mi41LjQuNCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDp1cmkiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPlNtaXRoPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIEZyaWVuZGx5TmFtZT0iZ2l2ZW5OYW1lIiBOYW1lPSJ1cm46b2lkOjIuNS40LjQyIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OnVyaSI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+QWxpY2U8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgRnJpZW5kbHlOYW1lPSJjbiIgTmFtZT0idXJuOm9pZDoyLjUuNC4zIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OnVyaSI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+QWxpY2UgU21pdGg8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgRnJpZW5kbHlOYW1lPSJlZHVQZXJzb25BZmZpbGlhdGlvbiIgTmFtZT0idXJuOm9pZDoxLjMuNi4xLjQuMS41OTIzLjEuMS4xLjEiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6dXJpIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6c3RyaW5nIj5BZG1pbmlzdHJhdG9yczwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6c3RyaW5nIj5Vc2Vyczwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg==", + ), + requestID: "id-b22c90db88bf01d82ffb0a7b6fe25ac9fcb2c679", + }, + want: want{ + id: "alice@example.com", + attributes: map[string][]string{ + "urn:oid:0.9.2342.19200300.100.1.1": {"alice"}, + "urn:oid:1.3.6.1.4.1.5923.1.1.1.6": {"alice@example.com"}, + "urn:oid:2.5.4.4": {"Smith"}, + "urn:oid:2.5.4.42": {"Alice"}, + "urn:oid:2.5.4.3": {"Alice Smith"}, + "urn:oid:1.3.6.1.4.1.5923.1.1.1.1": {"Administrators", "Users"}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := assert.New(t) + + provider, err := New(tt.fields.name, tt.fields.rootURL, tt.fields.metadata, tt.fields.certificate, tt.fields.key, tt.fields.options...) + require.NoError(t, err) + + sp, err := provider.GetSP() + require.NoError(t, err) + + session := &Session{ + ServiceProvider: sp, + state: tt.args.intentID, + RequestID: tt.args.requestID, + Request: tt.args.request, + } + // set to time of response for validation + saml.TimeNow = func() time.Time { + time, _ := time.Parse(time.RFC3339, "2023-09-21T13:47:40.0Z") + return time + } + user, err := session.FetchUser(context.Background()) + if tt.want.err != nil && !errors.Is(err, tt.want.err) { + a.Fail("invalid error", "expected %v, got %v", tt.want.err, err) + } + if tt.want.err == nil { + a.NoError(err) + a.Equal(tt.want.id, user.GetID()) + } + }) + } +} + +func httpPostFormRequest(t *testing.T, callbackURL, relayState, response string) *http.Request { + body := url.Values{ + "SAMLResponse": {response}, + "RelayState": {relayState}, + } + + req, err := http.NewRequest("POST", callbackURL, strings.NewReader(body.Encode())) + assert.NoError(t, err) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + assert.NoError(t, req.ParseForm()) + return req +} diff --git a/internal/idp/session.go b/internal/idp/session.go index 75f2e7a1e6..6d6519a54c 100644 --- a/internal/idp/session.go +++ b/internal/idp/session.go @@ -6,7 +6,7 @@ import ( // Session is the minimal implementation for a session of a 3rd party authentication [Provider] type Session interface { - GetAuthURL() string + GetAuth(ctx context.Context) (content string, redirect bool) FetchUser(ctx context.Context) (User, error) } @@ -18,3 +18,11 @@ type Session interface { type SessionSupportsMigration interface { RetrievePreviousID() (previousID string, err error) } + +func Redirect(redirectURL string) (string, bool) { + return redirectURL, true +} + +func Form(html string) (string, bool) { + return html, false +} diff --git a/internal/integration/client.go b/internal/integration/client.go index 4e82ea5442..71bc9805c4 100644 --- a/internal/integration/client.go +++ b/internal/integration/client.go @@ -6,6 +6,7 @@ import ( "testing" "time" + crewjam_saml "github.com/crewjam/saml" "github.com/stretchr/testify/require" "github.com/zitadel/logging" "github.com/zitadel/oidc/v2/pkg/oidc" @@ -17,6 +18,7 @@ import ( "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/idp/providers/ldap" openid "github.com/zitadel/zitadel/internal/idp/providers/oidc" + "github.com/zitadel/zitadel/internal/idp/providers/saml" "github.com/zitadel/zitadel/internal/repository/idp" "github.com/zitadel/zitadel/pkg/grpc/admin" "github.com/zitadel/zitadel/pkg/grpc/auth" @@ -196,6 +198,54 @@ func (s *Tester) AddGenericOAuthProvider(t *testing.T) string { return id } +func (s *Tester) AddSAMLProvider(t *testing.T) string { + ctx := authz.WithInstance(context.Background(), s.Instance) + id, _, err := s.Server.Commands.AddInstanceSAMLProvider(ctx, command.SAMLProvider{ + Name: "saml-idp", + Metadata: []byte("\n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n \n urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n \n \n \n"), + IDPOptions: idp.Options{ + IsLinkingAllowed: true, + IsCreationAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + }, + }) + require.NoError(t, err) + return id +} + +func (s *Tester) AddSAMLRedirectProvider(t *testing.T) string { + ctx := authz.WithInstance(context.Background(), s.Instance) + id, _, err := s.Server.Commands.AddInstanceSAMLProvider(ctx, command.SAMLProvider{ + Name: "saml-idp-redirect", + Metadata: []byte("\n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n \n urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n \n \n"), + IDPOptions: idp.Options{ + IsLinkingAllowed: true, + IsCreationAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + }, + }) + require.NoError(t, err) + return id +} + +func (s *Tester) AddSAMLPostProvider(t *testing.T) string { + ctx := authz.WithInstance(context.Background(), s.Instance) + id, _, err := s.Server.Commands.AddInstanceSAMLProvider(ctx, command.SAMLProvider{ + Name: "saml-idp-post", + Metadata: []byte("\n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=\n \n \n \n \n \n \n \n urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n \n \n"), + IDPOptions: idp.Options{ + IsLinkingAllowed: true, + IsCreationAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + }, + }) + require.NoError(t, err) + return id +} + func (s *Tester) CreateIntent(t *testing.T, idpID string) string { ctx := authz.WithInstance(context.Background(), s.Instance) writeModel, _, err := s.Commands.CreateIntent(ctx, idpID, "https://example.com/success", "https://example.com/failure", s.Organisation.ID) @@ -257,6 +307,23 @@ func (s *Tester) CreateSuccessfulLDAPIntent(t *testing.T, idpID, userID, idpUser return intentID, token, writeModel.ChangeDate, writeModel.ProcessedSequence } +func (s *Tester) CreateSuccessfulSAMLIntent(t *testing.T, idpID, userID, idpUserID string) (string, string, time.Time, uint64) { + ctx := authz.WithInstance(context.Background(), s.Instance) + intentID := s.CreateIntent(t, idpID) + writeModel, err := s.Server.Commands.GetIntentWriteModel(ctx, intentID, s.Organisation.ID) + require.NoError(t, err) + + idpUser := &saml.UserMapper{ + ID: idpUserID, + Attributes: map[string][]string{"attribute1": {"value1"}}, + } + assertion := &crewjam_saml.Assertion{ID: "id"} + + token, err := s.Server.Commands.SucceedSAMLIDPIntent(ctx, writeModel, idpUser, userID, assertion) + require.NoError(t, err) + return intentID, token, writeModel.ChangeDate, writeModel.ProcessedSequence +} + func (s *Tester) CreateVerfiedWebAuthNSession(t *testing.T, ctx context.Context, userID string) (id, token string, start, change time.Time) { createResp, err := s.Client.SessionV2.CreateSession(ctx, &session.CreateSessionRequest{ Checks: &session.Checks{ diff --git a/internal/integration/oidc.go b/internal/integration/oidc.go index e34850d5e8..ad1a44de32 100644 --- a/internal/integration/oidc.go +++ b/internal/integration/oidc.go @@ -93,7 +93,12 @@ func (s *Tester) CreateOIDCAuthRequest(clientID, loginClient, redirectURI string codeChallenge := oidc.NewSHACodeChallenge(codeVerifier) authURL := rp.AuthURL("state", provider, rp.WithCodeChallenge(codeChallenge)) - loc, err := CheckRedirect(authURL, map[string]string{oidc_internal.LoginClientHeader: loginClient}) + req, err := GetRequest(authURL, map[string]string{oidc_internal.LoginClientHeader: loginClient}) + if err != nil { + return "", err + } + + loc, err := CheckRedirect(req) if err != nil { return "", err } @@ -120,7 +125,12 @@ func (s *Tester) CreateOIDCAuthRequestImplicit(clientID, loginClient, redirectUR parsed.RawQuery = queries.Encode() authURL = parsed.String() - loc, err := CheckRedirect(authURL, map[string]string{oidc_internal.LoginClientHeader: loginClient}) + req, err := GetRequest(authURL, map[string]string{oidc_internal.LoginClientHeader: loginClient}) + if err != nil { + return "", err + } + + loc, err := CheckRedirect(req) if err != nil { return "", err } @@ -161,7 +171,7 @@ func (s *Tester) CreateResourceServer(keyFileData []byte) (rs.ResourceServer, er return rs.NewResourceServerJWTProfile(s.OIDCIssuer(), keyFile.ClientID, keyFile.KeyID, []byte(keyFile.Key)) } -func CheckRedirect(url string, headers map[string]string) (*url.URL, error) { +func GetRequest(url string, headers map[string]string) (*http.Request, error) { req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err @@ -169,7 +179,10 @@ func CheckRedirect(url string, headers map[string]string) (*url.URL, error) { for key, value := range headers { req.Header.Set(key, value) } + return req, nil +} +func CheckRedirect(req *http.Request) (*url.URL, error) { client := &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse diff --git a/internal/notification/messages/email.go b/internal/notification/messages/email.go index 38ceaaa6fa..0a71b691df 100644 --- a/internal/notification/messages/email.go +++ b/internal/notification/messages/email.go @@ -54,7 +54,7 @@ func (msg *Email) GetContent() (string, error) { if !isHTML(msg.Content) { mime = "MIME-version: 1.0;" + lineBreak + "Content-Type: text/plain; charset=\"UTF-8\";" + lineBreak + lineBreak } - subject := "Subject: " + msg.Subject + lineBreak + subject := "Subject: " + qEncodeSubject(msg.Subject) + lineBreak message += subject + mime + lineBreak + msg.Content return message, nil @@ -67,3 +67,8 @@ func (msg *Email) GetTriggeringEvent() eventstore.Event { func isHTML(input string) bool { return isHTMLRgx.MatchString(input) } + +// returns a RFC1342 "Q" encoded string to allow non-ascii characters +func qEncodeSubject(subject string) string { + return "=?utf-8?q?" + subject + "?=" +} diff --git a/internal/query/idp_template.go b/internal/query/idp_template.go index 148fad49f3..b12824dbb8 100644 --- a/internal/query/idp_template.go +++ b/internal/query/idp_template.go @@ -44,6 +44,7 @@ type IDPTemplate struct { *GoogleIDPTemplate *LDAPIDPTemplate *AppleIDPTemplate + *SAMLIDPTemplate } type IDPTemplates struct { @@ -150,6 +151,15 @@ type AppleIDPTemplate struct { Scopes database.StringArray } +type SAMLIDPTemplate struct { + IDPID string + Metadata []byte + Key *crypto.CryptoValue + Certificate []byte + Binding string + WithSignedRequest bool +} + var ( idpTemplateTable = table{ name: projection.IDPTemplateTable, @@ -650,6 +660,41 @@ var ( } ) +var ( + samlIdpTemplateTable = table{ + name: projection.IDPTemplateSAMLTable, + instanceIDCol: projection.IDPTemplateInstanceIDCol, + } + SAMLIDCol = Column{ + name: projection.SAMLIDCol, + table: samlIdpTemplateTable, + } + SAMLInstanceCol = Column{ + name: projection.SAMLInstanceIDCol, + table: samlIdpTemplateTable, + } + SAMLMetadataCol = Column{ + name: projection.SAMLMetadataCol, + table: samlIdpTemplateTable, + } + SAMLKeyCol = Column{ + name: projection.SAMLKeyCol, + table: samlIdpTemplateTable, + } + SAMLCertificateCol = Column{ + name: projection.SAMLCertificateCol, + table: samlIdpTemplateTable, + } + SAMLBindingCol = Column{ + name: projection.SAMLBindingCol, + table: samlIdpTemplateTable, + } + SAMLWithSignedRequestCol = Column{ + name: projection.SAMLWithSignedRequestCol, + table: samlIdpTemplateTable, + } +) + // IDPTemplateByID searches for the requested id func (q *Queries) IDPTemplateByID(ctx context.Context, shouldTriggerBulk bool, id string, withOwnerRemoved bool, queries ...SearchQuery) (template *IDPTemplate, err error) { ctx, span := tracing.NewSpan(ctx) @@ -820,6 +865,13 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se GoogleClientIDCol.identifier(), GoogleClientSecretCol.identifier(), GoogleScopesCol.identifier(), + // saml + SAMLIDCol.identifier(), + SAMLMetadataCol.identifier(), + SAMLKeyCol.identifier(), + SAMLCertificateCol.identifier(), + SAMLBindingCol.identifier(), + SAMLWithSignedRequestCol.identifier(), // ldap LDAPIDCol.identifier(), LDAPServersCol.identifier(), @@ -861,6 +913,7 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se LeftJoin(join(GitLabIDCol, IDPTemplateIDCol)). LeftJoin(join(GitLabSelfHostedIDCol, IDPTemplateIDCol)). LeftJoin(join(GoogleIDCol, IDPTemplateIDCol)). + LeftJoin(join(SAMLIDCol, IDPTemplateIDCol)). LeftJoin(join(LDAPIDCol, IDPTemplateIDCol)). LeftJoin(join(AppleIDCol, IDPTemplateIDCol) + db.Timetravel(call.Took(ctx))). PlaceholderFormat(sq.Dollar), @@ -927,6 +980,13 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se googleClientSecret := new(crypto.CryptoValue) googleScopes := database.StringArray{} + samlID := sql.NullString{} + var samlMetadata []byte + samlKey := new(crypto.CryptoValue) + var samlCertificate []byte + samlBinding := sql.NullString{} + samlWithSignedRequest := sql.NullBool{} + ldapID := sql.NullString{} ldapServers := database.StringArray{} ldapStartTls := sql.NullBool{} @@ -1030,6 +1090,13 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se &googleClientID, &googleClientSecret, &googleScopes, + // saml + &samlID, + &samlMetadata, + &samlKey, + &samlCertificate, + &samlBinding, + &samlWithSignedRequest, // ldap &ldapID, &ldapServers, @@ -1156,6 +1223,16 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se Scopes: googleScopes, } } + if samlID.Valid { + idpTemplate.SAMLIDPTemplate = &SAMLIDPTemplate{ + IDPID: samlID.String, + Metadata: samlMetadata, + Key: samlKey, + Certificate: samlCertificate, + Binding: samlBinding.String, + WithSignedRequest: samlWithSignedRequest.Bool, + } + } if ldapID.Valid { idpTemplate.LDAPIDPTemplate = &LDAPIDPTemplate{ IDPID: ldapID.String, @@ -1273,6 +1350,13 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec GoogleClientIDCol.identifier(), GoogleClientSecretCol.identifier(), GoogleScopesCol.identifier(), + // saml + SAMLIDCol.identifier(), + SAMLMetadataCol.identifier(), + SAMLKeyCol.identifier(), + SAMLCertificateCol.identifier(), + SAMLBindingCol.identifier(), + SAMLWithSignedRequestCol.identifier(), // ldap LDAPIDCol.identifier(), LDAPServersCol.identifier(), @@ -1316,6 +1400,7 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec LeftJoin(join(GitLabIDCol, IDPTemplateIDCol)). LeftJoin(join(GitLabSelfHostedIDCol, IDPTemplateIDCol)). LeftJoin(join(GoogleIDCol, IDPTemplateIDCol)). + LeftJoin(join(SAMLIDCol, IDPTemplateIDCol)). LeftJoin(join(LDAPIDCol, IDPTemplateIDCol)). LeftJoin(join(AppleIDCol, IDPTemplateIDCol) + db.Timetravel(call.Took(ctx))). PlaceholderFormat(sq.Dollar), @@ -1385,6 +1470,13 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec googleClientSecret := new(crypto.CryptoValue) googleScopes := database.StringArray{} + samlID := sql.NullString{} + var samlMetadata []byte + samlKey := new(crypto.CryptoValue) + var samlCertificate []byte + samlBinding := sql.NullString{} + samlWithSignedRequest := sql.NullBool{} + ldapID := sql.NullString{} ldapServers := database.StringArray{} ldapStartTls := sql.NullBool{} @@ -1488,6 +1580,13 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec &googleClientID, &googleClientSecret, &googleScopes, + // saml + &samlID, + &samlMetadata, + &samlKey, + &samlCertificate, + &samlBinding, + &samlWithSignedRequest, // ldap &ldapID, &ldapServers, @@ -1613,6 +1712,16 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec Scopes: googleScopes, } } + if samlID.Valid { + idpTemplate.SAMLIDPTemplate = &SAMLIDPTemplate{ + IDPID: samlID.String, + Metadata: samlMetadata, + Key: samlKey, + Certificate: samlCertificate, + Binding: samlBinding.String, + WithSignedRequest: samlWithSignedRequest.Bool, + } + } if ldapID.Valid { idpTemplate.LDAPIDPTemplate = &LDAPIDPTemplate{ IDPID: ldapID.String, diff --git a/internal/query/idp_template_test.go b/internal/query/idp_template_test.go index e5c27565d4..b6eb54aad7 100644 --- a/internal/query/idp_template_test.go +++ b/internal/query/idp_template_test.go @@ -87,6 +87,13 @@ var ( ` projections.idp_templates5_google.client_id,` + ` projections.idp_templates5_google.client_secret,` + ` projections.idp_templates5_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,` + // ldap ` projections.idp_templates5_ldap2.idp_id,` + ` projections.idp_templates5_ldap2.servers,` + @@ -128,6 +135,7 @@ var ( ` 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` + ` AS OF SYSTEM TIME '-1 ms'` @@ -203,6 +211,13 @@ var ( "client_id", "client_secret", "scopes", + // saml config + "idp_id", + "metadata", + "key", + "certificate", + "binding", + "with_signed_request", // ldap config "idp_id", "servers", @@ -306,6 +321,13 @@ var ( ` projections.idp_templates5_google.client_id,` + ` projections.idp_templates5_google.client_secret,` + ` projections.idp_templates5_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,` + // ldap ` projections.idp_templates5_ldap2.idp_id,` + ` projections.idp_templates5_ldap2.servers,` + @@ -348,6 +370,7 @@ var ( ` 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` + ` AS OF SYSTEM TIME '-1 ms'` @@ -423,6 +446,13 @@ var ( "client_id", "client_secret", "scopes", + // saml config + "idp_id", + "metadata", + "key", + "certificate", + "binding", + "with_signed_request", // ldap config "idp_id", "servers", @@ -566,6 +596,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { nil, nil, nil, + // saml + nil, + nil, + nil, + nil, + nil, + nil, // ldap config nil, nil, @@ -705,6 +742,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { nil, nil, nil, + // saml + nil, + nil, + nil, + nil, + nil, + nil, // ldap config nil, nil, @@ -842,6 +886,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { nil, nil, nil, + // saml + nil, + nil, + nil, + nil, + nil, + nil, // ldap config nil, nil, @@ -978,6 +1029,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { nil, nil, nil, + // saml + nil, + nil, + nil, + nil, + nil, + nil, // ldap config nil, nil, @@ -1113,6 +1171,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { nil, nil, nil, + // saml + nil, + nil, + nil, + nil, + nil, + nil, // ldap config nil, nil, @@ -1248,6 +1313,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { nil, nil, nil, + // saml + nil, + nil, + nil, + nil, + nil, + nil, // ldap config nil, nil, @@ -1384,6 +1456,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { "client_id", nil, database.StringArray{"profile"}, + // saml + nil, + nil, + nil, + nil, + nil, + nil, // ldap config nil, nil, @@ -1440,6 +1519,150 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { }, }, }, + { + name: "prepareIDPTemplateByIDQuery saml idp", + prepare: prepareIDPTemplateByIDQuery, + want: want{ + sqlExpectations: mockQuery( + regexp.QuoteMeta(idpTemplateQuery), + idpTemplateCols, + []driver.Value{ + "idp-id", + "ro", + testNow, + testNow, + uint64(20211109), + domain.IDPConfigStateActive, + "idp-name", + domain.IDPTypeSAML, + domain.IdentityProviderTypeOrg, + true, + true, + true, + true, + // oauth + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + // oidc + nil, + nil, + nil, + nil, + nil, + nil, + // jwt + nil, + nil, + nil, + nil, + nil, + // azure + nil, + nil, + nil, + nil, + nil, + nil, + // github + nil, + nil, + nil, + nil, + // github enterprise + nil, + nil, + nil, + nil, + nil, + nil, + nil, + // gitlab + nil, + nil, + nil, + nil, + // gitlab self hosted + nil, + nil, + nil, + nil, + nil, + // google + nil, + nil, + nil, + nil, + // saml + "idp-id", + []byte("metadata"), + nil, + nil, + "binding", + false, + // ldap config + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + // apple + nil, + nil, + nil, + nil, + nil, + nil, + }, + ), + }, + object: &IDPTemplate{ + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211109, + ResourceOwner: "ro", + ID: "idp-id", + State: domain.IDPStateActive, + Name: "idp-name", + Type: domain.IDPTypeSAML, + OwnerType: domain.IdentityProviderTypeOrg, + IsCreationAllowed: true, + IsLinkingAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + SAMLIDPTemplate: &SAMLIDPTemplate{ + IDPID: "idp-id", + Metadata: []byte("metadata"), + Key: nil, + Certificate: nil, + Binding: "binding", + WithSignedRequest: false, + }, + }, + }, { name: "prepareIDPTemplateByIDQuery ldap idp", prepare: prepareIDPTemplateByIDQuery, @@ -1519,6 +1742,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { nil, nil, nil, + // saml + nil, + nil, + nil, + nil, + nil, + nil, // ldap config "idp-id", database.StringArray{"server"}, @@ -1674,6 +1904,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { nil, nil, nil, + // saml + nil, + nil, + nil, + nil, + nil, + nil, // ldap config nil, nil, @@ -1811,6 +2048,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { nil, nil, nil, + // saml + nil, + nil, + nil, + nil, + nil, + nil, // ldap config nil, nil, @@ -1976,6 +2220,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { nil, nil, nil, + // saml + nil, + nil, + nil, + nil, + nil, + nil, // ldap config "idp-id", database.StringArray{"server"}, @@ -2140,6 +2391,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { nil, nil, nil, + // saml + nil, + nil, + nil, + nil, + nil, + nil, // ldap config nil, nil, @@ -2278,6 +2536,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { nil, nil, nil, + // saml + nil, + nil, + nil, + nil, + nil, + nil, // ldap config "idp-id-ldap", database.StringArray{"server"}, @@ -2310,6 +2575,117 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { nil, nil, }, + { + "idp-id-saml", + "ro", + testNow, + testNow, + uint64(20211109), + domain.IDPConfigStateActive, + "idp-name", + domain.IDPTypeSAML, + domain.IdentityProviderTypeOrg, + true, + true, + true, + true, + // oauth + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + // oidc + nil, + nil, + nil, + nil, + nil, + nil, + // jwt + nil, + nil, + nil, + nil, + nil, + // azure + nil, + nil, + nil, + nil, + nil, + nil, + // github + nil, + nil, + nil, + nil, + // github enterprise + nil, + nil, + nil, + nil, + nil, + nil, + nil, + // gitlab + nil, + nil, + nil, + nil, + // gitlab self hosted + nil, + nil, + nil, + nil, + nil, + // google + nil, + nil, + nil, + nil, + // saml + "idp-id-saml", + []byte("metadata"), + nil, + nil, + "binding", + false, + // ldap config + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + // apple + nil, + nil, + nil, + nil, + nil, + nil, + }, { "idp-id-google", "ro", @@ -2382,6 +2758,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { "client_id", nil, database.StringArray{"profile"}, + // saml + nil, + nil, + nil, + nil, + nil, + nil, // ldap config nil, nil, @@ -2486,6 +2869,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { nil, nil, nil, + // saml + nil, + nil, + nil, + nil, + nil, + nil, // ldap config nil, nil, @@ -2590,6 +2980,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { nil, nil, nil, + // saml + nil, + nil, + nil, + nil, + nil, + nil, // ldap config nil, nil, @@ -2694,6 +3091,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { nil, nil, nil, + // saml + nil, + nil, + nil, + nil, + nil, + nil, // ldap config nil, nil, @@ -2731,7 +3135,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { }, object: &IDPTemplates{ SearchResponse: SearchResponse{ - Count: 5, + Count: 6, }, Templates: []*IDPTemplate{ { @@ -2775,6 +3179,29 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) { }, }, }, + { + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211109, + ResourceOwner: "ro", + ID: "idp-id-saml", + State: domain.IDPStateActive, + Name: "idp-name", + Type: domain.IDPTypeSAML, + OwnerType: domain.IdentityProviderTypeOrg, + IsCreationAllowed: true, + IsLinkingAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + SAMLIDPTemplate: &SAMLIDPTemplate{ + IDPID: "idp-id-saml", + Metadata: []byte("metadata"), + Key: nil, + Certificate: nil, + Binding: "binding", + WithSignedRequest: false, + }, + }, { CreationDate: testNow, ChangeDate: testNow, diff --git a/internal/query/projection/idp_template.go b/internal/query/projection/idp_template.go index 9dc9ff6073..715b853c57 100644 --- a/internal/query/projection/idp_template.go +++ b/internal/query/projection/idp_template.go @@ -29,6 +29,7 @@ const ( IDPTemplateGoogleTable = IDPTemplateTable + "_" + IDPTemplateGoogleSuffix IDPTemplateLDAPTable = IDPTemplateTable + "_" + IDPTemplateLDAPSuffix IDPTemplateAppleTable = IDPTemplateTable + "_" + IDPTemplateAppleSuffix + IDPTemplateSAMLTable = IDPTemplateTable + "_" + IDPTemplateSAMLSuffix IDPTemplateOAuthSuffix = "oauth2" IDPTemplateOIDCSuffix = "oidc" @@ -41,6 +42,7 @@ const ( IDPTemplateGoogleSuffix = "google" IDPTemplateLDAPSuffix = "ldap2" IDPTemplateAppleSuffix = "apple" + IDPTemplateSAMLSuffix = "saml" IDPTemplateIDCol = "id" IDPTemplateCreationDateCol = "creation_date" @@ -157,6 +159,14 @@ const ( AppleKeyIDCol = "key_id" ApplePrivateKeyCol = "private_key" AppleScopesCol = "scopes" + + SAMLIDCol = "idp_id" + SAMLInstanceIDCol = "instance_id" + SAMLMetadataCol = "metadata" + SAMLKeyCol = "key" + SAMLCertificateCol = "certificate" + SAMLBindingCol = "binding" + SAMLWithSignedRequestCol = "with_signed_request" ) type idpTemplateProjection struct { @@ -344,6 +354,19 @@ func newIDPTemplateProjection(ctx context.Context, config crdb.StatementHandlerC IDPTemplateAppleSuffix, crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys()), ), + crdb.NewSuffixedTable([]*crdb.Column{ + crdb.NewColumn(SAMLIDCol, crdb.ColumnTypeText), + crdb.NewColumn(SAMLInstanceIDCol, crdb.ColumnTypeText), + crdb.NewColumn(SAMLMetadataCol, crdb.ColumnTypeBytes), + crdb.NewColumn(SAMLKeyCol, crdb.ColumnTypeJSONB), + crdb.NewColumn(SAMLCertificateCol, crdb.ColumnTypeBytes), + crdb.NewColumn(SAMLBindingCol, crdb.ColumnTypeText, crdb.Nullable()), + crdb.NewColumn(SAMLWithSignedRequestCol, crdb.ColumnTypeBool, crdb.Nullable()), + }, + crdb.NewPrimaryKey(SAMLInstanceIDCol, SAMLIDCol), + IDPTemplateSAMLSuffix, + crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys()), + ), ) p.StatementHandler = crdb.NewStatementHandler(ctx, config) return p @@ -474,6 +497,14 @@ func (p *idpTemplateProjection) reducers() []handler.AggregateReducer { Event: instance.AppleIDPChangedEventType, Reduce: p.reduceAppleIDPChanged, }, + { + Event: instance.SAMLIDPAddedEventType, + Reduce: p.reduceSAMLIDPAdded, + }, + { + Event: instance.SAMLIDPChangedEventType, + Reduce: p.reduceSAMLIDPChanged, + }, { Event: instance.IDPConfigRemovedEventType, Reduce: p.reduceIDPConfigRemoved, @@ -611,6 +642,14 @@ func (p *idpTemplateProjection) reducers() []handler.AggregateReducer { Event: org.AppleIDPChangedEventType, Reduce: p.reduceAppleIDPChanged, }, + { + Event: org.SAMLIDPAddedEventType, + Reduce: p.reduceSAMLIDPAdded, + }, + { + Event: org.SAMLIDPChangedEventType, + Reduce: p.reduceSAMLIDPChanged, + }, { Event: org.IDPConfigRemovedEventType, Reduce: p.reduceIDPConfigRemoved, @@ -1898,6 +1937,97 @@ func (p *idpTemplateProjection) reduceLDAPIDPChanged(event eventstore.Event) (*h ), nil } +func (p *idpTemplateProjection) reduceSAMLIDPAdded(event eventstore.Event) (*handler.Statement, error) { + var idpEvent idp.SAMLIDPAddedEvent + var idpOwnerType domain.IdentityProviderType + switch e := event.(type) { + case *org.SAMLIDPAddedEvent: + idpEvent = e.SAMLIDPAddedEvent + idpOwnerType = domain.IdentityProviderTypeOrg + case *instance.SAMLIDPAddedEvent: + idpEvent = e.SAMLIDPAddedEvent + idpOwnerType = domain.IdentityProviderTypeSystem + default: + return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-9s02m1", "reduce.wrong.event.type %v", []eventstore.EventType{org.SAMLIDPAddedEventType, instance.SAMLIDPAddedEventType}) + } + + return crdb.NewMultiStatement( + &idpEvent, + crdb.AddCreateStatement( + []handler.Column{ + handler.NewCol(IDPTemplateIDCol, idpEvent.ID), + handler.NewCol(IDPTemplateCreationDateCol, idpEvent.CreationDate()), + handler.NewCol(IDPTemplateChangeDateCol, idpEvent.CreationDate()), + handler.NewCol(IDPTemplateSequenceCol, idpEvent.Sequence()), + handler.NewCol(IDPTemplateResourceOwnerCol, idpEvent.Aggregate().ResourceOwner), + handler.NewCol(IDPTemplateInstanceIDCol, idpEvent.Aggregate().InstanceID), + handler.NewCol(IDPTemplateStateCol, domain.IDPStateActive), + handler.NewCol(IDPTemplateNameCol, idpEvent.Name), + handler.NewCol(IDPTemplateOwnerTypeCol, idpOwnerType), + handler.NewCol(IDPTemplateTypeCol, domain.IDPTypeSAML), + handler.NewCol(IDPTemplateIsCreationAllowedCol, idpEvent.IsCreationAllowed), + handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), + handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), + handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + }, + ), + crdb.AddCreateStatement( + []handler.Column{ + handler.NewCol(SAMLIDCol, idpEvent.ID), + handler.NewCol(SAMLInstanceIDCol, idpEvent.Aggregate().InstanceID), + handler.NewCol(SAMLMetadataCol, idpEvent.Metadata), + handler.NewCol(SAMLKeyCol, idpEvent.Key), + handler.NewCol(SAMLCertificateCol, idpEvent.Certificate), + handler.NewCol(SAMLBindingCol, idpEvent.Binding), + handler.NewCol(SAMLWithSignedRequestCol, idpEvent.WithSignedRequest), + }, + crdb.WithTableSuffix(IDPTemplateSAMLSuffix), + ), + ), nil +} + +func (p *idpTemplateProjection) reduceSAMLIDPChanged(event eventstore.Event) (*handler.Statement, error) { + var idpEvent idp.SAMLIDPChangedEvent + switch e := event.(type) { + case *org.SAMLIDPChangedEvent: + idpEvent = e.SAMLIDPChangedEvent + case *instance.SAMLIDPChangedEvent: + idpEvent = e.SAMLIDPChangedEvent + default: + return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-o7c0fii4ad", "reduce.wrong.event.type %v", []eventstore.EventType{org.SAMLIDPChangedEventType, instance.SAMLIDPChangedEventType}) + } + + ops := make([]func(eventstore.Event) crdb.Exec, 0, 2) + ops = append(ops, + crdb.AddUpdateStatement( + reduceIDPChangedTemplateColumns(idpEvent.Name, idpEvent.CreationDate(), idpEvent.Sequence(), idpEvent.OptionChanges), + []handler.Condition{ + handler.NewCond(IDPTemplateIDCol, idpEvent.ID), + handler.NewCond(IDPTemplateInstanceIDCol, idpEvent.Aggregate().InstanceID), + }, + ), + ) + + SAMLCols := reduceSAMLIDPChangedColumns(idpEvent) + if len(SAMLCols) > 0 { + ops = append(ops, + crdb.AddUpdateStatement( + SAMLCols, + []handler.Condition{ + handler.NewCond(SAMLIDCol, idpEvent.ID), + handler.NewCond(SAMLInstanceIDCol, idpEvent.Aggregate().InstanceID), + }, + crdb.WithTableSuffix(IDPTemplateSAMLSuffix), + ), + ) + } + + return crdb.NewMultiStatement( + &idpEvent, + ops..., + ), nil +} + func (p *idpTemplateProjection) reduceAppleIDPAdded(event eventstore.Event) (*handler.Statement, error) { var idpEvent idp.AppleIDPAddedEvent var idpOwnerType domain.IdentityProviderType @@ -2067,7 +2197,7 @@ func reduceIDPChangedTemplateColumns(name *string, creationDate time.Time, seque } func reduceOAuthIDPChangedColumns(idpEvent idp.OAuthIDPChangedEvent) []handler.Column { - oauthCols := make([]handler.Column, 0, 6) + oauthCols := make([]handler.Column, 0, 7) if idpEvent.ClientID != nil { oauthCols = append(oauthCols, handler.NewCol(OAuthClientIDCol, *idpEvent.ClientID)) } @@ -2093,7 +2223,7 @@ func reduceOAuthIDPChangedColumns(idpEvent idp.OAuthIDPChangedEvent) []handler.C } func reduceOIDCIDPChangedColumns(idpEvent idp.OIDCIDPChangedEvent) []handler.Column { - oidcCols := make([]handler.Column, 0, 4) + oidcCols := make([]handler.Column, 0, 5) if idpEvent.ClientID != nil { oidcCols = append(oidcCols, handler.NewCol(OIDCClientIDCol, *idpEvent.ClientID)) } @@ -2232,7 +2362,7 @@ func reduceGoogleIDPChangedColumns(idpEvent idp.GoogleIDPChangedEvent) []handler } func reduceLDAPIDPChangedColumns(idpEvent idp.LDAPIDPChangedEvent) []handler.Column { - ldapCols := make([]handler.Column, 0, 4) + ldapCols := make([]handler.Column, 0, 22) if idpEvent.Servers != nil { ldapCols = append(ldapCols, handler.NewCol(LDAPServersCol, database.StringArray(idpEvent.Servers))) } @@ -2321,3 +2451,23 @@ func reduceAppleIDPChangedColumns(idpEvent idp.AppleIDPChangedEvent) []handler.C } return appleCols } + +func reduceSAMLIDPChangedColumns(idpEvent idp.SAMLIDPChangedEvent) []handler.Column { + SAMLCols := make([]handler.Column, 0, 5) + if idpEvent.Metadata != nil { + SAMLCols = append(SAMLCols, handler.NewCol(SAMLMetadataCol, idpEvent.Metadata)) + } + if idpEvent.Key != nil { + SAMLCols = append(SAMLCols, handler.NewCol(SAMLKeyCol, idpEvent.Key)) + } + if idpEvent.Certificate != nil { + SAMLCols = append(SAMLCols, handler.NewCol(SAMLCertificateCol, idpEvent.Certificate)) + } + if idpEvent.Binding != nil { + SAMLCols = append(SAMLCols, handler.NewCol(SAMLBindingCol, *idpEvent.Binding)) + } + if idpEvent.WithSignedRequest != nil { + SAMLCols = append(SAMLCols, handler.NewCol(SAMLWithSignedRequestCol, *idpEvent.WithSignedRequest)) + } + return SAMLCols +} diff --git a/internal/query/projection/idp_template_test.go b/internal/query/projection/idp_template_test.go index fd4eedf880..9e99c6cdbc 100644 --- a/internal/query/projection/idp_template_test.go +++ b/internal/query/projection/idp_template_test.go @@ -1,6 +1,7 @@ package projection import ( + "encoding/json" "testing" "time" @@ -2696,6 +2697,297 @@ func TestIDPTemplateProjection_reducesApple(t *testing.T) { } } +func TestIDPTemplateProjection_reducesSAML(t *testing.T) { + type args struct { + event func(t *testing.T) eventstore.Event + } + tests := []struct { + name string + args args + reduce func(event eventstore.Event) (*handler.Statement, error) + want wantReduce + }{ + { + name: "instance reduceSAMLIDPAdded", + args: args{ + event: getEvent(testEvent( + repository.EventType(instance.SAMLIDPAddedEventType), + instance.AggregateType, + []byte(`{ + "id": "idp-id", + "name": "custom-zitadel-instance", + "metadata": `+stringToJSONByte("metadata")+`, + "key": { + "cryptoType": 0, + "algorithm": "RSA-265", + "keyId": "key-id" + }, + "certificate": `+stringToJSONByte("certificate")+`, + "binding": "binding", + "withSignedRequest": true, + "isCreationAllowed": true, + "isLinkingAllowed": true, + "isAutoCreation": true, + "isAutoUpdate": true +}`), + ), instance.SAMLIDPAddedEventMapper), + }, + reduce: (&idpTemplateProjection{}).reduceSAMLIDPAdded, + want: wantReduce{ + aggregateType: eventstore.AggregateType("instance"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: idpTemplateInsertStmt, + expectedArgs: []interface{}{ + "idp-id", + anyArg{}, + anyArg{}, + uint64(15), + "ro-id", + "instance-id", + domain.IDPStateActive, + "custom-zitadel-instance", + domain.IdentityProviderTypeSystem, + domain.IDPTypeSAML, + true, + true, + true, + true, + }, + }, + { + 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)", + expectedArgs: []interface{}{ + "idp-id", + "instance-id", + []byte("metadata"), + anyArg{}, + anyArg{}, + "binding", + true, + }, + }, + }, + }, + }, + }, + { + name: "org reduceSAMLIDPAdded", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.SAMLIDPAddedEventType), + org.AggregateType, + []byte(`{ + "id": "idp-id", + "name": "custom-zitadel-instance", + "metadata": `+stringToJSONByte("metadata")+`, + "key": { + "cryptoType": 0, + "algorithm": "RSA-265", + "keyId": "key-id" + }, + "certificate": `+stringToJSONByte("certificate")+`, + "binding": "binding", + "withSignedRequest": true, + "isCreationAllowed": true, + "isLinkingAllowed": true, + "isAutoCreation": true, + "isAutoUpdate": true +}`), + ), org.SAMLIDPAddedEventMapper), + }, + reduce: (&idpTemplateProjection{}).reduceSAMLIDPAdded, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: idpTemplateInsertStmt, + expectedArgs: []interface{}{ + "idp-id", + anyArg{}, + anyArg{}, + uint64(15), + "ro-id", + "instance-id", + domain.IDPStateActive, + "custom-zitadel-instance", + domain.IdentityProviderTypeOrg, + domain.IDPTypeSAML, + true, + true, + true, + true, + }, + }, + { + 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)", + expectedArgs: []interface{}{ + "idp-id", + "instance-id", + []byte("metadata"), + anyArg{}, + anyArg{}, + "binding", + true, + }, + }, + }, + }, + }, + }, + { + name: "instance reduceSAMLIDPChanged minimal", + args: args{ + event: getEvent(testEvent( + repository.EventType(instance.SAMLIDPChangedEventType), + instance.AggregateType, + []byte(`{ + "id": "idp-id", + "name": "custom-zitadel-instance", + "binding": "binding" +}`), + ), instance.SAMLIDPChangedEventMapper), + }, + reduce: (&idpTemplateProjection{}).reduceSAMLIDPChanged, + want: wantReduce{ + aggregateType: eventstore.AggregateType("instance"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.idp_templates5 SET (name, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedArgs: []interface{}{ + "custom-zitadel-instance", + anyArg{}, + uint64(15), + "idp-id", + "instance-id", + }, + }, + { + expectedStmt: "UPDATE projections.idp_templates5_saml SET binding = $1 WHERE (idp_id = $2) AND (instance_id = $3)", + expectedArgs: []interface{}{ + "binding", + "idp-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "instance reduceSAMLIDPChanged", + args: args{ + event: getEvent(testEvent( + repository.EventType(instance.SAMLIDPChangedEventType), + instance.AggregateType, + []byte(`{ + "id": "idp-id", + "name": "custom-zitadel-instance", + "metadata": `+stringToJSONByte("metadata")+`, + "key": { + "cryptoType": 0, + "algorithm": "RSA-265", + "keyId": "key-id" + }, + "certificate": `+stringToJSONByte("certificate")+`, + "binding": "binding", + "withSignedRequest": true, + "isCreationAllowed": true, + "isLinkingAllowed": true, + "isAutoCreation": true, + "isAutoUpdate": true +}`), + ), instance.SAMLIDPChangedEventMapper), + }, + reduce: (&idpTemplateProjection{}).reduceSAMLIDPChanged, + want: wantReduce{ + aggregateType: eventstore.AggregateType("instance"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: idpTemplateUpdateStmt, + expectedArgs: []interface{}{ + "custom-zitadel-instance", + true, + true, + true, + true, + anyArg{}, + uint64(15), + "idp-id", + "instance-id", + }, + }, + { + 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)", + expectedArgs: []interface{}{ + []byte("metadata"), + anyArg{}, + anyArg{}, + "binding", + true, + "idp-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "org.reduceOwnerRemoved", + reduce: (&idpProjection{}).reduceOwnerRemoved, + args: args{ + event: getEvent(testEvent( + repository.EventType(org.OrgRemovedEventType), + org.AggregateType, + nil, + ), org.OrgRemovedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM projections.idp_templates5 WHERE (instance_id = $1) AND (resource_owner = $2)", + expectedArgs: []interface{}{ + "instance-id", + "agg-id", + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + event := baseEvent(t) + got, err := tt.reduce(event) + if !errors.IsErrorInvalidArgument(err) { + t.Errorf("no wrong event mapping: %v, got: %v", err, got) + } + + event = tt.args.event(t) + got, err = tt.reduce(event) + assertReduce(t, got, err, IDPTemplateTable, tt.want) + }) + } +} + func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { type args struct { event func(t *testing.T) eventstore.Event @@ -4058,3 +4350,8 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) { }) } } + +func stringToJSONByte(data string) string { + jsondata, _ := json.Marshal([]byte(data)) + return string(jsondata) +} diff --git a/internal/repository/feature/aggregate.go b/internal/repository/feature/aggregate.go new file mode 100644 index 0000000000..479df776d7 --- /dev/null +++ b/internal/repository/feature/aggregate.go @@ -0,0 +1,30 @@ +package feature + +import ( + "github.com/zitadel/zitadel/internal/eventstore" +) + +const ( + eventTypePrefix = eventstore.EventType("feature.") + setSuffix = ".set" +) + +const ( + AggregateType = "feature" + AggregateVersion = "v1" +) + +type Aggregate struct { + eventstore.Aggregate +} + +func NewAggregate(id, resourceOwner string) *Aggregate { + return &Aggregate{ + Aggregate: eventstore.Aggregate{ + Type: AggregateType, + Version: AggregateVersion, + ID: id, + ResourceOwner: resourceOwner, + }, + } +} diff --git a/internal/repository/feature/eventstore.go b/internal/repository/feature/eventstore.go new file mode 100644 index 0000000000..4fe6a37271 --- /dev/null +++ b/internal/repository/feature/eventstore.go @@ -0,0 +1,9 @@ +package feature + +import ( + "github.com/zitadel/zitadel/internal/eventstore" +) + +func RegisterEventMappers(es *eventstore.Eventstore) { + es.RegisterFilterEventMapper(AggregateType, DefaultLoginInstanceEventType, eventstore.GenericEventMapper[SetEvent[Boolean]]) +} diff --git a/internal/repository/feature/feature.go b/internal/repository/feature/feature.go new file mode 100644 index 0000000000..b3757c1035 --- /dev/null +++ b/internal/repository/feature/feature.go @@ -0,0 +1,65 @@ +package feature + +import ( + "context" + "strings" + + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" +) + +var ( + DefaultLoginInstanceEventType = EventTypeFromFeature(domain.FeatureLoginDefaultOrg) +) + +func EventTypeFromFeature(feature domain.Feature) eventstore.EventType { + return eventTypePrefix + eventstore.EventType(strings.ToLower(feature.String())) + setSuffix +} + +type SetEvent[T SetEventType] struct { + *eventstore.BaseEvent + + Value T +} + +func (e *SetEvent[T]) SetBaseEvent(b *eventstore.BaseEvent) { + e.BaseEvent = b +} + +func (e *SetEvent[T]) Data() interface{} { + return e +} + +func (e *SetEvent[T]) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + +type SetEventType interface { + Boolean + FeatureType() domain.FeatureType +} + +type EventType[T SetEventType] struct { + eventstore.EventType +} + +type Boolean struct { + Boolean bool +} + +func (b Boolean) FeatureType() domain.FeatureType { + return domain.FeatureTypeBoolean +} + +func NewSetEvent[T SetEventType]( + ctx context.Context, + aggregate *eventstore.Aggregate, + eventType eventstore.EventType, + setType T, +) *SetEvent[T] { + return &SetEvent[T]{ + eventstore.NewBaseEventForPush( + ctx, aggregate, eventType), + setType, + } +} diff --git a/internal/repository/idp/saml.go b/internal/repository/idp/saml.go new file mode 100644 index 0000000000..2030bcd6f4 --- /dev/null +++ b/internal/repository/idp/saml.go @@ -0,0 +1,164 @@ +package idp + +import ( + "encoding/json" + + "github.com/zitadel/zitadel/internal/crypto" + "github.com/zitadel/zitadel/internal/errors" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/eventstore/repository" +) + +type SAMLIDPAddedEvent struct { + eventstore.BaseEvent `json:"-"` + + ID string `json:"id"` + Name string `json:"name,omitempty"` + Metadata []byte `json:"metadata,omitempty"` + Key *crypto.CryptoValue `json:"key,omitempty"` + Certificate []byte `json:"certificate,omitempty"` + Binding string `json:"binding,omitempty"` + WithSignedRequest bool `json:"withSignedRequest,omitempty"` + Options +} + +func NewSAMLIDPAddedEvent( + base *eventstore.BaseEvent, + id, + name string, + metadata []byte, + key *crypto.CryptoValue, + certificate []byte, + binding string, + withSignedRequest bool, + options Options, +) *SAMLIDPAddedEvent { + return &SAMLIDPAddedEvent{ + BaseEvent: *base, + ID: id, + Name: name, + Metadata: metadata, + Key: key, + Certificate: certificate, + Binding: binding, + WithSignedRequest: withSignedRequest, + Options: options, + } +} + +func (e *SAMLIDPAddedEvent) Data() interface{} { + return e +} + +func (e *SAMLIDPAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + +func SAMLIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error) { + e := &SAMLIDPAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + } + + err := json.Unmarshal(event.Data, e) + if err != nil { + return nil, errors.ThrowInternal(err, "IDP-v9uajo3k71", "unable to unmarshal event") + } + + return e, nil +} + +type SAMLIDPChangedEvent struct { + eventstore.BaseEvent `json:"-"` + + ID string `json:"id"` + Name *string `json:"name,omitempty"` + Metadata []byte `json:"metadata,omitempty"` + Key *crypto.CryptoValue `json:"key,omitempty"` + Certificate []byte `json:"certificate,omitempty"` + Binding *string `json:"binding,omitempty"` + WithSignedRequest *bool `json:"withSignedRequest,omitempty"` + OptionChanges +} + +func NewSAMLIDPChangedEvent( + base *eventstore.BaseEvent, + id string, + changes []SAMLIDPChanges, +) (*SAMLIDPChangedEvent, error) { + if len(changes) == 0 { + return nil, errors.ThrowPreconditionFailed(nil, "IDP-cz6mnf860t", "Errors.NoChangesFound") + } + changedEvent := &SAMLIDPChangedEvent{ + BaseEvent: *base, + ID: id, + } + for _, change := range changes { + change(changedEvent) + } + return changedEvent, nil +} + +type SAMLIDPChanges func(*SAMLIDPChangedEvent) + +func ChangeSAMLName(name string) func(*SAMLIDPChangedEvent) { + return func(e *SAMLIDPChangedEvent) { + e.Name = &name + } +} + +func ChangeSAMLMetadata(metadata []byte) func(*SAMLIDPChangedEvent) { + return func(e *SAMLIDPChangedEvent) { + e.Metadata = metadata + } +} + +func ChangeSAMLKey(key *crypto.CryptoValue) func(*SAMLIDPChangedEvent) { + return func(e *SAMLIDPChangedEvent) { + e.Key = key + } +} + +func ChangeSAMLCertificate(certificate []byte) func(*SAMLIDPChangedEvent) { + return func(e *SAMLIDPChangedEvent) { + e.Certificate = certificate + } +} + +func ChangeSAMLBinding(binding string) func(*SAMLIDPChangedEvent) { + return func(e *SAMLIDPChangedEvent) { + e.Binding = &binding + } +} + +func ChangeSAMLWithSignedRequest(withSignedRequest bool) func(*SAMLIDPChangedEvent) { + return func(e *SAMLIDPChangedEvent) { + e.WithSignedRequest = &withSignedRequest + } +} + +func ChangeSAMLOptions(options OptionChanges) func(*SAMLIDPChangedEvent) { + return func(e *SAMLIDPChangedEvent) { + e.OptionChanges = options + } +} + +func (e *SAMLIDPChangedEvent) Data() interface{} { + return e +} + +func (e *SAMLIDPChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + +func SAMLIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error) { + e := &SAMLIDPChangedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + } + + err := json.Unmarshal(event.Data, e) + if err != nil { + return nil, errors.ThrowInternal(err, "IDP-w1t1824tw5", "unable to unmarshal event") + } + + return e, nil +} diff --git a/internal/repository/idpintent/eventstore.go b/internal/repository/idpintent/eventstore.go index 12129673a7..1c7417ef0c 100644 --- a/internal/repository/idpintent/eventstore.go +++ b/internal/repository/idpintent/eventstore.go @@ -7,6 +7,8 @@ import ( func RegisterEventMappers(es *eventstore.Eventstore) { es.RegisterFilterEventMapper(AggregateType, StartedEventType, StartedEventMapper). RegisterFilterEventMapper(AggregateType, SucceededEventType, SucceededEventMapper). + RegisterFilterEventMapper(AggregateType, SAMLSucceededEventType, SAMLSucceededEventMapper). + RegisterFilterEventMapper(AggregateType, SAMLRequestEventType, SAMLRequestEventMapper). RegisterFilterEventMapper(AggregateType, LDAPSucceededEventType, LDAPSucceededEventMapper). RegisterFilterEventMapper(AggregateType, FailedEventType, FailedEventMapper) } diff --git a/internal/repository/idpintent/intent.go b/internal/repository/idpintent/intent.go index a4ff650b40..39779df4dd 100644 --- a/internal/repository/idpintent/intent.go +++ b/internal/repository/idpintent/intent.go @@ -14,6 +14,8 @@ import ( const ( StartedEventType = instanceEventTypePrefix + "started" SucceededEventType = instanceEventTypePrefix + "succeeded" + SAMLSucceededEventType = instanceEventTypePrefix + "saml.succeeded" + SAMLRequestEventType = instanceEventTypePrefix + "saml.requested" LDAPSucceededEventType = instanceEventTypePrefix + "ldap.succeeded" FailedEventType = instanceEventTypePrefix + "failed" ) @@ -124,6 +126,103 @@ func SucceededEventMapper(event *repository.Event) (eventstore.Event, error) { return e, nil } +type SAMLSucceededEvent struct { + eventstore.BaseEvent `json:"-"` + + IDPUser []byte `json:"idpUser"` + IDPUserID string `json:"idpUserId,omitempty"` + IDPUserName string `json:"idpUserName,omitempty"` + UserID string `json:"userId,omitempty"` + + Assertion *crypto.CryptoValue `json:"assertion,omitempty"` +} + +func NewSAMLSucceededEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + idpUser []byte, + idpUserID, + idpUserName, + userID string, + assertion *crypto.CryptoValue, +) *SAMLSucceededEvent { + return &SAMLSucceededEvent{ + BaseEvent: *eventstore.NewBaseEventForPush( + ctx, + aggregate, + SAMLSucceededEventType, + ), + IDPUser: idpUser, + IDPUserID: idpUserID, + IDPUserName: idpUserName, + UserID: userID, + Assertion: assertion, + } +} + +func (e *SAMLSucceededEvent) Data() interface{} { + return e +} + +func (e *SAMLSucceededEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + +func SAMLSucceededEventMapper(event *repository.Event) (eventstore.Event, error) { + e := &SAMLSucceededEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + } + + err := json.Unmarshal(event.Data, e) + if err != nil { + return nil, errors.ThrowInternal(err, "IDP-l4tw23y6lq", "unable to unmarshal event") + } + + return e, nil +} + +type SAMLRequestEvent struct { + eventstore.BaseEvent `json:"-"` + + RequestID string `json:"requestId"` +} + +func NewSAMLRequestEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + requestID string, +) *SAMLRequestEvent { + return &SAMLRequestEvent{ + BaseEvent: *eventstore.NewBaseEventForPush( + ctx, + aggregate, + SAMLRequestEventType, + ), + RequestID: requestID, + } +} + +func (e *SAMLRequestEvent) Data() interface{} { + return e +} + +func (e *SAMLRequestEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + +func SAMLRequestEventMapper(event *repository.Event) (eventstore.Event, error) { + e := &SAMLRequestEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + } + + err := json.Unmarshal(event.Data, e) + if err != nil { + return nil, errors.ThrowInternal(err, "IDP-l85678vwlf", "unable to unmarshal event") + } + + return e, nil +} + type LDAPSucceededEvent struct { eventstore.BaseEvent `json:"-"` diff --git a/internal/repository/instance/eventstore.go b/internal/repository/instance/eventstore.go index 709abde5e6..c9d750e3b1 100644 --- a/internal/repository/instance/eventstore.go +++ b/internal/repository/instance/eventstore.go @@ -94,6 +94,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) { RegisterFilterEventMapper(AggregateType, LDAPIDPChangedEventType, LDAPIDPChangedEventMapper). RegisterFilterEventMapper(AggregateType, AppleIDPAddedEventType, AppleIDPAddedEventMapper). RegisterFilterEventMapper(AggregateType, AppleIDPChangedEventType, AppleIDPChangedEventMapper). + RegisterFilterEventMapper(AggregateType, SAMLIDPAddedEventType, SAMLIDPAddedEventMapper). + RegisterFilterEventMapper(AggregateType, SAMLIDPChangedEventType, SAMLIDPChangedEventMapper). RegisterFilterEventMapper(AggregateType, IDPRemovedEventType, IDPRemovedEventMapper). RegisterFilterEventMapper(AggregateType, LoginPolicyIDPProviderAddedEventType, IdentityProviderAddedEventMapper). RegisterFilterEventMapper(AggregateType, LoginPolicyIDPProviderRemovedEventType, IdentityProviderRemovedEventMapper). diff --git a/internal/repository/instance/idp.go b/internal/repository/instance/idp.go index 69b1886277..7e82c89b05 100644 --- a/internal/repository/instance/idp.go +++ b/internal/repository/instance/idp.go @@ -35,6 +35,8 @@ const ( LDAPIDPChangedEventType eventstore.EventType = "instance.idp.ldap.v2.changed" AppleIDPAddedEventType eventstore.EventType = "instance.idp.apple.added" AppleIDPChangedEventType eventstore.EventType = "instance.idp.apple.changed" + SAMLIDPAddedEventType eventstore.EventType = "instance.idp.saml.added" + SAMLIDPChangedEventType eventstore.EventType = "instance.idp.saml.changed" IDPRemovedEventType eventstore.EventType = "instance.idp.removed" ) @@ -897,7 +899,6 @@ func NewLDAPIDPChangedEvent( id string, changes []idp.LDAPIDPChanges, ) (*LDAPIDPChangedEvent, error) { - changedEvent, err := idp.NewLDAPIDPChangedEvent( eventstore.NewBaseEventForPush( ctx, @@ -1002,6 +1003,85 @@ func AppleIDPChangedEventMapper(event *repository.Event) (eventstore.Event, erro return &AppleIDPChangedEvent{AppleIDPChangedEvent: *e.(*idp.AppleIDPChangedEvent)}, nil } +type SAMLIDPAddedEvent struct { + idp.SAMLIDPAddedEvent +} + +func NewSAMLIDPAddedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + id, + name string, + metadata []byte, + key *crypto.CryptoValue, + certificate []byte, + binding string, + withSignedRequest bool, + options idp.Options, +) *SAMLIDPAddedEvent { + return &SAMLIDPAddedEvent{ + SAMLIDPAddedEvent: *idp.NewSAMLIDPAddedEvent( + eventstore.NewBaseEventForPush( + ctx, + aggregate, + SAMLIDPAddedEventType, + ), + id, + name, + metadata, + key, + certificate, + binding, + withSignedRequest, + options, + ), + } +} + +func SAMLIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error) { + e, err := idp.SAMLIDPAddedEventMapper(event) + if err != nil { + return nil, err + } + + return &SAMLIDPAddedEvent{SAMLIDPAddedEvent: *e.(*idp.SAMLIDPAddedEvent)}, nil +} + +type SAMLIDPChangedEvent struct { + idp.SAMLIDPChangedEvent +} + +func NewSAMLIDPChangedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + id string, + changes []idp.SAMLIDPChanges, +) (*SAMLIDPChangedEvent, error) { + + changedEvent, err := idp.NewSAMLIDPChangedEvent( + eventstore.NewBaseEventForPush( + ctx, + aggregate, + SAMLIDPChangedEventType, + ), + id, + changes, + ) + if err != nil { + return nil, err + } + return &SAMLIDPChangedEvent{SAMLIDPChangedEvent: *changedEvent}, nil +} + +func SAMLIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error) { + e, err := idp.SAMLIDPChangedEventMapper(event) + if err != nil { + return nil, err + } + + return &SAMLIDPChangedEvent{SAMLIDPChangedEvent: *e.(*idp.SAMLIDPChangedEvent)}, nil +} + type IDPRemovedEvent struct { idp.RemovedEvent } diff --git a/internal/repository/org/eventstore.go b/internal/repository/org/eventstore.go index 31ad0c655d..79d409e1c7 100644 --- a/internal/repository/org/eventstore.go +++ b/internal/repository/org/eventstore.go @@ -103,6 +103,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) { RegisterFilterEventMapper(AggregateType, LDAPIDPChangedEventType, LDAPIDPChangedEventMapper). RegisterFilterEventMapper(AggregateType, AppleIDPAddedEventType, AppleIDPAddedEventMapper). RegisterFilterEventMapper(AggregateType, AppleIDPChangedEventType, AppleIDPChangedEventMapper). + RegisterFilterEventMapper(AggregateType, SAMLIDPAddedEventType, SAMLIDPAddedEventMapper). + RegisterFilterEventMapper(AggregateType, SAMLIDPChangedEventType, SAMLIDPChangedEventMapper). RegisterFilterEventMapper(AggregateType, IDPRemovedEventType, IDPRemovedEventMapper). RegisterFilterEventMapper(AggregateType, TriggerActionsSetEventType, TriggerActionsSetEventMapper). RegisterFilterEventMapper(AggregateType, TriggerActionsCascadeRemovedEventType, TriggerActionsCascadeRemovedEventMapper). diff --git a/internal/repository/org/idp.go b/internal/repository/org/idp.go index d6e85c13f8..ef590df3fb 100644 --- a/internal/repository/org/idp.go +++ b/internal/repository/org/idp.go @@ -35,6 +35,8 @@ const ( LDAPIDPChangedEventType eventstore.EventType = "org.idp.ldap.changed" AppleIDPAddedEventType eventstore.EventType = "org.idp.apple.added" AppleIDPChangedEventType eventstore.EventType = "org.idp.apple.changed" + SAMLIDPAddedEventType eventstore.EventType = "org.idp.saml.added" + SAMLIDPChangedEventType eventstore.EventType = "org.idp.saml.changed" IDPRemovedEventType eventstore.EventType = "org.idp.removed" ) @@ -1002,6 +1004,85 @@ func AppleIDPChangedEventMapper(event *repository.Event) (eventstore.Event, erro return &AppleIDPChangedEvent{AppleIDPChangedEvent: *e.(*idp.AppleIDPChangedEvent)}, nil } +type SAMLIDPAddedEvent struct { + idp.SAMLIDPAddedEvent +} + +func NewSAMLIDPAddedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + id, + name string, + metadata []byte, + key *crypto.CryptoValue, + certificate []byte, + binding string, + withSignedRequest bool, + options idp.Options, +) *SAMLIDPAddedEvent { + + return &SAMLIDPAddedEvent{ + SAMLIDPAddedEvent: *idp.NewSAMLIDPAddedEvent( + eventstore.NewBaseEventForPush( + ctx, + aggregate, + SAMLIDPAddedEventType, + ), + id, + name, + metadata, + key, + certificate, + binding, + withSignedRequest, + options, + ), + } +} + +func SAMLIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error) { + e, err := idp.SAMLIDPAddedEventMapper(event) + if err != nil { + return nil, err + } + + return &SAMLIDPAddedEvent{SAMLIDPAddedEvent: *e.(*idp.SAMLIDPAddedEvent)}, nil +} + +type SAMLIDPChangedEvent struct { + idp.SAMLIDPChangedEvent +} + +func NewSAMLIDPChangedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + id string, + changes []idp.SAMLIDPChanges, +) (*SAMLIDPChangedEvent, error) { + changedEvent, err := idp.NewSAMLIDPChangedEvent( + eventstore.NewBaseEventForPush( + ctx, + aggregate, + SAMLIDPChangedEventType, + ), + id, + changes, + ) + if err != nil { + return nil, err + } + return &SAMLIDPChangedEvent{SAMLIDPChangedEvent: *changedEvent}, nil +} + +func SAMLIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error) { + e, err := idp.SAMLIDPChangedEventMapper(event) + if err != nil { + return nil, err + } + + return &SAMLIDPChangedEvent{SAMLIDPChangedEvent: *e.(*idp.SAMLIDPChangedEvent)}, nil +} + type IDPRemovedEvent struct { idp.RemovedEvent } diff --git a/internal/static/i18n/bg.yaml b/internal/static/i18n/bg.yaml index f0c1bf273c..3588c25312 100644 --- a/internal/static/i18n/bg.yaml +++ b/internal/static/i18n/bg.yaml @@ -122,7 +122,7 @@ Errors: Empty: Паролата е празна Invalid: Паролата е невалидна NotSet: Потребителят не е задал парола - NotChanged: Паролата не е променена + NotChanged: Новата парола не може да съвпада с текущата парола NotSupported: Хеш кодирането на паролата не се поддържа PasswordComplexityPolicy: NotFound: Политиката за парола не е намерена @@ -505,6 +505,8 @@ Errors: NoChallenge: Сесия без WebAuthN предизвикателство Intent: IDPMissing: IDP липсва в заявката + IDPInvalid: IDP невалиден за заявката + ResponseInvalid: Отговорът на IDP е невалиден SuccessURLMissing: В заявката липсва URL адрес за успех FailureURLMissing: В заявката липсва URL адрес за грешка StateMissing: В заявката липсва параметър състояние @@ -522,6 +524,10 @@ Errors: Token: Invalid: Токенът е невалиден Expired: Токенът е изтекъл + Feature: + NotExisting: Функцията не съществува + TypeNotSupported: Типът функция не се поддържа + InvalidValue: Невалидна стойност за тази функция AggregateTypes: action: Действие @@ -532,6 +538,8 @@ AggregateTypes: user: Потребител usergrant: Предоставяне на потребител quota: Квота + feature: Особеност + EventTypes: user: added: Добавен потребител diff --git a/internal/static/i18n/de.yaml b/internal/static/i18n/de.yaml index 0d7ce3539d..731d63962b 100644 --- a/internal/static/i18n/de.yaml +++ b/internal/static/i18n/de.yaml @@ -120,7 +120,7 @@ Errors: Empty: Passwort ist leer Invalid: Passwort ungültig NotSet: Benutzer hat kein Passwort gesetzt - NotChanged: Passwort nicht geändert + NotChanged: Das neue Passwort darf nicht mit deinem aktuellen Passwort übereinstimmen NotSupported: Passwort-Hash-Kodierung wird nicht unterstützt PasswordComplexityPolicy: NotFound: Passwort Policy konnte nicht gefunden werden @@ -487,6 +487,8 @@ Errors: NoChallenge: Sitzung ohne WebAuthN-Challenge Intent: IDPMissing: IDP ID fehlt im Request + IDPInvalid: IDP ungültig für die Anfrage + ResponseInvalid: IDP-Antwort ist ungültig SuccessURLMissing: Success URL fehlt im Request FailureURLMissing: Failure URL fehlt im Request StateMissing: State parameter fehlt im Request @@ -505,6 +507,10 @@ Errors: Invalid: Token ist ungültig Expired: Token ist abgelaufen InvalidClient: Token wurde nicht für diesen Client ausgestellt + Feature: + NotExisting: Feature existiert nicht + TypeNotSupported: Feature Typ wird nicht unterstützt + InvalidValue: Ungültiger Wert für dieses Feature AggregateTypes: action: Action @@ -515,6 +521,7 @@ AggregateTypes: user: Benutzer usergrant: Benutzerberechtigung quota: Kontingent + feature: Feature EventTypes: user: diff --git a/internal/static/i18n/en.yaml b/internal/static/i18n/en.yaml index fcda9ec0b8..a05c418c3d 100644 --- a/internal/static/i18n/en.yaml +++ b/internal/static/i18n/en.yaml @@ -120,7 +120,7 @@ Errors: Empty: Password is empty Invalid: Password is invalid NotSet: User has not set a password - NotChanged: Password not changed + NotChanged: New password cannot be the same as your current password NotSupported: Password hash encoding not supported PasswordComplexityPolicy: NotFound: Password policy not found @@ -487,6 +487,8 @@ Errors: NoChallenge: Session without WebAuthN challenge Intent: IDPMissing: IDP ID is missing in the request + IDPInvalid: IDP invalid for the request + ResponseInvalid: IDP response is invalid SuccessURLMissing: Success URL is missing in the request FailureURLMissing: Failure URL is missing in the request StateMissing: State parameter is missing in the request @@ -505,6 +507,10 @@ Errors: Invalid: Token is invalid Expired: Token is expired InvalidClient: Token was not issued for this client + Feature: + NotExisting: Feature does not exist + TypeNotSupported: Feature type is not supported + InvalidValue: Invalid value for this feature AggregateTypes: action: Action @@ -515,6 +521,7 @@ AggregateTypes: user: User usergrant: User grant quota: Quota + feature: Feature EventTypes: user: diff --git a/internal/static/i18n/es.yaml b/internal/static/i18n/es.yaml index f316baa2a0..ceb0312735 100644 --- a/internal/static/i18n/es.yaml +++ b/internal/static/i18n/es.yaml @@ -120,7 +120,7 @@ Errors: Empty: La contraseña está vacía Invalid: La contraseña no es válida NotSet: El usuario no ha establecido una contraseña - NotChanged: Contraseña no modificada + NotChanged: La nueva contraseña no puede coincidir con la contraseña actual NotSupported: No se admite la codificación hash de contraseña PasswordComplexityPolicy: NotFound: Política de contraseñas no encontrada @@ -487,6 +487,8 @@ Errors: NoChallenge: Sesión sin desafío WebAuthN Intent: IDPMissing: Falta IDP en la solicitud + IDPInvalid: IDP no válido para la solicitud + ResponseInvalid: La respuesta del IDP no es válida SuccessURLMissing: Falta la URL de éxito en la solicitud FailureURLMissing: Falta la URL de error en la solicitud StateMissing: Falta un parámetro de estado en la solicitud @@ -505,6 +507,10 @@ Errors: Invalid: El token no es válido Expired: El token ha caducado InvalidClient: El token no ha sido emitido para este cliente + Feature: + NotExisting: La característica no existe + TypeNotSupported: El tipo de característica no es compatible + InvalidValue: Valor no válido para esta característica AggregateTypes: action: Acción @@ -515,6 +521,7 @@ AggregateTypes: user: Usuario usergrant: Concesión de usuario quota: Cuota + feature: Característica EventTypes: user: diff --git a/internal/static/i18n/fr.yaml b/internal/static/i18n/fr.yaml index fce9e07bf3..d3b5bf7eea 100644 --- a/internal/static/i18n/fr.yaml +++ b/internal/static/i18n/fr.yaml @@ -120,7 +120,7 @@ Errors: Empty: Le mot de passe est vide Invalid: Le mot de passe n'est pas valide NotSet: L'utilisateur n'a pas défini de mot de passe - NotChanged: Mot de passe non modifié + NotChanged: Le nouveau mot de passe ne peut pas être le même que votre mot de passe actuel NotSupported: Encodage de hachage de mot de passe non pris en charge PasswordComplexityPolicy: NotFound: Politique de mot de passe non trouvée @@ -487,6 +487,8 @@ Errors: NoChallenge: Session sans challenge WebAuthN Intent: IDPMissing: IDP manquant dans la requête + IDPInvalid: IDP non valide pour la demande + ResponseInvalid: La réponse de l'IDP n'est pas valide SuccessURLMissing: Success URL absent de la requête FailureURLMissing: Failure URL absent de la requête StateMissing: Paramètre d'état manquant dans la requête @@ -505,6 +507,10 @@ Errors: Invalid: Le jeton n'est pas valide Expired: Le jeton est expiré InvalidClient: Le token n'a pas été émis pour ce client + Feature: + NotExisting: La fonctionnalité n'existe pas + TypeNotSupported: Le type de fonctionnalité n'est pas pris en charge + InvalidValue: Valeur non valide pour cette fonctionnalité AggregateTypes: action: Action @@ -515,6 +521,7 @@ AggregateTypes: user: Utilisateur usergrant: Subvention de l'utilisateur quota: Contingent + feature: Fonctionnalité EventTypes: user: diff --git a/internal/static/i18n/it.yaml b/internal/static/i18n/it.yaml index 3b0d8b2bd7..e8f09e0e95 100644 --- a/internal/static/i18n/it.yaml +++ b/internal/static/i18n/it.yaml @@ -120,7 +120,7 @@ Errors: Empty: La password è vuota Invalid: La password non è valida NotSet: L'utente non ha impostato una password - NotChanged: Password non modificata + NotChanged: La nuova password non può essere uguale alla password attuale NotSupported: Codifica hash password non supportata PasswordComplexityPolicy: NotFound: Impostazioni di complessità password non trovati @@ -487,6 +487,8 @@ Errors: NoChallenge: Sessione senza sfida WebAuthN Intent: IDPMissing: IDP mancante nella richiesta + IDPInvalid: IDP non valido per la richiesta + ResponseInvalid: La risposta dell'IDP non è valida SuccessURLMissing: URL di successo mancante nella richiesta FailureURLMissing: URL di errore mancante nella richiesta StateMissing: parametro di stato mancante nella richiesta @@ -505,6 +507,10 @@ Errors: Invalid: Token non è valido Expired: Token è scaduto InvalidClient: Il token non è stato emesso per questo cliente + Feature: + NotExisting: La funzionalità non esiste + TypeNotSupported: Il tipo di funzionalità non è supportato + InvalidValue: Valore non valido per questa funzionalità AggregateTypes: action: Azione @@ -515,6 +521,7 @@ AggregateTypes: user: Utente usergrant: Sovvenzione utente quota: Quota + feature: Funzionalità EventTypes: user: diff --git a/internal/static/i18n/ja.yaml b/internal/static/i18n/ja.yaml index 974b18acd7..d302596104 100644 --- a/internal/static/i18n/ja.yaml +++ b/internal/static/i18n/ja.yaml @@ -112,7 +112,7 @@ Errors: Empty: パスワードは空です Invalid: 無効なパスワードです NotSet: パスワードが未設置です - NotChanged: パスワードは変更されていません + NotChanged: 新しいパスワードは現在のパスワードと同じにすることはできません NotSupported: パスワードハッシュエンコードはサポートされていません PasswordComplexityPolicy: NotFound: パスワードポリシーが見つかりません @@ -476,6 +476,8 @@ Errors: NoChallenge: WebAuthN チャレンジを使用しないセッション Intent: IDPMissing: リクエストにIDP IDが含まれていません + IDPInvalid: リクエストのIDPが無効 + ResponseInvalid: IDPの回答は無効 SuccessURLMissing: リクエストに成功時の URL がありません FailureURLMissing: リクエストに失敗の URL がありません StateMissing: リクエストに State パラメータがありません @@ -494,6 +496,10 @@ Errors: Invalid: トークンが無効です Expired: トークンの有効期限が切れている InvalidClient: トークンが発行されていません + Feature: + NotExisting: 機能が存在しません + TypeNotSupported: 機能タイプはサポートされていません + InvalidValue: この機能には無効な値です AggregateTypes: action: アクション @@ -504,6 +510,7 @@ AggregateTypes: user: ユーザー usergrant: ユーザーグラント quota: クォータ + feature: 特徴 EventTypes: user: diff --git a/internal/static/i18n/mk.yaml b/internal/static/i18n/mk.yaml index dd691345dc..a11d1fb9ff 100644 --- a/internal/static/i18n/mk.yaml +++ b/internal/static/i18n/mk.yaml @@ -120,7 +120,7 @@ Errors: Empty: Лозинката е празна Invalid: Невалидна лозинка NotSet: Корисникот нема поставено лозинка - NotChanged: Лозинката не е променета + NotChanged: Новата лозинка не може да биде иста со вашата тековна лозинка NotSupported: Не е поддржано хаш-кодирањето на лозинката PasswordComplexityPolicy: NotFound: Политиката за комплексност на лозинката не е пронајдена @@ -486,7 +486,9 @@ Errors: WebAuthN: NoChallenge: Сесија без предизвик WebAuthN Intent: - IDPMissing: ID на IDP недостасува во барањето + IDPMissing: ID на IDP недостасува во барањето6bg + IDPInvalid: ВРЛ неважечки за барањето + ResponseInvalid: Одговорот на ВРЛ е неважечки SuccessURLMissing: URL за успех недостасува во барањето FailureURLMissing: URL за неуспех недостасува во барањето StateMissing: Параметарот State недостасува во барањето @@ -505,6 +507,10 @@ Errors: Invalid: токенот е неважечки Expired: токенот е истечен InvalidClient: Токен не беше издаден на овој клиент + Feature: + NotExisting: Функцијата не постои + TypeNotSupported: Типот на функција не е поддржан + InvalidValue: Неважечка вредност за оваа функција AggregateTypes: action: Акција @@ -515,6 +521,7 @@ AggregateTypes: user: Корисник usergrant: Овластување на корисник quota: Квота + feature: Карактеристика EventTypes: user: diff --git a/internal/static/i18n/pl.yaml b/internal/static/i18n/pl.yaml index 98bd773e7d..6808de2eb6 100644 --- a/internal/static/i18n/pl.yaml +++ b/internal/static/i18n/pl.yaml @@ -120,7 +120,7 @@ Errors: Empty: Hasło jest puste Invalid: Hasło jest nieprawidłowe NotSet: Użytkownik nie ustawił hasła - NotChanged: Hasło nie zostało zmienione + NotChanged: Nowe hasło nie może być takie samo jak Twoje obecne hasło NotSupported: Kodowanie skrótu hasła nie jest obsługiwane PasswordComplexityPolicy: NotFound: Polityka hasła nie znaleziona @@ -487,6 +487,8 @@ Errors: NoChallenge: Sesja bez wyzwania WebAuthN Intent: IDPMissing: Brak identyfikatora IDP w żądaniu + IDPInvalid: IDP nieprawidłowe dla żądania + ResponseInvalid: Odpowiedź IDP jest nieprawidłowa SuccessURLMissing: Brak adresu URL powodzenia w żądaniu FailureURLMissing: Brak adresu URL niepowodzenia w żądaniu StateMissing: Brak parametru stanu w żądaniu @@ -505,6 +507,10 @@ Errors: Invalid: Token jest nieprawidłowy Expired: Token wygasł InvalidClient: Token nie został wydany dla tego klienta + Feature: + NotExisting: Funkcja nie istnieje + TypeNotSupported: Typ funkcji nie jest obsługiwany + InvalidValue: Nieprawidłowa wartość dla tej funkcji AggregateTypes: action: Działanie @@ -515,6 +521,7 @@ AggregateTypes: user: Użytkownik usergrant: Uprawnienie użytkownika quota: Limit + feature: Funkcja EventTypes: user: diff --git a/internal/static/i18n/pt.yaml b/internal/static/i18n/pt.yaml index 95f64e43b1..9888a2a494 100644 --- a/internal/static/i18n/pt.yaml +++ b/internal/static/i18n/pt.yaml @@ -120,7 +120,7 @@ Errors: Empty: Senha está vazia Invalid: Senha é inválida NotSet: O usuário não definiu uma senha - NotChanged: Senha não alterada + NotChanged: A nova senha não pode ser igual à sua senha atual PasswordComplexityPolicy: NotFound: Política de complexidade de senha não encontrada MinLength: A senha é muito curta @@ -485,6 +485,8 @@ Errors: NoChallenge: Sessão sem desafio WebAuthN Intent: IDPMissing: O ID do IDP está faltando na solicitação + IDPInvalid: IDP inválido para o pedido + ResponseInvalid: A resposta da PDI é inválida SuccessURLMissing: A URL de sucesso está faltando na solicitação FailureURLMissing: A URL de falha está faltando na solicitação StateMissing: O parâmetro de estado está faltando na solicitação @@ -499,6 +501,10 @@ Errors: WrongLoginClient: A solicitação de autenticação foi criada por outro cliente de login OIDCSession: RefreshTokenInvalid: O Refresh Token é inválido + Feature: + NotExisting: O recurso não existe + TypeNotSupported: O tipo de recurso não é compatível + InvalidValue: Valor inválido para este recurso AggregateTypes: action: Ação @@ -509,6 +515,7 @@ AggregateTypes: user: Usuário usergrant: Concessão de usuário quota: Cota + feature: Recurso EventTypes: user: diff --git a/internal/static/i18n/zh.yaml b/internal/static/i18n/zh.yaml index 6014382241..83b7b18398 100644 --- a/internal/static/i18n/zh.yaml +++ b/internal/static/i18n/zh.yaml @@ -120,7 +120,7 @@ Errors: Empty: 密码为空 Invalid: 密码无效 NotSet: 用户未设置密码 - NotChanged: 密码未更改 + NotChanged: 新密码不能与您当前的密码相同 NotSupported: 不支持密码哈希编码 PasswordComplexityPolicy: NotFound: 未找到密码策略 @@ -487,6 +487,8 @@ Errors: NoChallenge: 没有 WebAuthN 质询的会话 Intent: IDPMissing: 请求中缺少IDP ID + IDPInvalid: 请求的 IDP 无效 + ResponseInvalid: IDP 响应无效 SuccessURLMissing: 请求中缺少成功URL FailureURLMissing: 请求中缺少失败的URL StateMissing: 请求中缺少状态参数 @@ -505,6 +507,10 @@ Errors: Invalid: 令牌无效 Expired: 令牌已过期 InvalidClient: 没有为该客户发放令牌 + Feature: + NotExisting: 功能不存在 + TypeNotSupported: 不支持功能类型 + InvalidValue: 此功能的值无效 AggregateTypes: action: 动作 @@ -515,6 +521,7 @@ AggregateTypes: user: 用户 usergrant: 用户授权 quota: 配额 + feature: 特征 EventTypes: user: diff --git a/proto/buf.yaml b/proto/buf.yaml index 5d5a33e93d..f8cf192a95 100644 --- a/proto/buf.yaml +++ b/proto/buf.yaml @@ -19,6 +19,7 @@ lint: - zitadel/auth.proto - zitadel/change.proto - zitadel/event.proto + - zitadel/feature.proto - zitadel/idp.proto - zitadel/instance.proto - zitadel/management.proto diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto index 153f3b8f54..620936f12b 100644 --- a/proto/zitadel/admin.proto +++ b/proto/zitadel/admin.proto @@ -26,7 +26,7 @@ import "validate/validate.proto"; package zitadel.admin.v1; -option go_package ="github.com/zitadel/zitadel/pkg/grpc/admin"; +option go_package = "github.com/zitadel/zitadel/pkg/grpc/admin"; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { info: { @@ -85,7 +85,7 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { name: "Message Texts" }, { - name: "Notification Providers" + name: "Notification Providers" }, { name: "Notification Settings" @@ -130,7 +130,7 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { consumes: "application/grpc-web+proto"; produces: "application/grpc-web+proto"; - host: "$ZITADEL_DOMAIN"; + host: "$CUSTOM-DOMAIN"; base_path: "/admin/v1"; external_docs: { @@ -150,8 +150,8 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { value: { type: TYPE_OAUTH2; flow: FLOW_ACCESS_CODE; - authorization_url: "$ZITADEL_DOMAIN/oauth/v2/authorize"; - token_url: "$ZITADEL_DOMAIN/oauth/v2/token"; + authorization_url: "$CUSTOM-DOMAIN/oauth/v2/authorize"; + token_url: "$CUSTOM-DOMAIN/oauth/v2/token"; scopes: { scope: { key: "openid"; @@ -167,13 +167,13 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { } security: { security_requirement: { - key: "OAuth2"; - value: { - scope: "openid"; - scope: "urn:zitadel:iam:org:project:id:zitadel:aud"; - } + key: "OAuth2"; + value: { + scope: "openid"; + scope: "urn:zitadel:iam:org:project:id:zitadel:aud"; + } } - } + } responses: { key: "403"; value: { @@ -1684,6 +1684,60 @@ service AdminService { }; } + // Add a new SAML identity provider on the instance + rpc AddSAMLProvider(AddSAMLProviderRequest) returns (AddSAMLProviderResponse) { + option (google.api.http) = { + post: "/idps/saml" + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.idp.write" + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Identity Providers"; + summary: "Add SAML Identity Provider"; + description: ""; + }; + } + + // Change an existing SAML identity provider on the instance + rpc UpdateSAMLProvider(UpdateSAMLProviderRequest) returns (UpdateSAMLProviderResponse) { + option (google.api.http) = { + put: "/idps/saml/{id}" + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.idp.write" + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Identity Providers"; + summary: "Update SAML Identity Provider"; + description: ""; + }; + } + + // Regenerate certificate for an existing SAML identity provider in the organization + rpc RegenerateSAMLProviderCertificate(RegenerateSAMLProviderCertificateRequest) returns (RegenerateSAMLProviderCertificateResponse) { + option (google.api.http) = { + post: "/idps/saml/{id}/_generate_certificate" + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.idp.write" + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Identity Providers"; + summary: "Regenerate SAML Identity Provider Certificate"; + description: ""; + }; + } + // Remove an identity provider // Will remove all linked providers of this configuration on the users rpc DeleteProvider(DeleteProviderRequest) returns (DeleteProviderResponse) { @@ -1692,7 +1746,7 @@ service AdminService { }; option (zitadel.v1.auth_option) = { - permission: "org.idp.write" + permission: "iam.idp.write" }; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { @@ -1702,7 +1756,7 @@ service AdminService { }; } - rpc GetOrgIAMPolicy(GetOrgIAMPolicyRequest) returns (GetOrgIAMPolicyResponse) { + rpc GetOrgIAMPolicy(GetOrgIAMPolicyRequest) returns (GetOrgIAMPolicyResponse) { option (google.api.http) = { get: "/policies/orgiam"; }; @@ -2004,7 +2058,7 @@ service AdminService { }; } - rpc UpdateLabelPolicy(UpdateLabelPolicyRequest) returns (UpdateLabelPolicyResponse) { + rpc UpdateLabelPolicy(UpdateLabelPolicyRequest) returns (UpdateLabelPolicyResponse) { option (google.api.http) = { put: "/policies/label"; body: "*"; @@ -3706,6 +3760,19 @@ service AdminService { description: "Returns a list of the possible aggregate types in ZITADEL. This is used to filter the aggregate types in the list events request." }; } + + // Activates the "LoginDefaultOrg" feature by setting the flag to "true" + // This is irreversible! + // Once activated, the login UI will use the settings of the default org (and not from the instance) if not organisation context is set + rpc ActivateFeatureLoginDefaultOrg(ActivateFeatureLoginDefaultOrgRequest) returns (ActivateFeatureLoginDefaultOrgResponse) { + option (google.api.http) = { + put: "/features/login_default_org" + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.feature.write"; + }; + } } @@ -4093,10 +4160,10 @@ message GetOIDCSettingsResponse { } message AddOIDCSettingsRequest { - google.protobuf.Duration access_token_lifetime = 1; - google.protobuf.Duration id_token_lifetime = 2; - google.protobuf.Duration refresh_token_idle_expiration = 3; - google.protobuf.Duration refresh_token_expiration = 4; + google.protobuf.Duration access_token_lifetime = 1; + google.protobuf.Duration id_token_lifetime = 2; + google.protobuf.Duration refresh_token_idle_expiration = 3; + google.protobuf.Duration refresh_token_expiration = 4; } message AddOIDCSettingsResponse { @@ -4104,10 +4171,10 @@ message AddOIDCSettingsResponse { } message UpdateOIDCSettingsRequest { - google.protobuf.Duration access_token_lifetime = 1; - google.protobuf.Duration id_token_lifetime = 2; - google.protobuf.Duration refresh_token_idle_expiration = 3; - google.protobuf.Duration refresh_token_expiration = 4; + google.protobuf.Duration access_token_lifetime = 1; + google.protobuf.Duration id_token_lifetime = 2; + google.protobuf.Duration refresh_token_idle_expiration = 3; + google.protobuf.Duration refresh_token_expiration = 4; } message UpdateOIDCSettingsResponse { @@ -4123,9 +4190,9 @@ message GetSecurityPolicyResponse{ message SetSecurityPolicyRequest{ // states if iframe embedding is enabled or disabled - bool enable_iframe_embedding = 1; - // origins allowed loading ZITADEL in an iframe if enable_iframe_embedding is true - repeated string allowed_origins = 2; + bool enable_iframe_embedding = 1; + // origins allowed loading ZITADEL in an iframe if enable_iframe_embedding is true + repeated string allowed_origins = 2; } message SetSecurityPolicyResponse{ @@ -4136,11 +4203,11 @@ message SetSecurityPolicyResponse{ // at least one argument has to be provided message IsOrgUniqueRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - description: "All unique fields of an organization"; - required: ["name", "domain"] - }; - }; + json_schema: { + description: "All unique fields of an organization"; + required: ["name", "domain"] + }; + }; string name = 1 [ (validate.rules).string = {max_len: 200}, @@ -4179,11 +4246,11 @@ message GetOrgByIDResponse { message ListOrgsRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - description: "Search query for lists"; - required: ["query"] - }; - }; + json_schema: { + description: "Search query for lists"; + required: ["query"] + }; + }; //list limitations and ordering zitadel.v1.ListQuery query = 1; @@ -4201,18 +4268,18 @@ message ListOrgsResponse { message SetUpOrgRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - description: "Request to set up an organization. User is required"; - required: ["org", "user"] - }; - }; + json_schema: { + description: "Request to set up an organization. User is required"; + required: ["org", "user"] + }; + }; message Org { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { json_schema: { required: ["name"] }; - }; + }; string name = 1 [ (validate.rules).string = {min_len: 1, max_len: 200}, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -4236,7 +4303,7 @@ message SetUpOrgRequest { json_schema: { required: ["user_name", "profile", "email", "password"]; }; - }; + }; message Profile { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { @@ -4570,11 +4637,11 @@ message AddJWTIDPResponse { message UpdateIDPRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - description: "Updates fields of an IDP"; - required: ["idp_id", "name"] - }; - }; + json_schema: { + description: "Updates fields of an IDP"; + required: ["idp_id", "name"] + }; + }; string idp_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; string name = 2 [ @@ -4600,10 +4667,10 @@ message UpdateIDPResponse { message DeactivateIDPRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - required: ["idp_id"] - }; - }; + json_schema: { + required: ["idp_id"] + }; + }; string idp_id = 1 [ (validate.rules).string = {min_len: 1, max_len: 200}, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -4620,10 +4687,10 @@ message DeactivateIDPResponse { message ReactivateIDPRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - required: ["idp_id"] - }; - }; + json_schema: { + required: ["idp_id"] + }; + }; string idp_id = 1 [ (validate.rules).string = {min_len: 1, max_len: 200}, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -4640,10 +4707,10 @@ message ReactivateIDPResponse { message RemoveIDPRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - required: ["idp_id"] - }; - }; + json_schema: { + required: ["idp_id"] + }; + }; string idp_id = 1 [ (validate.rules).string = {min_len: 1, max_len: 200}, @@ -4661,10 +4728,10 @@ message RemoveIDPResponse { message UpdateIDPOIDCConfigRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - required: ["idp_id", "issuer", "client_id"] - }; - }; + json_schema: { + required: ["idp_id", "issuer", "client_id"] + }; + }; string idp_id = 1 [ (validate.rules).string = {min_len: 1, max_len: 200}, @@ -5728,6 +5795,86 @@ message UpdateAppleProviderResponse { zitadel.v1.ObjectDetails details = 1; } +message AddSAMLProviderRequest { + string name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; + oneof metadata { + option (validate.required) = true; + bytes metadata_xml = 2 [ + (validate.rules).bytes.max_len = 500000, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Metadata of the SAML identity provider"; + } + ]; + string metadata_url = 3 [ + (validate.rules).string.max_len = 200, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"https://test.com/saml/metadata\"" + description: "Url to the metadata of the SAML identity provider"; + } + ]; + } + zitadel.idp.v1.SAMLBinding binding = 4 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Binding which defines the type of communication with the identity provider"; + } + ]; + bool with_signed_request = 5 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Boolean which defines if the authentication requests are signed"; + } + ]; + zitadel.idp.v1.Options provider_options = 6; +} + +message AddSAMLProviderResponse { + zitadel.v1.ObjectDetails details = 1; + string id = 2; +} + +message UpdateSAMLProviderRequest { + string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; + string name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}]; + oneof metadata { + option (validate.required) = true; + bytes metadata_xml = 3 [ + (validate.rules).bytes.max_len = 500000, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Metadata of the SAML identity provider"; + } + ]; + string metadata_url = 4 [ + (validate.rules).string.max_len = 200, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"https://test.com/saml/metadata\"" + description: "Url to the metadata of the SAML identity provider"; + } + ]; + } + zitadel.idp.v1.SAMLBinding binding = 5 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Binding which defines the type of communication with the identity provider"; + } + ]; + bool with_signed_request = 6 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Boolean which defines if the authentication requests are signed"; + } + ]; + zitadel.idp.v1.Options provider_options = 7; +} + +message UpdateSAMLProviderResponse { + zitadel.v1.ObjectDetails details = 1; +} + +message RegenerateSAMLProviderCertificateRequest { + string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; +} + +message RegenerateSAMLProviderCertificateResponse { + zitadel.v1.ObjectDetails details = 1; +} + message DeleteProviderRequest { string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; } @@ -5863,10 +6010,10 @@ message UpdateDomainPolicyResponse { message GetCustomDomainPolicyRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - required: ["org_id"] - }; - }; + json_schema: { + required: ["org_id"] + }; + }; string org_id = 1 [ (validate.rules).string = {min_len: 1, max_len: 200}, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -5885,10 +6032,10 @@ message GetCustomDomainPolicyResponse { message AddCustomDomainPolicyRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - required: ["org_id"] - }; - }; + json_schema: { + required: ["org_id"] + }; + }; string org_id = 1 [ (validate.rules).string = {min_len: 1, max_len: 200}, @@ -5921,10 +6068,10 @@ message AddCustomDomainPolicyResponse { message UpdateCustomDomainPolicyRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - required: ["org_id"] - }; - }; + json_schema: { + required: ["org_id"] + }; + }; string org_id = 1 [ (validate.rules).string = {min_len: 1, max_len: 200}, @@ -5957,10 +6104,10 @@ message UpdateCustomDomainPolicyResponse { message ResetCustomDomainPolicyToDefaultRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - required: ["org_id"] - }; - }; + json_schema: { + required: ["org_id"] + }; + }; string org_id = 1 [ (validate.rules).string = {min_len: 1, max_len: 200}, @@ -6037,7 +6184,7 @@ message UpdateLabelPolicyRequest { } ]; string background_color_dark = 8 [ - (validate.rules).string = { max_len: 50}, + (validate.rules).string = {max_len: 50}, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { description: "hex value for background color dark theme"; example: "\"#111827\""; @@ -6045,7 +6192,7 @@ message UpdateLabelPolicyRequest { } ]; string warn_color_dark = 9 [ - (validate.rules).string = { max_len: 50}, + (validate.rules).string = {max_len: 50}, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { description: "hex value for warning color dark theme"; example: "\"#FF3B5B\""; @@ -6053,7 +6200,7 @@ message UpdateLabelPolicyRequest { } ]; string font_color_dark = 10 [ - (validate.rules).string = { max_len: 50}, + (validate.rules).string = {max_len: 50}, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { description: "hex value for font color dark theme"; example: "\"#FFFFFF\""; @@ -6201,10 +6348,10 @@ message ListLoginPolicyIDPsResponse { message AddIDPToLoginPolicyRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - required: ["org_id"] - }; - }; + json_schema: { + required: ["org_id"] + }; + }; string idp_id = 1 [ (validate.rules).string = {min_len: 1, max_len: 200}, @@ -6224,10 +6371,10 @@ message AddIDPToLoginPolicyResponse { message RemoveIDPFromLoginPolicyRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - required: ["idp_id"] - }; - }; + json_schema: { + required: ["idp_id"] + }; + }; string idp_id = 1 [ (validate.rules).string = {min_len: 1, max_len: 200}, @@ -6253,10 +6400,10 @@ message ListLoginPolicySecondFactorsResponse { message AddSecondFactorToLoginPolicyRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - required: ["type"] - }; - }; + json_schema: { + required: ["type"] + }; + }; zitadel.policy.v1.SecondFactorType type = 1 [(validate.rules).enum = {defined_only: true, not_in: [0]}]; } @@ -6267,10 +6414,10 @@ message AddSecondFactorToLoginPolicyResponse { message RemoveSecondFactorFromLoginPolicyRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - required: ["type"] - }; - }; + json_schema: { + required: ["type"] + }; + }; zitadel.policy.v1.SecondFactorType type = 1 [(validate.rules).enum = {defined_only: true, not_in: [0]}]; } @@ -6289,10 +6436,10 @@ message ListLoginPolicyMultiFactorsResponse { message AddMultiFactorToLoginPolicyRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - required: ["type"] - }; - }; + json_schema: { + required: ["type"] + }; + }; zitadel.policy.v1.MultiFactorType type = 1 [(validate.rules).enum = {defined_only: true, not_in: [0]}]; } @@ -6303,10 +6450,10 @@ message AddMultiFactorToLoginPolicyResponse { message RemoveMultiFactorFromLoginPolicyRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - required: ["type"] - }; - }; + json_schema: { + required: ["type"] + }; + }; zitadel.policy.v1.MultiFactorType type = 1 [(validate.rules).enum = {defined_only: true, not_in: [0]}]; } @@ -6456,11 +6603,11 @@ message GetNotificationPolicyResponse { } message UpdateNotificationPolicyRequest { - bool password_change = 1 [ - (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { - description: "If set to true the users will get a notification whenever their password has been changed."; - } - ]; + bool password_change = 1 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "If set to true the users will get a notification whenever their password has been changed."; + } + ]; } message UpdateNotificationPolicyResponse { @@ -7248,10 +7395,10 @@ message ResetCustomLoginTextsToDefaultResponse { message AddIAMMemberRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - required: ["user_id"] - }; - }; + json_schema: { + required: ["user_id"] + }; + }; string user_id = 1 [ (validate.rules).string = {min_len: 1, max_len: 200}, @@ -7275,10 +7422,10 @@ message AddIAMMemberResponse { message UpdateIAMMemberRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - required: ["user_id"] - }; - }; + json_schema: { + required: ["user_id"] + }; + }; string user_id = 1 [ (validate.rules).string = {min_len: 1, max_len: 200}, @@ -7302,10 +7449,10 @@ message UpdateIAMMemberResponse { message RemoveIAMMemberRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - required: ["user_id"] - }; - }; + json_schema: { + required: ["user_id"] + }; + }; string user_id = 1 [ (validate.rules).string = {min_len: 1, max_len: 200}, @@ -7363,10 +7510,10 @@ message ListFailedEventsResponse { message RemoveFailedEventRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - required: ["database", "view_name", "failed_sequence"] - }; - }; + json_schema: { + required: ["database", "view_name", "failed_sequence"] + }; + }; string database = 1 [ (validate.rules).string = {min_len: 1, max_len: 200}, @@ -7463,7 +7610,7 @@ message ImportDataRequest { message S3Input{ string path = 1; string endpoint = 2; - string access_key_id =3; + string access_key_id = 3; string secret_access_key = 4; bool ssl = 5; string bucket = 6; @@ -7622,7 +7769,7 @@ message ExportDataRequest { message S3Output{ string path = 1; string endpoint = 2; - string access_key_id =3; + string access_key_id = 3; string secret_access_key = 4; bool ssl = 5; string bucket = 6; @@ -7740,3 +7887,9 @@ message ListAggregateTypesRequest {} message ListAggregateTypesResponse { repeated zitadel.event.v1.AggregateType aggregate_types = 1; } + +message ActivateFeatureLoginDefaultOrgRequest {} + +message ActivateFeatureLoginDefaultOrgResponse { + zitadel.v1.ObjectDetails details = 1; +} \ No newline at end of file diff --git a/proto/zitadel/auth.proto b/proto/zitadel/auth.proto index 934a275709..5fd8772edc 100644 --- a/proto/zitadel/auth.proto +++ b/proto/zitadel/auth.proto @@ -77,7 +77,7 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { produces: "application/grpc"; produces: "application/grpc-web+proto"; - host: "$ZITADEL_DOMAIN"; + host: "$CUSTOM-DOMAIN"; base_path: "/auth/v1"; external_docs: { @@ -97,8 +97,8 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { value: { type: TYPE_OAUTH2; flow: FLOW_ACCESS_CODE; - authorization_url: "$ZITADEL_DOMAIN/oauth/v2/authorize"; - token_url: "$ZITADEL_DOMAIN/oauth/v2/token"; + authorization_url: "$CUSTOM-DOMAIN/oauth/v2/authorize"; + token_url: "$CUSTOM-DOMAIN/oauth/v2/token"; scopes: { scope: { key: "openid"; diff --git a/proto/zitadel/feature.proto b/proto/zitadel/feature.proto new file mode 100644 index 0000000000..7b82f71517 --- /dev/null +++ b/proto/zitadel/feature.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package zitadel.feature.v1; + +option go_package ="github.com/zitadel/zitadel/pkg/grpc/feature"; + +enum InstanceFeature { + INSTANCE_FEATURE_UNSPECIFIED = 0; + INSTANCE_FEATURE_LOGIN_DEFAULT_ORG = 1; +} \ No newline at end of file diff --git a/proto/zitadel/idp.proto b/proto/zitadel/idp.proto index ddf416dd54..6171471075 100644 --- a/proto/zitadel/idp.proto +++ b/proto/zitadel/idp.proto @@ -117,7 +117,6 @@ enum IDPStylingType { enum IDPType { IDP_TYPE_UNSPECIFIED = 0; IDP_TYPE_OIDC = 1; - //PLANNED: IDP_TYPE_SAML IDP_TYPE_JWT = 3; } @@ -267,6 +266,14 @@ enum ProviderType { PROVIDER_TYPE_GITLAB_SELF_HOSTED = 9; PROVIDER_TYPE_GOOGLE = 10; PROVIDER_TYPE_APPLE = 11; + PROVIDER_TYPE_SAML = 12; +} + +enum SAMLBinding { + SAML_BINDING_UNSPECIFIED = 0; + SAML_BINDING_POST = 1; + SAML_BINDING_REDIRECT = 2; + SAML_BINDING_ARTIFACT = 3; } message ProviderConfig { @@ -283,6 +290,7 @@ message ProviderConfig { GitLabSelfHostedConfig gitlab_self_hosted = 10; AzureADConfig azure_ad = 11; AppleConfig apple = 12; + SAMLConfig saml = 13; } } @@ -443,6 +451,24 @@ message LDAPConfig { LDAPAttributes attributes = 9; } +message SAMLConfig { + bytes metadata_xml = 1 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Metadata of the SAML identity provider"; + } + ]; + zitadel.idp.v1.SAMLBinding binding = 2 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Binding which defines the type of communication with the identity provider"; + } + ]; + bool with_signed_request = 3 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Boolean which defines if the authentication requests are signed"; + } + ]; +} + message AzureADConfig { string client_id = 1 [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto index 5285d2ffdc..6770f77427 100644 --- a/proto/zitadel/management.proto +++ b/proto/zitadel/management.proto @@ -146,7 +146,7 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { consumes: "application/grpc-web+proto"; produces: "application/grpc-web+proto"; - host: "$ZITADEL_DOMAIN"; + host: "$CUSTOM-DOMAIN"; base_path: "/management/v1"; external_docs: { @@ -166,8 +166,8 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { value: { type: TYPE_OAUTH2; flow: FLOW_ACCESS_CODE; - authorization_url: "$ZITADEL_DOMAIN/oauth/v2/authorize"; - token_url: "$ZITADEL_DOMAIN/oauth/v2/token"; + authorization_url: "$CUSTOM-DOMAIN/oauth/v2/authorize"; + token_url: "$CUSTOM-DOMAIN/oauth/v2/token"; scopes: { scope: { key: "openid"; @@ -7094,6 +7094,60 @@ service ManagementService { }; } + // Add a new SAML identity provider in the organization + rpc AddSAMLProvider(AddSAMLProviderRequest) returns (AddSAMLProviderResponse) { + option (google.api.http) = { + post: "/idps/saml" + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "org.idp.write" + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Identity Providers"; + summary: "Add SAML Identity Provider"; + description: ""; + }; + } + + // Change an existing SAML identity provider in the organization + rpc UpdateSAMLProvider(UpdateSAMLProviderRequest) returns (UpdateSAMLProviderResponse) { + option (google.api.http) = { + put: "/idps/saml/{id}" + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "org.idp.write" + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Identity Providers"; + summary: "Update SAML Identity Provider"; + description: ""; + }; + } + + // Regenerate certificate for an existing SAML identity provider in the organization + rpc RegenerateSAMLProviderCertificate(RegenerateSAMLProviderCertificateRequest) returns (RegenerateSAMLProviderCertificateResponse) { + option (google.api.http) = { + post: "/idps/saml/{id}/_generate_certificate" + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "org.idp.write" + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Identity Providers"; + summary: "Regenerate SAML Identity Provider Certificate"; + description: ""; + }; + } + // Remove an identity provider // Will remove all linked providers of this configuration on the users rpc DeleteProvider(DeleteProviderRequest) returns (DeleteProviderResponse) { @@ -12485,6 +12539,86 @@ message UpdateLDAPProviderResponse { zitadel.v1.ObjectDetails details = 1; } +message AddSAMLProviderRequest { + string name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; + oneof metadata { + option (validate.required) = true; + bytes metadata_xml = 2 [ + (validate.rules).bytes.max_len = 500000, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Metadata of the SAML identity provider"; + } + ]; + string metadata_url = 3 [ + (validate.rules).string.max_len = 200, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"https://test.com/saml/metadata\"" + description: "Url to the metadata of the SAML identity provider"; + } + ]; + } + zitadel.idp.v1.SAMLBinding binding = 4 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Binding which defines the type of communication with the identity provider"; + } + ]; + bool with_signed_request = 5 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Boolean which defines if the authentication requests are signed"; + } + ]; + zitadel.idp.v1.Options provider_options = 6; +} + +message AddSAMLProviderResponse { + zitadel.v1.ObjectDetails details = 1; + string id = 2; +} + +message UpdateSAMLProviderRequest { + string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; + string name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}]; + oneof metadata { + option (validate.required) = true; + bytes metadata_xml = 3 [ + (validate.rules).bytes.max_len = 500000, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Metadata of the SAML identity provider"; + } + ]; + string metadata_url = 4 [ + (validate.rules).string.max_len = 200, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"https://test.com/saml/metadata\"" + description: "Url to the metadata of the SAML identity provider"; + } + ]; + } + zitadel.idp.v1.SAMLBinding binding = 5 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Binding which defines the type of communication with the identity provider"; + } + ]; + bool with_signed_request = 6 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Boolean which defines if the authentication requests are signed"; + } + ]; + zitadel.idp.v1.Options provider_options = 7; +} + +message UpdateSAMLProviderResponse { + zitadel.v1.ObjectDetails details = 1; +} + +message RegenerateSAMLProviderCertificateRequest { + string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; +} + +message RegenerateSAMLProviderCertificateResponse { + zitadel.v1.ObjectDetails details = 1; +} + message AddAppleProviderRequest { // Apple will be used as default, if no name is provided string name = 1 [ diff --git a/proto/zitadel/oidc/v2beta/oidc_service.proto b/proto/zitadel/oidc/v2beta/oidc_service.proto index f6dd78730a..d962c90a91 100644 --- a/proto/zitadel/oidc/v2beta/oidc_service.proto +++ b/proto/zitadel/oidc/v2beta/oidc_service.proto @@ -39,7 +39,7 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { consumes: "application/grpc-web+proto"; produces: "application/grpc-web+proto"; - host: "$ZITADEL_DOMAIN"; + host: "$CUSTOM-DOMAIN"; base_path: "/"; external_docs: { @@ -52,8 +52,8 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { value: { type: TYPE_OAUTH2; flow: FLOW_ACCESS_CODE; - authorization_url: "$ZITADEL_DOMAIN/oauth/v2/authorize"; - token_url: "$ZITADEL_DOMAIN/oauth/v2/token"; + authorization_url: "$CUSTOM-DOMAIN/oauth/v2/authorize"; + token_url: "$CUSTOM-DOMAIN/oauth/v2/token"; scopes: { scope: { key: "openid"; diff --git a/proto/zitadel/org/v2beta/org_service.proto b/proto/zitadel/org/v2beta/org_service.proto index 7ea2581c1b..132f0f1f30 100644 --- a/proto/zitadel/org/v2beta/org_service.proto +++ b/proto/zitadel/org/v2beta/org_service.proto @@ -48,7 +48,7 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { consumes: "application/grpc-web+proto"; produces: "application/grpc-web+proto"; - host: "$ZITADEL_DOMAIN"; + host: "$CUSTOM-DOMAIN"; base_path: "/"; external_docs: { @@ -61,8 +61,8 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { value: { type: TYPE_OAUTH2; flow: FLOW_ACCESS_CODE; - authorization_url: "$ZITADEL_DOMAIN/oauth/v2/authorize"; - token_url: "$ZITADEL_DOMAIN/oauth/v2/token"; + authorization_url: "$CUSTOM-DOMAIN/oauth/v2/authorize"; + token_url: "$CUSTOM-DOMAIN/oauth/v2/token"; scopes: { scope: { key: "openid"; diff --git a/proto/zitadel/session/v2beta/session_service.proto b/proto/zitadel/session/v2beta/session_service.proto index d29930dcd6..745c430019 100644 --- a/proto/zitadel/session/v2beta/session_service.proto +++ b/proto/zitadel/session/v2beta/session_service.proto @@ -42,7 +42,7 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { consumes: "application/grpc-web+proto"; produces: "application/grpc-web+proto"; - host: "$ZITADEL_DOMAIN"; + host: "$CUSTOM-DOMAIN"; base_path: "/"; external_docs: { @@ -55,8 +55,8 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { value: { type: TYPE_OAUTH2; flow: FLOW_ACCESS_CODE; - authorization_url: "$ZITADEL_DOMAIN/oauth/v2/authorize"; - token_url: "$ZITADEL_DOMAIN/oauth/v2/token"; + authorization_url: "$CUSTOM-DOMAIN/oauth/v2/authorize"; + token_url: "$CUSTOM-DOMAIN/oauth/v2/token"; scopes: { scope: { key: "openid"; diff --git a/proto/zitadel/settings/v2beta/settings_service.proto b/proto/zitadel/settings/v2beta/settings_service.proto index 5a5c177eb8..9c5d54d6c4 100644 --- a/proto/zitadel/settings/v2beta/settings_service.proto +++ b/proto/zitadel/settings/v2beta/settings_service.proto @@ -44,7 +44,7 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { consumes: "application/grpc-web+proto"; produces: "application/grpc-web+proto"; - host: "$ZITADEL_DOMAIN"; + host: "$CUSTOM-DOMAIN"; base_path: "/"; external_docs: { @@ -57,8 +57,8 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { value: { type: TYPE_OAUTH2; flow: FLOW_ACCESS_CODE; - authorization_url: "$ZITADEL_DOMAIN/oauth/v2/authorize"; - token_url: "$ZITADEL_DOMAIN/oauth/v2/token"; + authorization_url: "$CUSTOM-DOMAIN/oauth/v2/authorize"; + token_url: "$CUSTOM-DOMAIN/oauth/v2/token"; scopes: { scope: { key: "openid"; diff --git a/proto/zitadel/system.proto b/proto/zitadel/system.proto index fe8e3a0ab7..ed2e73d29d 100644 --- a/proto/zitadel/system.proto +++ b/proto/zitadel/system.proto @@ -6,6 +6,7 @@ import "zitadel/instance.proto"; import "zitadel/member.proto"; import "zitadel/quota.proto"; import "zitadel/auth_n_key.proto"; +import "zitadel/feature.proto"; import "google/api/annotations.proto"; import "google/protobuf/timestamp.proto"; @@ -49,7 +50,7 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { consumes: "application/grpc-web+proto"; produces: "application/grpc-web+proto"; - host: "$ZITADEL_DOMAIN"; + host: "$CUSTOM-DOMAIN"; base_path: "/system/v1"; external_docs: { @@ -397,6 +398,18 @@ service SystemService { permission: "authenticated"; }; } + + // Set a feature flag on an instance + rpc SetInstanceFeature(SetInstanceFeatureRequest) returns (SetInstanceFeatureResponse) { + option (google.api.http) = { + put: "/instances/{instance_id}/features/{feature_id}" + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "authenticated"; + }; + } } @@ -879,3 +892,18 @@ message FailedEvent { } ]; } + +message SetInstanceFeatureRequest { + string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; + zitadel.feature.v1.InstanceFeature feature_id = 2 [(validate.rules).enum = {not_in: 0, defined_only: true}]; + // value based on the feature type + oneof value { + option (validate.required) = true; + + bool bool = 3; + } +} + +message SetInstanceFeatureResponse { + zitadel.v1.ObjectDetails details = 1; +} \ No newline at end of file diff --git a/proto/zitadel/user/v2beta/idp.proto b/proto/zitadel/user/v2beta/idp.proto index c8b5c27ae6..dc6e07a5e2 100644 --- a/proto/zitadel/user/v2beta/idp.proto +++ b/proto/zitadel/user/v2beta/idp.proto @@ -57,7 +57,7 @@ message IDPIntent { description: "ID of the IDP intent" min_length: 1; max_length: 200; - example: "\"163840776835432705=\""; + example: "\"163840776835432705\""; } ]; string idp_intent_token = 2 [ @@ -68,6 +68,13 @@ message IDPIntent { example: "\"SJKL3ioIDpo342ioqw98fjp3sdf32wahb=\""; } ]; + string user_id = 3 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "ID of the ZITADEL user if external user already linked" + max_length: 200; + example: "\"163840776835432345\""; + } + ]; } message IDPInformation{ @@ -82,6 +89,11 @@ message IDPInformation{ description: "LDAP entity attributes returned by the identity provider" } ]; + IDPSAMLAccessInformation saml = 7 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "SAMLResponse returned by the identity provider" + } + ]; } string idp_id = 2 [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -117,6 +129,10 @@ message IDPLDAPAccessInformation{ google.protobuf.Struct attributes = 1; } +message IDPSAMLAccessInformation{ + bytes assertion = 1; +} + message IDPLink { string idp_id = 1 [ (validate.rules).string = {min_len: 1, max_len: 200}, diff --git a/proto/zitadel/user/v2beta/user_service.proto b/proto/zitadel/user/v2beta/user_service.proto index 47f491f2d0..01abeceed4 100644 --- a/proto/zitadel/user/v2beta/user_service.proto +++ b/proto/zitadel/user/v2beta/user_service.proto @@ -46,7 +46,7 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { consumes: "application/grpc-web+proto"; produces: "application/grpc-web+proto"; - host: "$ZITADEL_DOMAIN"; + host: "$CUSTOM-DOMAIN"; base_path: "/"; external_docs: { @@ -59,8 +59,8 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { value: { type: TYPE_OAUTH2; flow: FLOW_ACCESS_CODE; - authorization_url: "$ZITADEL_DOMAIN/oauth/v2/authorize"; - token_url: "$ZITADEL_DOMAIN/oauth/v2/token"; + authorization_url: "$CUSTOM-DOMAIN/oauth/v2/authorize"; + token_url: "$CUSTOM-DOMAIN/oauth/v2/token"; scopes: { scope: { key: "openid"; @@ -1132,6 +1132,11 @@ message StartIdentityProviderIntentResponse{ description: "IDP Intent information" } ]; + bytes post_form = 4 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "POST call information" + } + ]; } } @@ -1159,6 +1164,12 @@ message RetrieveIdentityProviderIntentRequest{ message RetrieveIdentityProviderIntentResponse{ zitadel.object.v2beta.Details details = 1; IDPInformation idp_information = 2; + string user_id = 3 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "ID of the user in ZITADEL if external user is linked" + example: "\"163840776835432345\""; + } + ]; } message AddIDPLinkRequest{