mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-04 23:45:07 +00:00
feat: add apple as idp (#6442)
* feat: manage apple idp * handle apple idp callback * add tests for provider * basic console implementation * implement flow for login UI and add logos / styling * tests * cleanup * add upload button * begin i18n * apple logo positioning, file upload component * fix add apple instance idp * add missing apple logos for login * update to go 1.21 * fix slice compare * revert permission changes * concrete error messages * translate login apple logo -y-2px * change form parsing * sign in button * fix tests * lint console --------- Co-authored-by: peintnermax <max@caos.ch>
This commit is contained in:
parent
0d94947d3c
commit
e17b49e4ca
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
with:
|
||||
node_version: '18'
|
||||
buf_version: 'latest'
|
||||
go_version: '1.20'
|
||||
go_version: '1.21'
|
||||
|
||||
console:
|
||||
uses: ./.github/workflows/console.yml
|
||||
@ -35,7 +35,7 @@ jobs:
|
||||
needs: [core, console, version]
|
||||
uses: ./.github/workflows/compile.yml
|
||||
with:
|
||||
go_version: '1.20'
|
||||
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,7 +46,7 @@ jobs:
|
||||
needs: core
|
||||
uses: ./.github/workflows/core-unit-test.yml
|
||||
with:
|
||||
go_version: '1.20'
|
||||
go_version: '1.21'
|
||||
core_cache_key: ${{ needs.core.outputs.cache_key }}
|
||||
core_cache_path: ${{ needs.core.outputs.cache_path }}
|
||||
|
||||
@ -54,7 +54,7 @@ jobs:
|
||||
needs: core
|
||||
uses: ./.github/workflows/core-integration-test.yml
|
||||
with:
|
||||
go_version: '1.20'
|
||||
go_version: '1.21'
|
||||
core_cache_key: ${{ needs.core.outputs.cache_key }}
|
||||
core_cache_path: ${{ needs.core.outputs.cache_path }}
|
||||
|
||||
@ -62,7 +62,7 @@ jobs:
|
||||
needs: [core, console]
|
||||
uses: ./.github/workflows/lint.yml
|
||||
with:
|
||||
go_version: '1.20'
|
||||
go_version: '1.21'
|
||||
node_version: '18'
|
||||
buf_version: 'latest'
|
||||
go_lint_version: 'v1.53.2'
|
||||
|
@ -8,7 +8,7 @@ issues:
|
||||
run:
|
||||
concurrency: 4
|
||||
timeout: 10m
|
||||
go: '1.19'
|
||||
go: '1.21'
|
||||
skip-dirs:
|
||||
- .artifacts
|
||||
- .backups
|
||||
|
@ -135,7 +135,7 @@ ZITADEL uses [golangci-lint](https://golangci-lint.run) for code quality checks.
|
||||
The commands in this section are tested against the following software versions:
|
||||
|
||||
- [Docker version 20.10.17](https://docs.docker.com/engine/install/)
|
||||
- [Go version 1.20](https://go.dev/doc/install)
|
||||
- [Go version 1.21](https://go.dev/doc/install)
|
||||
- [Delve 1.9.1](https://github.com/go-delve/delve/tree/v1.9.1/Documentation/installation)
|
||||
|
||||
Make some changes to the source code, then run the database locally.
|
||||
|
@ -380,7 +380,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)
|
||||
userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, id.SonyFlakeGenerator(), config.ExternalSecure, login.EndpointResources, login.EndpointExternalLoginCallbackFormPost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -56,8 +56,8 @@
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "6mb",
|
||||
"maximumError": "7mb"
|
||||
"maximumWarning": "8mb",
|
||||
"maximumError": "9mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
|
@ -80,6 +80,11 @@
|
||||
<i class="idp-icon las la-building"></i>
|
||||
Active Directory / LDAP
|
||||
</div>
|
||||
<div class="idp-table-provider-type" *ngSwitchCase="ProviderType.PROVIDER_TYPE_APPLE">
|
||||
<img class="idp-logo apple dark" src="./assets/images/idp/apple-dark.svg" alt="apple" />
|
||||
<img class="idp-logo apple light" src="./assets/images/idp/apple.svg" alt="apple" />
|
||||
Apple
|
||||
</div>
|
||||
<div class="idp-table-provider-type" *ngSwitchDefault>coming soon</div>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -21,6 +21,10 @@
|
||||
width: 28px;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.apple {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
&.dark {
|
||||
display: if($is-dark-theme, block, none);
|
||||
}
|
||||
|
@ -261,6 +261,8 @@ export class IdpTableComponent implements OnInit, OnDestroy {
|
||||
];
|
||||
case ProviderType.PROVIDER_TYPE_GITHUB:
|
||||
return [row.owner === IDPOwnerType.IDP_OWNER_TYPE_SYSTEM ? '/instance' : '/org', 'provider', 'github', row.id];
|
||||
case ProviderType.PROVIDER_TYPE_APPLE:
|
||||
return [row.owner === IDPOwnerType.IDP_OWNER_TYPE_SYSTEM ? '/instance' : '/org', 'provider', 'apple', row.id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -109,6 +109,23 @@
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a
|
||||
class="item card"
|
||||
[routerLink]="
|
||||
serviceType === PolicyComponentServiceType.ADMIN
|
||||
? ['/instance', 'provider', 'apple', 'create']
|
||||
: serviceType === PolicyComponentServiceType.MGMT
|
||||
? ['/org', 'provider', 'apple', 'create']
|
||||
: []
|
||||
"
|
||||
>
|
||||
<img class="idp-logo apple dark" src="./assets/images/idp/apple-dark.svg" alt="Apple" />
|
||||
<img class="idp-logo apple light" src="./assets/images/idp/apple.svg" alt="Apple" />
|
||||
<div class="text-container">
|
||||
<span class="title">Apple</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a
|
||||
class="item card"
|
||||
[routerLink]="
|
||||
|
@ -64,6 +64,10 @@
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
|
||||
&.apple {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
&.dark {
|
||||
display: if($is-dark-theme, block, none);
|
||||
}
|
||||
|
@ -0,0 +1,146 @@
|
||||
<cnsl-create-layout
|
||||
title="{{ id ? ('IDP.DETAIL.TITLE' | translate) : ('IDP.CREATE.TITLE' | translate) }}"
|
||||
(closed)="close()"
|
||||
>
|
||||
<div class="identity-provider-create-content">
|
||||
<div class="title-row">
|
||||
<img class="idp-logo apple dark" src="./assets/images/idp/apple-dark.svg" alt="apple" />
|
||||
<img class="idp-logo apple light" src="./assets/images/idp/apple.svg" alt="apple" />
|
||||
<h1>{{ 'IDP.CREATE.APPLE.TITLE' | translate }}</h1>
|
||||
<mat-spinner diameter="25" *ngIf="loading" color="primary"></mat-spinner>
|
||||
</div>
|
||||
|
||||
<p class="identity-provider-desc cnsl-secondary-text">
|
||||
{{ !provider ? ('IDP.CREATE.APPLE.DESCRIPTION' | translate) : ('IDP.DETAIL.DESCRIPTION' | translate) }}
|
||||
</p>
|
||||
|
||||
<form [formGroup]="form" (ngSubmit)="submitForm()">
|
||||
<div class="identity-provider-content">
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.CLIENTID' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="clientId" />
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.APPLE.TEAMID' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="teamId" />
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.APPLE.KEYID' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="keyId" />
|
||||
</cnsl-form-field>
|
||||
|
||||
<mat-checkbox
|
||||
class="update-secret-checkbox"
|
||||
*ngIf="provider"
|
||||
[(ngModel)]="updatePrivateKey"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
>{{ 'IDP.APPLE.UPDATEPRIVATEKEY' | translate }}</mat-checkbox
|
||||
>
|
||||
|
||||
<ng-container *ngIf="!provider || (provider && updatePrivateKey)">
|
||||
<span class="pk-label cnsl-secondary-text">{{ 'IDP.APPLE.PRIVATEKEY' | translate }}</span>
|
||||
<div class="private-key-wrapper">
|
||||
<ng-container *ngIf="privateKey?.value; else addLogoButton">
|
||||
<i class="las la-file"></i>
|
||||
<button
|
||||
class="dl-btn"
|
||||
mat-icon-button
|
||||
color="warn"
|
||||
(click)="privateKey?.setValue('')"
|
||||
matTooltip="{{ 'ACTIONS.DELETE' | translate }}"
|
||||
>
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</ng-container>
|
||||
<ng-template #addLogoButton>
|
||||
<input
|
||||
#keyFileInput
|
||||
style="display: none"
|
||||
class="file-input"
|
||||
type="file"
|
||||
accept=".p8"
|
||||
(change)="onDropKey($any($event.target).files)"
|
||||
/>
|
||||
<button
|
||||
class="asset-add-btn"
|
||||
mat-icon-button
|
||||
matTooltip="{{ 'IDP.APPLE.UPLOADPRIVATEKEY' | translate }}"
|
||||
(click)="$event.preventDefault(); keyFileInput.click()"
|
||||
>
|
||||
<mat-icon>add</mat-icon>
|
||||
</button>
|
||||
</ng-template>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="identity-provider-optional-h-wrapper">
|
||||
<h2>{{ 'IDP.OPTIONAL' | translate }}</h2>
|
||||
<button (click)="showOptional = !showOptional" type="button" mat-icon-button>
|
||||
<mat-icon *ngIf="showOptional">keyboard_arrow_up</mat-icon
|
||||
><mat-icon *ngIf="!showOptional">keyboard_arrow_down</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="showOptional">
|
||||
<div class="idp-scopes">
|
||||
<div class="flex-line">
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.SCOPESLIST' | translate }}</cnsl-label>
|
||||
|
||||
<input
|
||||
cnslInput
|
||||
[matChipInputFor]="chipScopesList"
|
||||
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
||||
[matChipInputAddOnBlur]="true"
|
||||
(matChipInputTokenEnd)="addScope($event)"
|
||||
/>
|
||||
</cnsl-form-field>
|
||||
<button class="scope-add-button" (click)="addScope($any($event))" mat-icon-button>
|
||||
<mat-icon>add</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<cnsl-form-field class="formfield">
|
||||
<mat-chip-list #chipScopesList aria-label="scope selection">
|
||||
<mat-chip
|
||||
class="chip"
|
||||
*ngFor="let scope of scopesList?.value"
|
||||
selectable="false"
|
||||
removable
|
||||
(removed)="removeScope(scope)"
|
||||
>
|
||||
{{ scope }} <mat-icon matChipRemove>cancel</mat-icon>
|
||||
</mat-chip>
|
||||
</mat-chip-list>
|
||||
</cnsl-form-field>
|
||||
</div>
|
||||
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.NAME' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="name" />
|
||||
<span class="name-hint cnsl-secondary-text" cnslHint>{{ 'IDP.NAMEHINT' | translate }}</span>
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-provider-options
|
||||
[initialOptions]="provider?.config?.options"
|
||||
(optionsChanged)="options = $event"
|
||||
></cnsl-provider-options>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="identity-provider-create-actions">
|
||||
<button
|
||||
color="primary"
|
||||
mat-raised-button
|
||||
class="continue-button"
|
||||
[disabled]="form.invalid || form.disabled"
|
||||
type="submit"
|
||||
>
|
||||
<span *ngIf="id">{{ 'ACTIONS.SAVE' | translate }}</span>
|
||||
<span *ngIf="!id">{{ 'ACTIONS.CREATE' | translate }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</cnsl-create-layout>
|
@ -0,0 +1,24 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { ProviderAppleComponent } from './provider-apple.component';
|
||||
|
||||
describe('ProviderGoogleComponent', () => {
|
||||
let component: ProviderAppleComponent;
|
||||
let fixture: ComponentFixture<ProviderAppleComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ProviderAppleComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProviderAppleComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,306 @@
|
||||
import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
|
||||
import { Location } from '@angular/common';
|
||||
import { Component, Injector, Type } from '@angular/core';
|
||||
import { AbstractControl, FormControl, FormGroup } from '@angular/forms';
|
||||
import { MatLegacyChipInputEvent as MatChipInputEvent } from '@angular/material/legacy-chips';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { take } from 'rxjs';
|
||||
import {
|
||||
AddAppleProviderRequest as AdminAddAppleProviderRequest,
|
||||
GetProviderByIDRequest as AdminGetProviderByIDRequest,
|
||||
UpdateAppleProviderRequest as AdminUpdateAppleProviderRequest,
|
||||
} from 'src/app/proto/generated/zitadel/admin_pb';
|
||||
import { Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb';
|
||||
import {
|
||||
AddAppleProviderRequest as MgmtAddAppleProviderRequest,
|
||||
GetProviderByIDRequest as MgmtGetProviderByIDRequest,
|
||||
UpdateAppleProviderRequest as MgmtUpdateAppleProviderRequest,
|
||||
} from 'src/app/proto/generated/zitadel/management_pb';
|
||||
import { AdminService } from 'src/app/services/admin.service';
|
||||
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
import { requiredValidator } from '../../form-field/validators/validators';
|
||||
|
||||
import { PolicyComponentServiceType } from '../../policies/policy-component-types.enum';
|
||||
|
||||
const MAX_ALLOWED_SIZE = 5 * 1024;
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-provider-apple',
|
||||
templateUrl: './provider-apple.component.html',
|
||||
})
|
||||
export class ProviderAppleComponent {
|
||||
public showOptional: boolean = false;
|
||||
public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true);
|
||||
public id: string | null = '';
|
||||
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
|
||||
private service!: ManagementService | AdminService;
|
||||
|
||||
public readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE];
|
||||
|
||||
public form!: FormGroup;
|
||||
|
||||
public loading: boolean = false;
|
||||
|
||||
public provider?: Provider.AsObject;
|
||||
public updatePrivateKey: boolean = false;
|
||||
|
||||
constructor(
|
||||
private authService: GrpcAuthService,
|
||||
private route: ActivatedRoute,
|
||||
private toast: ToastService,
|
||||
private injector: Injector,
|
||||
private _location: Location,
|
||||
private breadcrumbService: BreadcrumbService,
|
||||
) {
|
||||
this.form = new FormGroup({
|
||||
name: new FormControl('', []),
|
||||
clientId: new FormControl('', [requiredValidator]),
|
||||
teamId: new FormControl('', [requiredValidator]),
|
||||
keyId: new FormControl('', [requiredValidator]),
|
||||
privateKey: new FormControl('', [requiredValidator]),
|
||||
scopesList: new FormControl(['name', 'email'], []),
|
||||
});
|
||||
|
||||
this.authService
|
||||
.isAllowed(
|
||||
this.serviceType === PolicyComponentServiceType.ADMIN
|
||||
? ['iam.idp.write']
|
||||
: this.serviceType === PolicyComponentServiceType.MGMT
|
||||
? ['org.idp.write']
|
||||
: [],
|
||||
)
|
||||
.pipe(take(1))
|
||||
.subscribe((allowed) => {
|
||||
if (allowed) {
|
||||
this.form.enable();
|
||||
} else {
|
||||
this.form.disable();
|
||||
}
|
||||
});
|
||||
|
||||
this.route.data.pipe(take(1)).subscribe((data) => {
|
||||
this.serviceType = data['serviceType'];
|
||||
|
||||
switch (this.serviceType) {
|
||||
case PolicyComponentServiceType.MGMT:
|
||||
this.service = this.injector.get(ManagementService as Type<ManagementService>);
|
||||
|
||||
const bread: Breadcrumb = {
|
||||
type: BreadcrumbType.ORG,
|
||||
routerLink: ['/org'],
|
||||
};
|
||||
|
||||
this.breadcrumbService.setBreadcrumb([bread]);
|
||||
break;
|
||||
case PolicyComponentServiceType.ADMIN:
|
||||
this.service = this.injector.get(AdminService as Type<AdminService>);
|
||||
|
||||
const iamBread = new Breadcrumb({
|
||||
type: BreadcrumbType.ORG,
|
||||
name: 'Instance',
|
||||
routerLink: ['/instance'],
|
||||
});
|
||||
this.breadcrumbService.setBreadcrumb([iamBread]);
|
||||
break;
|
||||
}
|
||||
|
||||
this.id = this.route.snapshot.paramMap.get('id');
|
||||
if (this.id) {
|
||||
this.privateKey?.setValidators([]);
|
||||
this.getData(this.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getData(id: string): void {
|
||||
const req =
|
||||
this.serviceType === PolicyComponentServiceType.ADMIN
|
||||
? new AdminGetProviderByIDRequest()
|
||||
: new MgmtGetProviderByIDRequest();
|
||||
req.setId(id);
|
||||
this.service
|
||||
.getProviderByID(req)
|
||||
.then((resp) => {
|
||||
this.provider = resp.idp;
|
||||
this.loading = false;
|
||||
if (this.provider?.config?.apple) {
|
||||
this.form.patchValue(this.provider.config.apple);
|
||||
this.name?.setValue(this.provider.name);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
public submitForm(): void {
|
||||
this.provider ? this.updateAppleProvider() : this.addAppleProvider();
|
||||
}
|
||||
|
||||
public addAppleProvider(): void {
|
||||
const req =
|
||||
this.serviceType === PolicyComponentServiceType.MGMT
|
||||
? new MgmtAddAppleProviderRequest()
|
||||
: new AdminAddAppleProviderRequest();
|
||||
|
||||
req.setName(this.name?.value);
|
||||
req.setClientId(this.clientId?.value);
|
||||
req.setTeamId(this.teamId?.value);
|
||||
req.setKeyId(this.keyId?.value);
|
||||
req.setPrivateKey(this.privateKey?.value);
|
||||
req.setScopesList(this.scopesList?.value);
|
||||
req.setProviderOptions(this.options);
|
||||
|
||||
this.loading = true;
|
||||
this.service
|
||||
.addAppleProvider(req)
|
||||
.then((idp) => {
|
||||
setTimeout(() => {
|
||||
this.loading = false;
|
||||
this.close();
|
||||
}, 2000);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
public updateAppleProvider(): void {
|
||||
if (this.provider) {
|
||||
if (this.serviceType === PolicyComponentServiceType.MGMT) {
|
||||
const req = new MgmtUpdateAppleProviderRequest();
|
||||
req.setId(this.provider.id);
|
||||
req.setName(this.name?.value);
|
||||
req.setClientId(this.clientId?.value);
|
||||
req.setTeamId(this.teamId?.value);
|
||||
req.setKeyId(this.keyId?.value);
|
||||
req.setScopesList(this.scopesList?.value);
|
||||
req.setProviderOptions(this.options);
|
||||
|
||||
if (this.updatePrivateKey) {
|
||||
req.setPrivateKey(this.privateKey?.value);
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
(this.service as ManagementService)
|
||||
.updateAppleProvider(req)
|
||||
.then((idp) => {
|
||||
setTimeout(() => {
|
||||
this.loading = false;
|
||||
this.close();
|
||||
}, 2000);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
this.loading = false;
|
||||
});
|
||||
} else if (PolicyComponentServiceType.ADMIN) {
|
||||
const req = new AdminUpdateAppleProviderRequest();
|
||||
req.setId(this.provider.id);
|
||||
req.setName(this.name?.value);
|
||||
req.setClientId(this.clientId?.value);
|
||||
req.setTeamId(this.teamId?.value);
|
||||
req.setKeyId(this.keyId?.value);
|
||||
req.setScopesList(this.scopesList?.value);
|
||||
req.setProviderOptions(this.options);
|
||||
|
||||
if (this.updatePrivateKey) {
|
||||
req.setPrivateKey(this.privateKey?.value);
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
(this.service as AdminService)
|
||||
.updateAppleProvider(req)
|
||||
.then((idp) => {
|
||||
setTimeout(() => {
|
||||
this.loading = false;
|
||||
this.close();
|
||||
}, 2000);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.loading = false;
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this._location.back();
|
||||
}
|
||||
|
||||
public onDropKey(filelist: FileList): void {
|
||||
const file = filelist.item(0);
|
||||
if (file) {
|
||||
if (file.size > MAX_ALLOWED_SIZE) {
|
||||
this.toast.showInfo('IDP.APPLE.KEYMAXSIZEEXCEEDED', true);
|
||||
} else {
|
||||
this.privateKey?.setValue('');
|
||||
const reader = new FileReader();
|
||||
reader.onload = ((aXML) => {
|
||||
return (e) => {
|
||||
const keyBase64 = e.target?.result;
|
||||
if (keyBase64 && typeof keyBase64 === 'string') {
|
||||
const cropped = keyBase64.replace('data:application/octet-stream;base64,', '');
|
||||
this.privateKey?.setValue(cropped);
|
||||
}
|
||||
};
|
||||
})(file);
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public addScope(event: MatChipInputEvent): void {
|
||||
const input = event.chipInput?.inputElement;
|
||||
const value = event.value.trim();
|
||||
|
||||
if (value !== '') {
|
||||
if (this.scopesList?.value) {
|
||||
this.scopesList.value.push(value);
|
||||
if (input) {
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public removeScope(uri: string): void {
|
||||
if (this.scopesList?.value) {
|
||||
const index = this.scopesList.value.indexOf(uri);
|
||||
|
||||
if (index !== undefined && index >= 0) {
|
||||
this.scopesList.value.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get name(): AbstractControl | null {
|
||||
return this.form.get('name');
|
||||
}
|
||||
|
||||
public get clientId(): AbstractControl | null {
|
||||
return this.form.get('clientId');
|
||||
}
|
||||
|
||||
public get teamId(): AbstractControl | null {
|
||||
return this.form.get('teamId');
|
||||
}
|
||||
|
||||
public get keyId(): AbstractControl | null {
|
||||
return this.form.get('keyId');
|
||||
}
|
||||
|
||||
public get privateKey(): AbstractControl | null {
|
||||
return this.form.get('privateKey');
|
||||
}
|
||||
|
||||
public get scopesList(): AbstractControl | null {
|
||||
return this.form.get('scopesList');
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { ProviderType } from 'src/app/proto/generated/zitadel/idp_pb';
|
||||
import { ProviderAppleComponent } from './provider-apple/provider-apple.component';
|
||||
import { ProviderAzureADComponent } from './provider-azure-ad/provider-azure-ad.component';
|
||||
import { ProviderGithubESComponent } from './provider-github-es/provider-github-es.component';
|
||||
import { ProviderGithubComponent } from './provider-github/provider-github.component';
|
||||
@ -27,6 +28,7 @@ const typeMap = {
|
||||
[ProviderType.PROVIDER_TYPE_OAUTH]: { path: 'oauth', component: ProviderOAuthComponent },
|
||||
[ProviderType.PROVIDER_TYPE_OIDC]: { path: 'oidc', component: ProviderOIDCComponent },
|
||||
[ProviderType.PROVIDER_TYPE_LDAP]: { path: 'ldap', component: ProviderLDAPComponent },
|
||||
[ProviderType.PROVIDER_TYPE_APPLE]: { path: 'apple', component: ProviderAppleComponent },
|
||||
};
|
||||
|
||||
const routes: Routes = Object.entries(typeMap).map(([key, value]) => {
|
||||
|
@ -17,6 +17,7 @@ import { InfoSectionModule } from '../info-section/info-section.module';
|
||||
import { ProviderOptionsModule } from '../provider-options/provider-options.module';
|
||||
import { StringListModule } from '../string-list/string-list.module';
|
||||
import { LDAPAttributesComponent } from './ldap-attributes/ldap-attributes.component';
|
||||
import { ProviderAppleComponent } from './provider-apple/provider-apple.component';
|
||||
import { ProviderAzureADComponent } from './provider-azure-ad/provider-azure-ad.component';
|
||||
import { ProviderGithubESComponent } from './provider-github-es/provider-github-es.component';
|
||||
import { ProviderGithubComponent } from './provider-github/provider-github.component';
|
||||
@ -43,6 +44,7 @@ import { ProvidersRoutingModule } from './providers-routing.module';
|
||||
ProviderOIDCComponent,
|
||||
ProviderOAuthComponent,
|
||||
ProviderLDAPComponent,
|
||||
ProviderAppleComponent,
|
||||
],
|
||||
imports: [
|
||||
ProvidersRoutingModule,
|
||||
|
@ -3,6 +3,7 @@
|
||||
@mixin identity-provider-theme($theme) {
|
||||
$is-dark-theme: map-get($theme, is-dark);
|
||||
$background: map-get($theme, background);
|
||||
$foreground: map-get($theme, foreground);
|
||||
|
||||
.identity-provider-desc {
|
||||
font-size: 14px;
|
||||
@ -19,6 +20,10 @@
|
||||
margin-right: 1rem;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.apple {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
&.dark {
|
||||
display: if($is-dark-theme, block, none);
|
||||
}
|
||||
@ -74,6 +79,58 @@
|
||||
}
|
||||
}
|
||||
|
||||
.pk-label {
|
||||
font-size: 12px;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.private-key-wrapper {
|
||||
position: relative;
|
||||
height: 70px;
|
||||
width: 70px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
border: 1px solid map-get($foreground, divider);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.dl-btn {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
visibility: hidden;
|
||||
transform: translateX(50%) translateY(-50%);
|
||||
}
|
||||
|
||||
img {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&.icon {
|
||||
border-radius: 50%;
|
||||
|
||||
img {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.dl-btn {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.string-list-component-wrapper {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ import {
|
||||
ActivateLabelPolicyResponse,
|
||||
ActivateSMSProviderRequest,
|
||||
ActivateSMSProviderResponse,
|
||||
AddAppleProviderRequest,
|
||||
AddAppleProviderResponse,
|
||||
AddAzureADProviderRequest,
|
||||
AddAzureADProviderResponse,
|
||||
AddCustomDomainPolicyRequest,
|
||||
@ -214,6 +216,8 @@ import {
|
||||
SetSecurityPolicyResponse,
|
||||
SetUpOrgRequest,
|
||||
SetUpOrgResponse,
|
||||
UpdateAppleProviderRequest,
|
||||
UpdateAppleProviderResponse,
|
||||
UpdateAzureADProviderRequest,
|
||||
UpdateAzureADProviderResponse,
|
||||
UpdateCustomDomainPolicyRequest,
|
||||
@ -1173,6 +1177,14 @@ export class AdminService {
|
||||
return this.grpcService.admin.updateGitHubEnterpriseServerProvider(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public addAppleProvider(req: AddAppleProviderRequest): Promise<AddAppleProviderResponse.AsObject> {
|
||||
return this.grpcService.admin.addAppleProvider(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public updateAppleProvider(req: UpdateAppleProviderRequest): Promise<UpdateAppleProviderResponse.AsObject> {
|
||||
return this.grpcService.admin.updateAppleProvider(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public deleteProvider(id: string): Promise<DeleteProviderResponse.AsObject> {
|
||||
const req = new DeleteProviderRequest();
|
||||
req.setId(id);
|
||||
|
@ -15,6 +15,8 @@ import {
|
||||
AddAPIAppResponse,
|
||||
AddAppKeyRequest,
|
||||
AddAppKeyResponse,
|
||||
AddAppleProviderRequest,
|
||||
AddAppleProviderResponse,
|
||||
AddAzureADProviderRequest,
|
||||
AddAzureADProviderResponse,
|
||||
AddCustomLabelPolicyRequest,
|
||||
@ -441,6 +443,8 @@ import {
|
||||
UpdateActionResponse,
|
||||
UpdateAPIAppConfigRequest,
|
||||
UpdateAPIAppConfigResponse,
|
||||
UpdateAppleProviderRequest,
|
||||
UpdateAppleProviderResponse,
|
||||
UpdateAppRequest,
|
||||
UpdateAppResponse,
|
||||
UpdateAzureADProviderRequest,
|
||||
@ -1041,6 +1045,14 @@ export class ManagementService {
|
||||
return this.grpcService.mgmt.updateGitHubEnterpriseServerProvider(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public addAppleProvider(req: AddAppleProviderRequest): Promise<AddAppleProviderResponse.AsObject> {
|
||||
return this.grpcService.mgmt.addAppleProvider(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public updateAppleProvider(req: UpdateAppleProviderRequest): Promise<UpdateAppleProviderResponse.AsObject> {
|
||||
return this.grpcService.mgmt.updateAppleProvider(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public deleteProvider(id: string): Promise<DeleteProviderResponse.AsObject> {
|
||||
const req = new DeleteProviderRequest();
|
||||
req.setId(id);
|
||||
|
@ -1706,6 +1706,10 @@
|
||||
"LDAP": {
|
||||
"TITLE": "Active Directory / LDAP",
|
||||
"DESCRIPTION": "Enter the credentials for your LDAP Provider"
|
||||
},
|
||||
"APPLE": {
|
||||
"TITLE": "Sign in with Apple",
|
||||
"DESCRIPTION": "Enter the credentials for your Apple Provider"
|
||||
}
|
||||
},
|
||||
"DETAIL": {
|
||||
@ -1819,6 +1823,14 @@
|
||||
"JWTENDPOINT": "JWT Endpoint",
|
||||
"JWTKEYSENDPOINT": "JWT Keys Endpoint"
|
||||
},
|
||||
"APPLE": {
|
||||
"TEAMID": "Team ID",
|
||||
"KEYID": "Key ID",
|
||||
"PRIVATEKEY": "Private Key",
|
||||
"UPDATEPRIVATEKEY": "Update Private Key",
|
||||
"UPLOADPRIVATEKEY": "Upload Private Key",
|
||||
"KEYMAXSIZEEXCEEDED": "Maximum size of 5kB exceeded."
|
||||
},
|
||||
"TOAST": {
|
||||
"SAVED": "Successfully saved.",
|
||||
"REACTIVATED": "Idp reactivated.",
|
||||
|
10
console/src/assets/images/idp/apple-dark.svg
Executable file
10
console/src/assets/images/idp/apple-dark.svg
Executable file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="56px" height="56px" viewBox="18 15.5 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 61 (89581) - https://sketch.com -->
|
||||
<title>White Logo Square </title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="White-Logo-Square-" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<rect id="Rectangle" fill="" x="6" y="6" width="44" height="44"></rect>
|
||||
<path d="M28.2226562,20.3846154 C29.0546875,20.3846154 30.0976562,19.8048315 30.71875,19.0317864 C31.28125,18.3312142 31.6914062,17.352829 31.6914062,16.3744437 C31.6914062,16.2415766 31.6796875,16.1087095 31.65625,16 C30.7304687,16.0362365 29.6171875,16.640178 28.9492187,17.4494596 C28.421875,18.06548 27.9414062,19.0317864 27.9414062,20.0222505 C27.9414062,20.1671964 27.9648438,20.3121424 27.9765625,20.3604577 C28.0351562,20.3725366 28.1289062,20.3846154 28.2226562,20.3846154 Z M25.2929688,35 C26.4296875,35 26.9335938,34.214876 28.3515625,34.214876 C29.7929688,34.214876 30.109375,34.9758423 31.375,34.9758423 C32.6171875,34.9758423 33.4492188,33.792117 34.234375,32.6325493 C35.1132812,31.3038779 35.4765625,29.9993643 35.5,29.9389701 C35.4179688,29.9148125 33.0390625,28.9122695 33.0390625,26.0979021 C33.0390625,23.6579784 34.9140625,22.5588048 35.0195312,22.474253 C33.7773438,20.6382708 31.890625,20.5899555 31.375,20.5899555 C29.9804688,20.5899555 28.84375,21.4596313 28.1289062,21.4596313 C27.3554688,21.4596313 26.3359375,20.6382708 25.1289062,20.6382708 C22.8320312,20.6382708 20.5,22.5950413 20.5,26.2911634 C20.5,28.5861411 21.3671875,31.013986 22.4335938,32.5842339 C23.3476562,33.9129053 24.1445312,35 25.2929688,35 Z" id="" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
10
console/src/assets/images/idp/apple.svg
Executable file
10
console/src/assets/images/idp/apple.svg
Executable file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="56px" height="56px" viewBox="18 15.5 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 61 (89581) - https://sketch.com -->
|
||||
<title>Black Logo Square</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="Black-Logo-Square" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<rect id="Rectangle" fill="" x="6" y="6" width="44" height="44"></rect>
|
||||
<path d="M28.2226562,20.3846154 C29.0546875,20.3846154 30.0976562,19.8048315 30.71875,19.0317864 C31.28125,18.3312142 31.6914062,17.352829 31.6914062,16.3744437 C31.6914062,16.2415766 31.6796875,16.1087095 31.65625,16 C30.7304687,16.0362365 29.6171875,16.640178 28.9492187,17.4494596 C28.421875,18.06548 27.9414062,19.0317864 27.9414062,20.0222505 C27.9414062,20.1671964 27.9648438,20.3121424 27.9765625,20.3604577 C28.0351562,20.3725366 28.1289062,20.3846154 28.2226562,20.3846154 Z M25.2929688,35 C26.4296875,35 26.9335938,34.214876 28.3515625,34.214876 C29.7929688,34.214876 30.109375,34.9758423 31.375,34.9758423 C32.6171875,34.9758423 33.4492188,33.792117 34.234375,32.6325493 C35.1132812,31.3038779 35.4765625,29.9993643 35.5,29.9389701 C35.4179688,29.9148125 33.0390625,28.9122695 33.0390625,26.0979021 C33.0390625,23.6579784 34.9140625,22.5588048 35.0195312,22.474253 C33.7773438,20.6382708 31.890625,20.5899555 31.375,20.5899555 C29.9804688,20.5899555 28.84375,21.4596313 28.1289062,21.4596313 C27.3554688,21.4596313 26.3359375,20.6382708 25.1289062,20.6382708 C22.8320312,20.6382708 20.5,22.5950413 20.5,26.2911634 C20.5,28.5861411 21.3671875,31.013986 22.4335938,32.5842339 C23.3476562,33.9129053 24.1445312,35 25.2929688,35 Z" id="" fill="#000000" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
2
go.mod
2
go.mod
@ -1,6 +1,6 @@
|
||||
module github.com/zitadel/zitadel
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
cloud.google.com/go/storage v1.30.1
|
||||
|
@ -405,6 +405,27 @@ func (s *Server) UpdateLDAPProvider(ctx context.Context, req *admin_pb.UpdateLDA
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) AddAppleProvider(ctx context.Context, req *admin_pb.AddAppleProviderRequest) (*admin_pb.AddAppleProviderResponse, error) {
|
||||
id, details, err := s.command.AddInstanceAppleProvider(ctx, addAppleProviderToCommand(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.AddAppleProviderResponse{
|
||||
Id: id,
|
||||
Details: object_pb.DomainToAddDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) UpdateAppleProvider(ctx context.Context, req *admin_pb.UpdateAppleProviderRequest) (*admin_pb.UpdateAppleProviderResponse, error) {
|
||||
details, err := s.command.UpdateInstanceAppleProvider(ctx, req.Id, updateAppleProviderToCommand(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.UpdateAppleProviderResponse{
|
||||
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 {
|
||||
|
@ -440,3 +440,27 @@ func updateLDAPProviderToCommand(req *admin_pb.UpdateLDAPProviderRequest) comman
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
}
|
||||
}
|
||||
|
||||
func addAppleProviderToCommand(req *admin_pb.AddAppleProviderRequest) command.AppleProvider {
|
||||
return command.AppleProvider{
|
||||
Name: req.Name,
|
||||
ClientID: req.ClientId,
|
||||
TeamID: req.TeamId,
|
||||
KeyID: req.KeyId,
|
||||
PrivateKey: req.PrivateKey,
|
||||
Scopes: req.Scopes,
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
}
|
||||
}
|
||||
|
||||
func updateAppleProviderToCommand(req *admin_pb.UpdateAppleProviderRequest) command.AppleProvider {
|
||||
return command.AppleProvider{
|
||||
Name: req.Name,
|
||||
ClientID: req.ClientId,
|
||||
TeamID: req.TeamId,
|
||||
KeyID: req.KeyId,
|
||||
PrivateKey: req.PrivateKey,
|
||||
Scopes: req.Scopes,
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
}
|
||||
}
|
||||
|
@ -414,6 +414,8 @@ func providerTypeToPb(idpType domain.IDPType) idp_pb.ProviderType {
|
||||
return idp_pb.ProviderType_PROVIDER_TYPE_GITLAB_SELF_HOSTED
|
||||
case domain.IDPTypeGoogle:
|
||||
return idp_pb.ProviderType_PROVIDER_TYPE_GOOGLE
|
||||
case domain.IDPTypeApple:
|
||||
return idp_pb.ProviderType_PROVIDER_TYPE_APPLE
|
||||
case domain.IDPTypeUnspecified:
|
||||
return idp_pb.ProviderType_PROVIDER_TYPE_UNSPECIFIED
|
||||
default:
|
||||
@ -470,6 +472,10 @@ func configToPb(config *query.IDPTemplate) *idp_pb.ProviderConfig {
|
||||
ldapConfigToPb(providerConfig, config.LDAPIDPTemplate)
|
||||
return providerConfig
|
||||
}
|
||||
if config.AppleIDPTemplate != nil {
|
||||
appleConfigToPb(providerConfig, config.AppleIDPTemplate)
|
||||
return providerConfig
|
||||
}
|
||||
return providerConfig
|
||||
}
|
||||
|
||||
@ -620,3 +626,14 @@ func ldapAttributesToPb(attributes idp.LDAPAttributes) *idp_pb.LDAPAttributes {
|
||||
ProfileAttribute: attributes.ProfileAttribute,
|
||||
}
|
||||
}
|
||||
|
||||
func appleConfigToPb(providerConfig *idp_pb.ProviderConfig, template *query.AppleIDPTemplate) {
|
||||
providerConfig.Config = &idp_pb.ProviderConfig_Apple{
|
||||
Apple: &idp_pb.AppleConfig{
|
||||
ClientId: template.ClientID,
|
||||
TeamId: template.TeamID,
|
||||
KeyId: template.KeyID,
|
||||
Scopes: template.Scopes,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -397,6 +397,27 @@ func (s *Server) UpdateLDAPProvider(ctx context.Context, req *mgmt_pb.UpdateLDAP
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) AddAppleProvider(ctx context.Context, req *mgmt_pb.AddAppleProviderRequest) (*mgmt_pb.AddAppleProviderResponse, error) {
|
||||
id, details, err := s.command.AddOrgAppleProvider(ctx, authz.GetCtxData(ctx).OrgID, addAppleProviderToCommand(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.AddAppleProviderResponse{
|
||||
Id: id,
|
||||
Details: object_pb.DomainToAddDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) UpdateAppleProvider(ctx context.Context, req *mgmt_pb.UpdateAppleProviderRequest) (*mgmt_pb.UpdateAppleProviderResponse, error) {
|
||||
details, err := s.command.UpdateOrgAppleProvider(ctx, authz.GetCtxData(ctx).OrgID, req.Id, updateAppleProviderToCommand(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.UpdateAppleProviderResponse{
|
||||
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 {
|
||||
|
@ -457,3 +457,27 @@ func updateLDAPProviderToCommand(req *mgmt_pb.UpdateLDAPProviderRequest) command
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
}
|
||||
}
|
||||
|
||||
func addAppleProviderToCommand(req *mgmt_pb.AddAppleProviderRequest) command.AppleProvider {
|
||||
return command.AppleProvider{
|
||||
Name: req.Name,
|
||||
ClientID: req.ClientId,
|
||||
TeamID: req.TeamId,
|
||||
KeyID: req.KeyId,
|
||||
PrivateKey: req.PrivateKey,
|
||||
Scopes: req.Scopes,
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
}
|
||||
}
|
||||
|
||||
func updateAppleProviderToCommand(req *mgmt_pb.UpdateAppleProviderRequest) command.AppleProvider {
|
||||
return command.AppleProvider{
|
||||
Name: req.Name,
|
||||
ClientID: req.ClientId,
|
||||
TeamID: req.TeamId,
|
||||
KeyID: req.KeyId,
|
||||
PrivateKey: req.PrivateKey,
|
||||
Scopes: req.Scopes,
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
z_errs "github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/form"
|
||||
"github.com/zitadel/zitadel/internal/idp"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/apple"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/github"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/gitlab"
|
||||
@ -52,6 +53,9 @@ type externalIDPCallbackData struct {
|
||||
Code string `schema:"code"`
|
||||
Error string `schema:"error"`
|
||||
ErrorDescription string `schema:"error_description"`
|
||||
|
||||
// Apple returns a user on first registration
|
||||
User string `schema:"user"`
|
||||
}
|
||||
|
||||
// CallbackURL generates the instance specific URL to the IDP callback handler
|
||||
@ -115,7 +119,7 @@ func (h *Handler) handleCallback(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
idpUser, idpSession, err := h.fetchIDPUser(ctx, provider, data.Code)
|
||||
idpUser, idpSession, err := h.fetchIDPUser(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")
|
||||
@ -214,7 +218,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) (user idp.User, idpTokens idp.Session, err error) {
|
||||
func (h *Handler) fetchIDPUser(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:
|
||||
@ -229,6 +233,8 @@ func (h *Handler) fetchIDPUser(ctx context.Context, identityProvider idp.Provide
|
||||
session = &openid.Session{Provider: provider.Provider, Code: code}
|
||||
case *google.Provider:
|
||||
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:
|
||||
return nil, nil, z_errs.ThrowInvalidArgument(nil, "IDP-52jmn", "Errors.ExternalIDP.IDPTypeNotImplemented")
|
||||
default:
|
||||
|
@ -3,9 +3,8 @@ package login
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
|
||||
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/idp"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/apple"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/github"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/gitlab"
|
||||
@ -41,6 +42,9 @@ type externalIDPData struct {
|
||||
type externalIDPCallbackData struct {
|
||||
State string `schema:"state"`
|
||||
Code string `schema:"code"`
|
||||
|
||||
// Apple returns a user on first registration
|
||||
User string `schema:"user"`
|
||||
}
|
||||
|
||||
type externalNotFoundOptionFormData struct {
|
||||
@ -159,6 +163,8 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai
|
||||
provider, err = l.gitlabSelfHostedProvider(r.Context(), identityProvider)
|
||||
case domain.IDPTypeGoogle:
|
||||
provider, err = l.googleProvider(r.Context(), identityProvider)
|
||||
case domain.IDPTypeApple:
|
||||
provider, err = l.appleProvider(r.Context(), identityProvider)
|
||||
case domain.IDPTypeLDAP:
|
||||
provider, err = l.ldapProvider(r.Context(), identityProvider)
|
||||
case domain.IDPTypeUnspecified:
|
||||
@ -180,6 +186,18 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai
|
||||
http.Redirect(w, r, session.GetAuthURL(), http.StatusFound)
|
||||
}
|
||||
|
||||
// handleExternalLoginCallbackForm handles the callback from a IDP with form_post.
|
||||
// It will redirect to the "normal" callback endpoint with the form data as query parameter.
|
||||
// This way cookies will be handled correctly (same site = lax).
|
||||
func (l *Login) handleExternalLoginCallbackForm(w http.ResponseWriter, r *http.Request) {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
l.renderLogin(w, r, nil, err)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, HandlerPrefix+EndpointExternalLoginCallback+"?"+r.Form.Encode(), 302)
|
||||
}
|
||||
|
||||
// handleExternalLoginCallback handles the callback from a IDP
|
||||
// and tries to extract the user with the provided data
|
||||
func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Request) {
|
||||
@ -259,6 +277,13 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque
|
||||
return
|
||||
}
|
||||
session = &openid.Session{Provider: provider.(*google.Provider).Provider, Code: data.Code}
|
||||
case domain.IDPTypeApple:
|
||||
provider, err = l.appleProvider(r.Context(), identityProvider)
|
||||
if err != nil {
|
||||
l.externalAuthFailed(w, r, authReq, nil, nil, err)
|
||||
return
|
||||
}
|
||||
session = &apple.Session{Session: &openid.Session{Provider: provider.(*apple.Provider).Provider, Code: data.Code}, UserFormValue: data.User}
|
||||
case domain.IDPTypeJWT,
|
||||
domain.IDPTypeLDAP,
|
||||
domain.IDPTypeUnspecified:
|
||||
@ -936,6 +961,21 @@ func (l *Login) gitlabSelfHostedProvider(ctx context.Context, identityProvider *
|
||||
)
|
||||
}
|
||||
|
||||
func (l *Login) appleProvider(ctx context.Context, identityProvider *query.IDPTemplate) (*apple.Provider, error) {
|
||||
privateKey, err := crypto.Decrypt(identityProvider.AppleIDPTemplate.PrivateKey, l.idpConfigAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return apple.New(
|
||||
identityProvider.AppleIDPTemplate.ClientID,
|
||||
identityProvider.AppleIDPTemplate.TeamID,
|
||||
identityProvider.AppleIDPTemplate.KeyID,
|
||||
l.baseURL(ctx)+EndpointExternalLoginCallbackFormPost,
|
||||
privateKey,
|
||||
identityProvider.AppleIDPTemplate.Scopes,
|
||||
)
|
||||
}
|
||||
|
||||
func (l *Login) appendUserGrants(ctx context.Context, userGrants []*domain.UserGrant, resourceOwner string) error {
|
||||
if len(userGrants) == 0 {
|
||||
return nil
|
||||
@ -971,6 +1011,8 @@ func tokens(session idp.Session) *oidc.Tokens[*oidc.IDTokenClaims] {
|
||||
return s.Tokens
|
||||
case *azuread.Session:
|
||||
return s.Tokens
|
||||
case *apple.Session:
|
||||
return s.Tokens
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -120,6 +120,12 @@ func createCSRFInterceptor(cookieName string, csrfCookieKey []byte, externalSecu
|
||||
handler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
// 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 {
|
||||
handler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
csrf.Protect(csrfCookieKey,
|
||||
csrf.Secure(externalSecure),
|
||||
csrf.CookieName(http_utils.SetCookiePrefix(cookieName, "", path, externalSecure)),
|
||||
|
@ -7,44 +7,45 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
EndpointRoot = "/"
|
||||
EndpointHealthz = "/healthz"
|
||||
EndpointReadiness = "/ready"
|
||||
EndpointLogin = "/login"
|
||||
EndpointExternalLogin = "/login/externalidp"
|
||||
EndpointExternalLoginCallback = "/login/externalidp/callback"
|
||||
EndpointJWTAuthorize = "/login/jwt/authorize"
|
||||
EndpointJWTCallback = "/login/jwt/callback"
|
||||
EndpointLDAPLogin = "/login/ldap"
|
||||
EndpointLDAPCallback = "/login/ldap/callback"
|
||||
EndpointPasswordlessLogin = "/login/passwordless"
|
||||
EndpointPasswordlessRegistration = "/login/passwordless/init"
|
||||
EndpointPasswordlessPrompt = "/login/passwordless/prompt"
|
||||
EndpointLoginName = "/loginname"
|
||||
EndpointUserSelection = "/userselection"
|
||||
EndpointChangeUsername = "/username/change"
|
||||
EndpointPassword = "/password"
|
||||
EndpointInitPassword = "/password/init"
|
||||
EndpointChangePassword = "/password/change"
|
||||
EndpointPasswordReset = "/password/reset"
|
||||
EndpointInitUser = "/user/init"
|
||||
EndpointMFAVerify = "/mfa/verify"
|
||||
EndpointMFAPrompt = "/mfa/prompt"
|
||||
EndpointMFAInitVerify = "/mfa/init/verify"
|
||||
EndpointMFASMSInitVerify = "/mfa/init/sms/verify"
|
||||
EndpointMFAOTPVerify = "/mfa/otp/verify"
|
||||
EndpointMFAInitU2FVerify = "/mfa/init/u2f/verify"
|
||||
EndpointU2FVerification = "/mfa/u2f/verify"
|
||||
EndpointMailVerification = "/mail/verification"
|
||||
EndpointMailVerified = "/mail/verified"
|
||||
EndpointRegisterOption = "/register/option"
|
||||
EndpointRegister = "/register"
|
||||
EndpointExternalRegister = "/register/externalidp"
|
||||
EndpointExternalRegisterCallback = "/register/externalidp/callback"
|
||||
EndpointRegisterOrg = "/register/org"
|
||||
EndpointLogoutDone = "/logout/done"
|
||||
EndpointLoginSuccess = "/login/success"
|
||||
EndpointExternalNotFoundOption = "/externaluser/option"
|
||||
EndpointRoot = "/"
|
||||
EndpointHealthz = "/healthz"
|
||||
EndpointReadiness = "/ready"
|
||||
EndpointLogin = "/login"
|
||||
EndpointExternalLogin = "/login/externalidp"
|
||||
EndpointExternalLoginCallback = "/login/externalidp/callback"
|
||||
EndpointExternalLoginCallbackFormPost = "/login/externalidp/callback/form"
|
||||
EndpointJWTAuthorize = "/login/jwt/authorize"
|
||||
EndpointJWTCallback = "/login/jwt/callback"
|
||||
EndpointLDAPLogin = "/login/ldap"
|
||||
EndpointLDAPCallback = "/login/ldap/callback"
|
||||
EndpointPasswordlessLogin = "/login/passwordless"
|
||||
EndpointPasswordlessRegistration = "/login/passwordless/init"
|
||||
EndpointPasswordlessPrompt = "/login/passwordless/prompt"
|
||||
EndpointLoginName = "/loginname"
|
||||
EndpointUserSelection = "/userselection"
|
||||
EndpointChangeUsername = "/username/change"
|
||||
EndpointPassword = "/password"
|
||||
EndpointInitPassword = "/password/init"
|
||||
EndpointChangePassword = "/password/change"
|
||||
EndpointPasswordReset = "/password/reset"
|
||||
EndpointInitUser = "/user/init"
|
||||
EndpointMFAVerify = "/mfa/verify"
|
||||
EndpointMFAPrompt = "/mfa/prompt"
|
||||
EndpointMFAInitVerify = "/mfa/init/verify"
|
||||
EndpointMFASMSInitVerify = "/mfa/init/sms/verify"
|
||||
EndpointMFAOTPVerify = "/mfa/otp/verify"
|
||||
EndpointMFAInitU2FVerify = "/mfa/init/u2f/verify"
|
||||
EndpointU2FVerification = "/mfa/u2f/verify"
|
||||
EndpointMailVerification = "/mail/verification"
|
||||
EndpointMailVerified = "/mail/verified"
|
||||
EndpointRegisterOption = "/register/option"
|
||||
EndpointRegister = "/register"
|
||||
EndpointExternalRegister = "/register/externalidp"
|
||||
EndpointExternalRegisterCallback = "/register/externalidp/callback"
|
||||
EndpointRegisterOrg = "/register/org"
|
||||
EndpointLogoutDone = "/logout/done"
|
||||
EndpointLoginSuccess = "/login/success"
|
||||
EndpointExternalNotFoundOption = "/externaluser/option"
|
||||
|
||||
EndpointResources = "/resources"
|
||||
EndpointDynamicResources = "/resources/dynamic"
|
||||
@ -71,6 +72,7 @@ func CreateRouter(login *Login, staticDir http.FileSystem, interceptors ...mux.M
|
||||
router.HandleFunc(EndpointLogin, login.handleLogin).Methods(http.MethodGet, http.MethodPost)
|
||||
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(EndpointJWTAuthorize, login.handleJWTRequest).Methods(http.MethodGet)
|
||||
router.HandleFunc(EndpointJWTCallback, login.handleJWTCallback).Methods(http.MethodGet)
|
||||
router.HandleFunc(EndpointPasswordlessLogin, login.handlePasswordlessVerification).Methods(http.MethodPost)
|
||||
|
@ -360,6 +360,7 @@ Footer:
|
||||
PrivacyPolicy: Политика за поверителност
|
||||
Help: Помогне
|
||||
SupportEmail: Поддръжка на имейл
|
||||
SignIn: Влезте с {{.Provider}}
|
||||
Errors:
|
||||
Internal: Възникна вътрешна грешка
|
||||
AuthRequest:
|
||||
|
@ -372,6 +372,8 @@ Footer:
|
||||
Help: Hilfe
|
||||
SupportEmail: Support E-Mail
|
||||
|
||||
SignIn: Mit {{.Provider}} anmelden
|
||||
|
||||
Errors:
|
||||
Internal: Es ist ein interner Fehler aufgetreten
|
||||
AuthRequest:
|
||||
|
@ -372,6 +372,8 @@ Footer:
|
||||
Help: Help
|
||||
SupportEmail: Support E-mail
|
||||
|
||||
SignIn: Sign in with {{.Provider}}
|
||||
|
||||
Errors:
|
||||
Internal: An internal error occurred
|
||||
AuthRequest:
|
||||
|
@ -354,6 +354,8 @@ Footer:
|
||||
Help: Ayuda
|
||||
SupportEmail: Email de soporte
|
||||
|
||||
SignIn: Iniciar sesión con {{.Provider}}
|
||||
|
||||
Errors:
|
||||
Internal: Se produjo un error interno
|
||||
AuthRequest:
|
||||
|
@ -372,6 +372,8 @@ Footer:
|
||||
Help: Aide
|
||||
SupportEmail: E-mail d'assistance
|
||||
|
||||
SignIn: Connexion avec {{.Provider}}
|
||||
|
||||
Errors:
|
||||
Internal: Une erreur interne s'est produite
|
||||
AuthRequest:
|
||||
|
@ -372,6 +372,8 @@ Footer:
|
||||
Help: Aiuto
|
||||
SupportEmail: E-mail di supporto
|
||||
|
||||
SignIn: Accedi con {{.Provider}}
|
||||
|
||||
Errors:
|
||||
Internal: Si è verificato un errore interno
|
||||
AuthRequest:
|
||||
|
@ -363,6 +363,8 @@ Footer:
|
||||
PrivacyPolicy: プライバシーポリシー
|
||||
Help: ヘルプ
|
||||
|
||||
SignIn: '{{.Provider}} でサインイン'
|
||||
|
||||
Errors:
|
||||
Internal: 内部でエラーが発生しました
|
||||
AuthRequest:
|
||||
|
@ -372,6 +372,8 @@ Footer:
|
||||
Help: Помош
|
||||
SupportEmail: Е-пошта за поддршка
|
||||
|
||||
SignIn: Пријавете се со {{.Provider}}
|
||||
|
||||
Errors:
|
||||
Internal: Се појави внатрешна грешка
|
||||
AuthRequest:
|
||||
|
@ -372,6 +372,8 @@ Footer:
|
||||
Help: Pomoc
|
||||
SupportEmail: E-mail wsparcia
|
||||
|
||||
SignIn: Zaloguj się, używając konta {{.Provider}}
|
||||
|
||||
Errors:
|
||||
Internal: Wewnętrzny błąd
|
||||
AuthRequest:
|
||||
|
@ -366,6 +366,8 @@ Footer:
|
||||
Help: Ajuda
|
||||
SupportEmail: E-mail de suporte
|
||||
|
||||
SignIn: Iniciar sessão com a {{.Provider}}
|
||||
|
||||
Errors:
|
||||
Internal: Ocorreu um erro interno
|
||||
AuthRequest:
|
||||
|
@ -372,6 +372,8 @@ Footer:
|
||||
Help: 帮助
|
||||
SupportEmail: 支持邮箱
|
||||
|
||||
SignIn: 通过 {{.Provider}} 登录
|
||||
|
||||
Errors:
|
||||
Internal: 发生了内部错误
|
||||
AuthRequest:
|
||||
|
10
internal/api/ui/login/static/resources/images/idp/apple-dark.svg
Executable file
10
internal/api/ui/login/static/resources/images/idp/apple-dark.svg
Executable file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="56px" height="56px" viewBox="18 15.5 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 61 (89581) - https://sketch.com -->
|
||||
<title>White Logo Square </title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="White-Logo-Square-" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<rect id="Rectangle" fill="" x="6" y="6" width="44" height="44"></rect>
|
||||
<path d="M28.2226562,20.3846154 C29.0546875,20.3846154 30.0976562,19.8048315 30.71875,19.0317864 C31.28125,18.3312142 31.6914062,17.352829 31.6914062,16.3744437 C31.6914062,16.2415766 31.6796875,16.1087095 31.65625,16 C30.7304687,16.0362365 29.6171875,16.640178 28.9492187,17.4494596 C28.421875,18.06548 27.9414062,19.0317864 27.9414062,20.0222505 C27.9414062,20.1671964 27.9648438,20.3121424 27.9765625,20.3604577 C28.0351562,20.3725366 28.1289062,20.3846154 28.2226562,20.3846154 Z M25.2929688,35 C26.4296875,35 26.9335938,34.214876 28.3515625,34.214876 C29.7929688,34.214876 30.109375,34.9758423 31.375,34.9758423 C32.6171875,34.9758423 33.4492188,33.792117 34.234375,32.6325493 C35.1132812,31.3038779 35.4765625,29.9993643 35.5,29.9389701 C35.4179688,29.9148125 33.0390625,28.9122695 33.0390625,26.0979021 C33.0390625,23.6579784 34.9140625,22.5588048 35.0195312,22.474253 C33.7773438,20.6382708 31.890625,20.5899555 31.375,20.5899555 C29.9804688,20.5899555 28.84375,21.4596313 28.1289062,21.4596313 C27.3554688,21.4596313 26.3359375,20.6382708 25.1289062,20.6382708 C22.8320312,20.6382708 20.5,22.5950413 20.5,26.2911634 C20.5,28.5861411 21.3671875,31.013986 22.4335938,32.5842339 C23.3476562,33.9129053 24.1445312,35 25.2929688,35 Z" id="" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
10
internal/api/ui/login/static/resources/images/idp/apple.svg
Executable file
10
internal/api/ui/login/static/resources/images/idp/apple.svg
Executable file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="56px" height="56px" viewBox="18 15.5 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 61 (89581) - https://sketch.com -->
|
||||
<title>Black Logo Square</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="Black-Logo-Square" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<rect id="Rectangle" fill="" x="6" y="6" width="44" height="44"></rect>
|
||||
<path d="M28.2226562,20.3846154 C29.0546875,20.3846154 30.0976562,19.8048315 30.71875,19.0317864 C31.28125,18.3312142 31.6914062,17.352829 31.6914062,16.3744437 C31.6914062,16.2415766 31.6796875,16.1087095 31.65625,16 C30.7304687,16.0362365 29.6171875,16.640178 28.9492187,17.4494596 C28.421875,18.06548 27.9414062,19.0317864 27.9414062,20.0222505 C27.9414062,20.1671964 27.9648438,20.3121424 27.9765625,20.3604577 C28.0351562,20.3725366 28.1289062,20.3846154 28.2226562,20.3846154 Z M25.2929688,35 C26.4296875,35 26.9335938,34.214876 28.3515625,34.214876 C29.7929688,34.214876 30.109375,34.9758423 31.375,34.9758423 C32.6171875,34.9758423 33.4492188,33.792117 34.234375,32.6325493 C35.1132812,31.3038779 35.4765625,29.9993643 35.5,29.9389701 C35.4179688,29.9148125 33.0390625,28.9122695 33.0390625,26.0979021 C33.0390625,23.6579784 34.9140625,22.5588048 35.0195312,22.474253 C33.7773438,20.6382708 31.890625,20.5899555 31.375,20.5899555 C29.9804688,20.5899555 28.84375,21.4596313 28.1289062,21.4596313 C27.3554688,21.4596313 26.3359375,20.6382708 25.1289062,20.6382708 C22.8320312,20.6382708 20.5,22.5950413 20.5,26.2911634 C20.5,28.5861411 21.3671875,31.013986 22.4335938,32.5842339 C23.3476562,33.9129053 24.1445312,35 25.2929688,35 Z" id="" fill="#000000" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
@ -2,6 +2,7 @@ $lgn-idp-margin: 0.5rem 0;
|
||||
$lgn-idp-padding: 0 1px;
|
||||
$lgn-idp-provider-name-line-height: 36px;
|
||||
$lgn-idp-border-radius: 0.5rem;
|
||||
$lgn-idp-logo-size: 46px;
|
||||
|
||||
@mixin lgn-idp-base {
|
||||
display: block;
|
||||
@ -17,14 +18,14 @@ $lgn-idp-border-radius: 0.5rem;
|
||||
transition: border-color 0.2s ease-in-out;
|
||||
|
||||
span.logo {
|
||||
height: 46px;
|
||||
width: 46px;
|
||||
height: $lgn-idp-logo-size;
|
||||
width: $lgn-idp-logo-size;
|
||||
}
|
||||
|
||||
span.provider-name {
|
||||
line-height: $lgn-idp-provider-name-line-height;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
position: relative;
|
||||
left: calc(50% - $lgn-idp-logo-size);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
@ -75,4 +76,17 @@ $lgn-idp-border-radius: 0.5rem;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&.apple {
|
||||
span.logo {
|
||||
height: 46px;
|
||||
width: 46px;
|
||||
background-image: var(--apple-image-src);
|
||||
background-size: 25px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 5px;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -117,6 +117,8 @@
|
||||
--zitadel-color-github-background: #ffffff;
|
||||
--zitadel-color-gitlab-text: #8b8d8d;
|
||||
--zitadel-color-gitlab-background: #ffffff;
|
||||
--zitadel-color-apple-text: #8b8d8d;
|
||||
--zitadel-color-apple-background: #ffffff;
|
||||
|
||||
--zitadel-color-qr: var(--zitadel-color-black);
|
||||
--zitadel-color-qr-background: var(--zitadel-color-white);
|
||||
@ -125,6 +127,7 @@
|
||||
--github-image-src: url(../../../images/idp/github.png);
|
||||
--gitlab-image-src: url(../../../images/idp/gitlab.png);
|
||||
--azure-image-src: url(../../../images/idp/ms.svg);
|
||||
--apple-image-src: url(../../../images/idp/apple.svg);
|
||||
}
|
||||
|
||||
.lgn-dark-theme {
|
||||
@ -227,9 +230,12 @@
|
||||
--zitadel-color-github-background: #ffffff;
|
||||
--zitadel-color-gitlab-text: #8b8d8d;
|
||||
--zitadel-color-gitlab-background: #ffffff;
|
||||
--zitadel-color-apple-text: #8b8d8d;
|
||||
--zitadel-color-apple-background: #ffffff;
|
||||
|
||||
--google-image-src: url(../../../images/idp/google.png);
|
||||
--github-image-src: url(../../../images/idp/github-white.png);
|
||||
--gitlab-image-src: url(../../../images/idp/gitlab.png);
|
||||
--azure-image-src: url(../../../images/idp/ms.svg);
|
||||
--apple-image-src: url(../../../images/idp/apple-dark.svg);
|
||||
}
|
||||
|
@ -52,7 +52,11 @@
|
||||
<a href="{{ externalIDPAuthURL $reqid $provider.IDPConfigID}}"
|
||||
class="lgn-idp {{idpProviderClass $provider.IDPType}}">
|
||||
<span class="logo"></span>
|
||||
{{if $provider.IDPType.IsSignInButton}}
|
||||
<span class="provider-name">{{t "SignIn" "Provider" $provider.DisplayName}}</span>
|
||||
{{else}}
|
||||
<span class="provider-name">{{$provider.DisplayName}}</span>
|
||||
{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
|
@ -29,7 +29,11 @@
|
||||
<a href="{{ externalIDPRegisterURL $reqid $provider.IDPConfigID}}"
|
||||
class="lgn-idp {{idpProviderClass $provider.IDPType}}">
|
||||
<span class="logo"></span>
|
||||
{{if $provider.IDPType.IsSignInButton}}
|
||||
<span class="provider-name">{{t "SignIn" "Provider" $provider.DisplayName}}</span>
|
||||
{{else}}
|
||||
<span class="provider-name">{{$provider.DisplayName}}</span>
|
||||
{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
@ -110,6 +110,16 @@ type LDAPProvider struct {
|
||||
IDPOptions idp.Options
|
||||
}
|
||||
|
||||
type AppleProvider struct {
|
||||
Name string
|
||||
ClientID string
|
||||
TeamID string
|
||||
KeyID string
|
||||
PrivateKey []byte
|
||||
Scopes []string
|
||||
IDPOptions idp.Options
|
||||
}
|
||||
|
||||
func ExistsIDP(ctx context.Context, filter preparation.FilterToQueryReducer, id, orgID string) (exists bool, err error) {
|
||||
writeModel := NewOrgIDPRemoveWriteModel(orgID, id)
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/idp"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/apple"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/jwt"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/oauth"
|
||||
@ -215,6 +216,8 @@ func tokensForSucceededIDPIntent(session idp.Session, encryptionAlg crypto.Encry
|
||||
tokens = s.Tokens
|
||||
case *azuread.Session:
|
||||
tokens = s.Tokens
|
||||
case *apple.Session:
|
||||
tokens = s.Tokens
|
||||
default:
|
||||
return nil, "", nil
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package command
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
@ -14,6 +15,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
providers "github.com/zitadel/zitadel/internal/idp"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/apple"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/github"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/gitlab"
|
||||
@ -1587,6 +1589,138 @@ func (wm *LDAPIDPWriteModel) GetProviderOptions() idp.Options {
|
||||
return wm.Options
|
||||
}
|
||||
|
||||
type AppleIDPWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
ID string
|
||||
Name string
|
||||
ClientID string
|
||||
TeamID string
|
||||
KeyID string
|
||||
PrivateKey *crypto.CryptoValue
|
||||
Scopes []string
|
||||
idp.Options
|
||||
|
||||
State domain.IDPState
|
||||
}
|
||||
|
||||
func (wm *AppleIDPWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *idp.AppleIDPAddedEvent:
|
||||
wm.reduceAddedEvent(e)
|
||||
case *idp.AppleIDPChangedEvent:
|
||||
wm.reduceChangedEvent(e)
|
||||
case *idp.RemovedEvent:
|
||||
wm.State = domain.IDPStateRemoved
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *AppleIDPWriteModel) reduceAddedEvent(e *idp.AppleIDPAddedEvent) {
|
||||
wm.Name = e.Name
|
||||
wm.ClientID = e.ClientID
|
||||
wm.TeamID = e.TeamID
|
||||
wm.KeyID = e.KeyID
|
||||
wm.PrivateKey = e.PrivateKey
|
||||
wm.Scopes = e.Scopes
|
||||
wm.Options = e.Options
|
||||
wm.State = domain.IDPStateActive
|
||||
}
|
||||
|
||||
func (wm *AppleIDPWriteModel) reduceChangedEvent(e *idp.AppleIDPChangedEvent) {
|
||||
if e.Name != nil {
|
||||
wm.Name = *e.Name
|
||||
}
|
||||
if e.ClientID != nil {
|
||||
wm.ClientID = *e.ClientID
|
||||
}
|
||||
if e.PrivateKey != nil {
|
||||
wm.PrivateKey = e.PrivateKey
|
||||
}
|
||||
if e.Scopes != nil {
|
||||
wm.Scopes = e.Scopes
|
||||
}
|
||||
wm.Options.ReduceChanges(e.OptionChanges)
|
||||
}
|
||||
|
||||
func (wm *AppleIDPWriteModel) NewChanges(
|
||||
name string,
|
||||
clientID string,
|
||||
teamID string,
|
||||
keyID string,
|
||||
privateKey []byte,
|
||||
secretCrypto crypto.Crypto,
|
||||
scopes []string,
|
||||
options idp.Options,
|
||||
) ([]idp.AppleIDPChanges, error) {
|
||||
changes := make([]idp.AppleIDPChanges, 0)
|
||||
var encryptedKey *crypto.CryptoValue
|
||||
var err error
|
||||
if len(privateKey) != 0 {
|
||||
encryptedKey, err = crypto.Crypt(privateKey, secretCrypto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
changes = append(changes, idp.ChangeApplePrivateKey(encryptedKey))
|
||||
}
|
||||
if wm.Name != name {
|
||||
changes = append(changes, idp.ChangeAppleName(name))
|
||||
}
|
||||
if wm.ClientID != clientID {
|
||||
changes = append(changes, idp.ChangeAppleClientID(clientID))
|
||||
}
|
||||
if wm.TeamID != teamID {
|
||||
changes = append(changes, idp.ChangeAppleTeamID(teamID))
|
||||
}
|
||||
if wm.KeyID != keyID {
|
||||
changes = append(changes, idp.ChangeAppleKeyID(keyID))
|
||||
}
|
||||
if slices.Compare(wm.Scopes, scopes) != 0 {
|
||||
changes = append(changes, idp.ChangeAppleScopes(scopes))
|
||||
}
|
||||
|
||||
opts := wm.Options.Changes(options)
|
||||
if !opts.IsZero() {
|
||||
changes = append(changes, idp.ChangeAppleOptions(opts))
|
||||
}
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
func (wm *AppleIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.EncryptionAlgorithm) (providers.Provider, error) {
|
||||
privateKey, err := crypto.Decrypt(wm.PrivateKey, idpAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts := make([]oidc.ProviderOpts, 0, 4)
|
||||
if wm.IsCreationAllowed {
|
||||
opts = append(opts, oidc.WithCreationAllowed())
|
||||
}
|
||||
if wm.IsLinkingAllowed {
|
||||
opts = append(opts, oidc.WithLinkingAllowed())
|
||||
}
|
||||
if wm.IsAutoCreation {
|
||||
opts = append(opts, oidc.WithAutoCreation())
|
||||
}
|
||||
if wm.IsAutoUpdate {
|
||||
opts = append(opts, oidc.WithAutoUpdate())
|
||||
}
|
||||
return apple.New(
|
||||
wm.ClientID,
|
||||
wm.TeamID,
|
||||
wm.KeyID,
|
||||
callbackURL,
|
||||
privateKey,
|
||||
wm.Scopes,
|
||||
opts...,
|
||||
)
|
||||
}
|
||||
|
||||
func (wm *AppleIDPWriteModel) GetProviderOptions() idp.Options {
|
||||
return wm.Options
|
||||
}
|
||||
|
||||
type IDPRemoveWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
@ -1617,6 +1751,8 @@ func (wm *IDPRemoveWriteModel) Reduce() error {
|
||||
wm.reduceAdded(e.ID)
|
||||
case *idp.LDAPIDPAddedEvent:
|
||||
wm.reduceAdded(e.ID)
|
||||
case *idp.AppleIDPAddedEvent:
|
||||
wm.reduceAdded(e.ID)
|
||||
case *idp.RemovedEvent:
|
||||
wm.reduceRemoved(e.ID)
|
||||
case *idpconfig.IDPConfigAddedEvent:
|
||||
@ -1699,6 +1835,10 @@ func (wm *IDPTypeWriteModel) Reduce() error {
|
||||
wm.reduceAdded(e.ID, domain.IDPTypeLDAP, e.Aggregate())
|
||||
case *org.LDAPIDPAddedEvent:
|
||||
wm.reduceAdded(e.ID, domain.IDPTypeLDAP, e.Aggregate())
|
||||
case *instance.AppleIDPAddedEvent:
|
||||
wm.reduceAdded(e.ID, domain.IDPTypeApple, e.Aggregate())
|
||||
case *org.AppleIDPAddedEvent:
|
||||
wm.reduceAdded(e.ID, domain.IDPTypeApple, e.Aggregate())
|
||||
case *instance.OIDCIDPMigratedAzureADEvent:
|
||||
wm.reduceChanged(e.ID, domain.IDPTypeAzureAD)
|
||||
case *org.OIDCIDPMigratedAzureADEvent:
|
||||
@ -1774,6 +1914,7 @@ func (wm *IDPTypeWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
instance.GitLabSelfHostedIDPAddedEventType,
|
||||
instance.GoogleIDPAddedEventType,
|
||||
instance.LDAPIDPAddedEventType,
|
||||
instance.AppleIDPAddedEventType,
|
||||
instance.OIDCIDPMigratedAzureADEventType,
|
||||
instance.OIDCIDPMigratedGoogleEventType,
|
||||
instance.IDPRemovedEventType,
|
||||
@ -1792,6 +1933,7 @@ func (wm *IDPTypeWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
org.GitLabSelfHostedIDPAddedEventType,
|
||||
org.GoogleIDPAddedEventType,
|
||||
org.LDAPIDPAddedEventType,
|
||||
org.AppleIDPAddedEventType,
|
||||
org.OIDCIDPMigratedAzureADEventType,
|
||||
org.OIDCIDPMigratedGoogleEventType,
|
||||
org.IDPRemovedEventType,
|
||||
@ -1859,6 +2001,8 @@ func NewAllIDPWriteModel(resourceOwner string, instanceBool bool, id string, idp
|
||||
writeModel.model = NewGitLabSelfHostedInstanceIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeGoogle:
|
||||
writeModel.model = NewGoogleInstanceIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeApple:
|
||||
writeModel.model = NewAppleInstanceIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeUnspecified:
|
||||
fallthrough
|
||||
default:
|
||||
@ -1886,6 +2030,8 @@ func NewAllIDPWriteModel(resourceOwner string, instanceBool bool, id string, idp
|
||||
writeModel.model = NewGitLabSelfHostedOrgIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeGoogle:
|
||||
writeModel.model = NewGoogleOrgIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeApple:
|
||||
writeModel.model = NewAppleOrgIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeUnspecified:
|
||||
fallthrough
|
||||
default:
|
||||
|
@ -467,6 +467,48 @@ func (c *Commands) UpdateInstanceLDAPProvider(ctx context.Context, id string, pr
|
||||
return pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddInstanceAppleProvider(ctx context.Context, provider AppleProvider) (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 := NewAppleInstanceIDPWriteModel(instanceID, id)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddInstanceAppleProvider(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) UpdateInstanceAppleProvider(ctx context.Context, id string, provider AppleProvider) (*domain.ObjectDetails, error) {
|
||||
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||
instanceAgg := instance.NewAggregate(instanceID)
|
||||
writeModel := NewAppleInstanceIDPWriteModel(instanceID, id)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateInstanceAppleProvider(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) 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))
|
||||
@ -1518,6 +1560,98 @@ func (c *Commands) prepareUpdateInstanceLDAPProvider(a *instance.Aggregate, writ
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) prepareAddInstanceAppleProvider(a *instance.Aggregate, writeModel *InstanceAppleIDPWriteModel, provider AppleProvider) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-jkn3w", "Errors.IDP.ClientIDMissing")
|
||||
}
|
||||
if provider.TeamID = strings.TrimSpace(provider.TeamID); provider.TeamID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-Ffg32", "Errors.IDP.TeamIDMissing")
|
||||
}
|
||||
if provider.KeyID = strings.TrimSpace(provider.KeyID); provider.KeyID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-GDjm5", "Errors.IDP.KeyIDMissing")
|
||||
}
|
||||
if len(provider.PrivateKey) == 0 {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-GVD4n", "Errors.IDP.PrivateKeyMissing")
|
||||
}
|
||||
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
|
||||
}
|
||||
privateKey, err := crypto.Encrypt(provider.PrivateKey, c.idpConfigEncryption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []eventstore.Command{
|
||||
instance.NewAppleIDPAddedEvent(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
writeModel.ID,
|
||||
provider.Name,
|
||||
provider.ClientID,
|
||||
provider.TeamID,
|
||||
provider.KeyID,
|
||||
privateKey,
|
||||
provider.Scopes,
|
||||
provider.IDPOptions,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) prepareUpdateInstanceAppleProvider(a *instance.Aggregate, writeModel *InstanceAppleIDPWriteModel, provider AppleProvider) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if writeModel.ID = strings.TrimSpace(writeModel.ID); writeModel.ID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-FRHBH", "Errors.IDMissing")
|
||||
}
|
||||
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-SFm4l", "Errors.IDP.ClientIDMissing")
|
||||
}
|
||||
if provider.TeamID = strings.TrimSpace(provider.TeamID); provider.TeamID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-SG34t", "Errors.IDP.TeamIDMissing")
|
||||
}
|
||||
if provider.KeyID = strings.TrimSpace(provider.KeyID); provider.KeyID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-Gh4z2", "Errors.IDP.KeyIDMissing")
|
||||
}
|
||||
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-SG3bh", "Errors.IDPConfig.NotExisting")
|
||||
}
|
||||
event, err := writeModel.NewChangedEvent(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
writeModel.ID,
|
||||
provider.Name,
|
||||
provider.ClientID,
|
||||
provider.TeamID,
|
||||
provider.KeyID,
|
||||
provider.PrivateKey,
|
||||
c.idpConfigEncryption,
|
||||
provider.Scopes,
|
||||
provider.IDPOptions,
|
||||
)
|
||||
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) {
|
||||
|
@ -793,6 +793,73 @@ func (wm *InstanceLDAPIDPWriteModel) NewChangedEvent(
|
||||
return instance.NewLDAPIDPChangedEvent(ctx, aggregate, id, changes)
|
||||
}
|
||||
|
||||
type InstanceAppleIDPWriteModel struct {
|
||||
AppleIDPWriteModel
|
||||
}
|
||||
|
||||
func NewAppleInstanceIDPWriteModel(instanceID, id string) *InstanceAppleIDPWriteModel {
|
||||
return &InstanceAppleIDPWriteModel{
|
||||
AppleIDPWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: instanceID,
|
||||
ResourceOwner: instanceID,
|
||||
},
|
||||
ID: id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *InstanceAppleIDPWriteModel) AppendEvents(events ...eventstore.Event) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *instance.AppleIDPAddedEvent:
|
||||
wm.AppleIDPWriteModel.AppendEvents(&e.AppleIDPAddedEvent)
|
||||
case *instance.AppleIDPChangedEvent:
|
||||
wm.AppleIDPWriteModel.AppendEvents(&e.AppleIDPChangedEvent)
|
||||
case *instance.IDPRemovedEvent:
|
||||
wm.AppleIDPWriteModel.AppendEvents(&e.RemovedEvent)
|
||||
default:
|
||||
wm.AppleIDPWriteModel.AppendEvents(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *InstanceAppleIDPWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(instance.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(
|
||||
instance.AppleIDPAddedEventType,
|
||||
instance.AppleIDPChangedEventType,
|
||||
instance.IDPRemovedEventType,
|
||||
).
|
||||
EventData(map[string]interface{}{"id": wm.ID}).
|
||||
Builder()
|
||||
}
|
||||
|
||||
func (wm *InstanceAppleIDPWriteModel) NewChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
id,
|
||||
name,
|
||||
clientID,
|
||||
teamID,
|
||||
keyID string,
|
||||
privateKey []byte,
|
||||
secretCrypto crypto.Crypto,
|
||||
scopes []string,
|
||||
options idp.Options,
|
||||
) (*instance.AppleIDPChangedEvent, error) {
|
||||
|
||||
changes, err := wm.AppleIDPWriteModel.NewChanges(name, clientID, teamID, keyID, privateKey, secretCrypto, scopes, options)
|
||||
if err != nil || len(changes) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return instance.NewAppleIDPChangedEvent(ctx, aggregate, id, changes)
|
||||
}
|
||||
|
||||
type InstanceIDPRemoveWriteModel struct {
|
||||
IDPRemoveWriteModel
|
||||
}
|
||||
@ -832,6 +899,8 @@ func (wm *InstanceIDPRemoveWriteModel) AppendEvents(events ...eventstore.Event)
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.GoogleIDPAddedEvent)
|
||||
case *instance.LDAPIDPAddedEvent:
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.LDAPIDPAddedEvent)
|
||||
case *instance.AppleIDPAddedEvent:
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.AppleIDPAddedEvent)
|
||||
case *instance.IDPRemovedEvent:
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.RemovedEvent)
|
||||
case *instance.IDPConfigAddedEvent:
|
||||
@ -861,6 +930,7 @@ func (wm *InstanceIDPRemoveWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
instance.GitLabSelfHostedIDPAddedEventType,
|
||||
instance.GoogleIDPAddedEventType,
|
||||
instance.LDAPIDPAddedEventType,
|
||||
instance.AppleIDPAddedEventType,
|
||||
instance.IDPRemovedEventType,
|
||||
).
|
||||
EventData(map[string]interface{}{"id": wm.ID}).
|
||||
|
@ -4857,3 +4857,464 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_AddInstanceAppleIDP(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
secretCrypto crypto.EncryptionAlgorithm
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
provider AppleProvider
|
||||
}
|
||||
type res struct {
|
||||
id string
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"invalid clientID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
provider: AppleProvider{},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-jkn3w", "Errors.IDP.ClientIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid teamID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-Ffg32", "Errors.IDP.TeamIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid keyID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-GDjm5", "Errors.IDP.KeyIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid privateKey",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-GVD4n", "Errors.IDP.PrivateKeyMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
instance.NewAppleIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
nil,
|
||||
idp.Options{},
|
||||
)),
|
||||
},
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
PrivateKey: []byte("privateKey"),
|
||||
},
|
||||
},
|
||||
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.NewAppleIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
[]string{"name", "email"},
|
||||
idp.Options{
|
||||
IsCreationAllowed: true,
|
||||
IsLinkingAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
)),
|
||||
},
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
PrivateKey: []byte("privateKey"),
|
||||
Scopes: []string{"name", "email"},
|
||||
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,
|
||||
}
|
||||
id, got, err := c.AddInstanceAppleProvider(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_UpdateInstanceAppleIDP(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
secretCrypto crypto.EncryptionAlgorithm
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
id string
|
||||
provider AppleProvider
|
||||
}
|
||||
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: AppleProvider{},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-FRHBH", "Errors.IDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid clientID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
id: "id1",
|
||||
provider: AppleProvider{},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-SFm4l", "Errors.IDP.ClientIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid teamID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-SG34t", "Errors.IDP.TeamIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid keyID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-Gh4z2", "Errors.IDP.KeyIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no changes",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewAppleIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
nil,
|
||||
idp.Options{},
|
||||
)),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewAppleIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
nil,
|
||||
idp.Options{},
|
||||
)),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
func() eventstore.Command {
|
||||
t := true
|
||||
event, _ := instance.NewAppleIDPChangedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
|
||||
"id1",
|
||||
[]idp.AppleIDPChanges{
|
||||
idp.ChangeAppleClientID("clientID2"),
|
||||
idp.ChangeAppleTeamID("teamID2"),
|
||||
idp.ChangeAppleKeyID("keyID2"),
|
||||
idp.ChangeApplePrivateKey(&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("newPrivateKey"),
|
||||
}),
|
||||
idp.ChangeAppleScopes([]string{"name", "email"}),
|
||||
idp.ChangeAppleOptions(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: AppleProvider{
|
||||
ClientID: "clientID2",
|
||||
TeamID: "teamID2",
|
||||
KeyID: "keyID2",
|
||||
PrivateKey: []byte("newPrivateKey"),
|
||||
Scopes: []string{"name", "email"},
|
||||
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.UpdateInstanceAppleProvider(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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -444,6 +444,46 @@ func (c *Commands) UpdateOrgLDAPProvider(ctx context.Context, resourceOwner, id
|
||||
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()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
writeModel := NewAppleOrgIDPWriteModel(resourceOwner, id)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddOrgAppleProvider(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) UpdateOrgAppleProvider(ctx context.Context, resourceOwner, id string, provider AppleProvider) (*domain.ObjectDetails, error) {
|
||||
orgAgg := org.NewAggregate(resourceOwner)
|
||||
writeModel := NewAppleOrgIDPWriteModel(resourceOwner, id)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateOrgAppleProvider(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) DeleteOrgProvider(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) {
|
||||
orgAgg := org.NewAggregate(resourceOwner)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareDeleteOrgProvider(orgAgg, resourceOwner, id))
|
||||
@ -1507,6 +1547,98 @@ func (c *Commands) prepareUpdateOrgLDAPProvider(a *org.Aggregate, writeModel *Or
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) prepareAddOrgAppleProvider(a *org.Aggregate, writeModel *OrgAppleIDPWriteModel, provider AppleProvider) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-jkn3w", "Errors.IDP.ClientIDMissing")
|
||||
}
|
||||
if provider.TeamID = strings.TrimSpace(provider.TeamID); provider.TeamID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-Ffg32", "Errors.IDP.TeamIDMissing")
|
||||
}
|
||||
if provider.KeyID = strings.TrimSpace(provider.KeyID); provider.KeyID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-GDjm5", "Errors.IDP.KeyIDMissing")
|
||||
}
|
||||
if len(provider.PrivateKey) == 0 {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-GVD4n", "Errors.IDP.PrivateKeyMissing")
|
||||
}
|
||||
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
|
||||
}
|
||||
privateKey, err := crypto.Encrypt(provider.PrivateKey, c.idpConfigEncryption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []eventstore.Command{
|
||||
org.NewAppleIDPAddedEvent(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
writeModel.ID,
|
||||
provider.Name,
|
||||
provider.ClientID,
|
||||
provider.TeamID,
|
||||
provider.KeyID,
|
||||
privateKey,
|
||||
provider.Scopes,
|
||||
provider.IDPOptions,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) prepareUpdateOrgAppleProvider(a *org.Aggregate, writeModel *OrgAppleIDPWriteModel, provider AppleProvider) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if writeModel.ID = strings.TrimSpace(writeModel.ID); writeModel.ID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-FRHBH", "Errors.IDMissing")
|
||||
}
|
||||
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-SFm4l", "Errors.IDP.ClientIDMissing")
|
||||
}
|
||||
if provider.TeamID = strings.TrimSpace(provider.TeamID); provider.TeamID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-SG34t", "Errors.IDP.TeamIDMissing")
|
||||
}
|
||||
if provider.KeyID = strings.TrimSpace(provider.KeyID); provider.KeyID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-Gh4z2", "Errors.IDP.KeyIDMissing")
|
||||
}
|
||||
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-SG3bh", "Errors.IDPConfig.NotExisting")
|
||||
}
|
||||
event, err := writeModel.NewChangedEvent(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
writeModel.ID,
|
||||
provider.Name,
|
||||
provider.ClientID,
|
||||
provider.TeamID,
|
||||
provider.KeyID,
|
||||
provider.PrivateKey,
|
||||
c.idpConfigEncryption,
|
||||
provider.Scopes,
|
||||
provider.IDPOptions,
|
||||
)
|
||||
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) {
|
||||
|
@ -803,6 +803,73 @@ func (wm *OrgLDAPIDPWriteModel) NewChangedEvent(
|
||||
return org.NewLDAPIDPChangedEvent(ctx, aggregate, id, changes)
|
||||
}
|
||||
|
||||
type OrgAppleIDPWriteModel struct {
|
||||
AppleIDPWriteModel
|
||||
}
|
||||
|
||||
func NewAppleOrgIDPWriteModel(orgID, id string) *OrgAppleIDPWriteModel {
|
||||
return &OrgAppleIDPWriteModel{
|
||||
AppleIDPWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: orgID,
|
||||
ResourceOwner: orgID,
|
||||
},
|
||||
ID: id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *OrgAppleIDPWriteModel) AppendEvents(events ...eventstore.Event) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *org.AppleIDPAddedEvent:
|
||||
wm.AppleIDPWriteModel.AppendEvents(&e.AppleIDPAddedEvent)
|
||||
case *org.AppleIDPChangedEvent:
|
||||
wm.AppleIDPWriteModel.AppendEvents(&e.AppleIDPChangedEvent)
|
||||
case *org.IDPRemovedEvent:
|
||||
wm.AppleIDPWriteModel.AppendEvents(&e.RemovedEvent)
|
||||
default:
|
||||
wm.AppleIDPWriteModel.AppendEvents(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *OrgAppleIDPWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(org.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(
|
||||
org.AppleIDPAddedEventType,
|
||||
org.AppleIDPChangedEventType,
|
||||
org.IDPRemovedEventType,
|
||||
).
|
||||
EventData(map[string]interface{}{"id": wm.ID}).
|
||||
Builder()
|
||||
}
|
||||
|
||||
func (wm *OrgAppleIDPWriteModel) NewChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
id,
|
||||
name,
|
||||
clientID,
|
||||
teamID,
|
||||
keyID string,
|
||||
privateKey []byte,
|
||||
secretCrypto crypto.Crypto,
|
||||
scopes []string,
|
||||
options idp.Options,
|
||||
) (*org.AppleIDPChangedEvent, error) {
|
||||
|
||||
changes, err := wm.AppleIDPWriteModel.NewChanges(name, clientID, teamID, keyID, privateKey, secretCrypto, scopes, options)
|
||||
if err != nil || len(changes) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return org.NewAppleIDPChangedEvent(ctx, aggregate, id, changes)
|
||||
}
|
||||
|
||||
type OrgIDPRemoveWriteModel struct {
|
||||
IDPRemoveWriteModel
|
||||
}
|
||||
@ -842,6 +909,8 @@ func (wm *OrgIDPRemoveWriteModel) AppendEvents(events ...eventstore.Event) {
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.GoogleIDPAddedEvent)
|
||||
case *org.LDAPIDPAddedEvent:
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.LDAPIDPAddedEvent)
|
||||
case *org.AppleIDPAddedEvent:
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.AppleIDPAddedEvent)
|
||||
case *org.IDPRemovedEvent:
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.RemovedEvent)
|
||||
case *org.IDPConfigAddedEvent:
|
||||
@ -871,6 +940,7 @@ func (wm *OrgIDPRemoveWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
org.GitLabSelfHostedIDPAddedEventType,
|
||||
org.GoogleIDPAddedEventType,
|
||||
org.LDAPIDPAddedEventType,
|
||||
org.AppleIDPAddedEventType,
|
||||
org.IDPRemovedEventType,
|
||||
).
|
||||
EventData(map[string]interface{}{"id": wm.ID}).
|
||||
|
@ -4926,6 +4926,473 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_AddOrgAppleIDP(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
secretCrypto crypto.EncryptionAlgorithm
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
resourceOwner string
|
||||
provider AppleProvider
|
||||
}
|
||||
type res struct {
|
||||
id string
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"invalid clientID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: AppleProvider{},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-jkn3w", "Errors.IDP.ClientIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid teamID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-Ffg32", "Errors.IDP.TeamIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid keyID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-GDjm5", "Errors.IDP.KeyIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid privateKey",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-GVD4n", "Errors.IDP.PrivateKeyMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
eventPusherToEvents(
|
||||
org.NewAppleIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
nil,
|
||||
idp.Options{},
|
||||
)),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
PrivateKey: []byte("privateKey"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
id: "id1",
|
||||
want: &domain.ObjectDetails{ResourceOwner: "org1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok all set",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
eventPusherToEvents(
|
||||
org.NewAppleIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
[]string{"name", "email"},
|
||||
idp.Options{
|
||||
IsCreationAllowed: true,
|
||||
IsLinkingAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
)),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
PrivateKey: []byte("privateKey"),
|
||||
Scopes: []string{"name", "email"},
|
||||
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,
|
||||
}
|
||||
id, got, err := c.AddOrgAppleProvider(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_UpdateOrgAppleIDP(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
secretCrypto crypto.EncryptionAlgorithm
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
resourceOwner string
|
||||
id string
|
||||
provider AppleProvider
|
||||
}
|
||||
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: AppleProvider{},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-FRHBH", "Errors.IDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid clientID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
id: "id1",
|
||||
provider: AppleProvider{},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-SFm4l", "Errors.IDP.ClientIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid teamID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-SG34t", "Errors.IDP.TeamIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid keyID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-Gh4z2", "Errors.IDP.KeyIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no changes",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewAppleIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
nil,
|
||||
idp.Options{},
|
||||
)),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{ResourceOwner: "org1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewAppleIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
nil,
|
||||
idp.Options{},
|
||||
)),
|
||||
),
|
||||
expectPush(
|
||||
eventPusherToEvents(
|
||||
func() eventstore.Command {
|
||||
t := true
|
||||
event, _ := org.NewAppleIDPChangedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"id1",
|
||||
[]idp.AppleIDPChanges{
|
||||
idp.ChangeAppleClientID("clientID2"),
|
||||
idp.ChangeAppleTeamID("teamID2"),
|
||||
idp.ChangeAppleKeyID("keyID2"),
|
||||
idp.ChangeApplePrivateKey(&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("newPrivateKey"),
|
||||
}),
|
||||
idp.ChangeAppleScopes([]string{"name", "email"}),
|
||||
idp.ChangeAppleOptions(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: AppleProvider{
|
||||
ClientID: "clientID2",
|
||||
TeamID: "teamID2",
|
||||
KeyID: "keyID2",
|
||||
PrivateKey: []byte("newPrivateKey"),
|
||||
Scopes: []string{"name", "email"},
|
||||
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.UpdateOrgAppleProvider(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 stringPointer(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ const (
|
||||
IDPTypeGitLab
|
||||
IDPTypeGitLabSelfHosted
|
||||
IDPTypeGoogle
|
||||
IDPTypeApple
|
||||
)
|
||||
|
||||
func (t IDPType) GetCSSClass() string {
|
||||
@ -50,6 +51,8 @@ func (t IDPType) GetCSSClass() string {
|
||||
return "gitlab"
|
||||
case IDPTypeAzureAD:
|
||||
return "azure"
|
||||
case IDPTypeApple:
|
||||
return "apple"
|
||||
case IDPTypeUnspecified,
|
||||
IDPTypeOIDC,
|
||||
IDPTypeJWT,
|
||||
@ -78,6 +81,8 @@ func (t IDPType) DisplayName() string {
|
||||
return "GitLab"
|
||||
case IDPTypeGoogle:
|
||||
return "Google"
|
||||
case IDPTypeApple:
|
||||
return "Apple"
|
||||
case IDPTypeUnspecified,
|
||||
IDPTypeOIDC,
|
||||
IDPTypeJWT,
|
||||
@ -94,6 +99,12 @@ func (t IDPType) DisplayName() string {
|
||||
}
|
||||
}
|
||||
|
||||
// IsSignInButton returns if the button should be displayed with a translated
|
||||
// "Sign in with {{.DisplayName}}", e.g. "Sign in with Apple"
|
||||
func (t IDPType) IsSignInButton() bool {
|
||||
return t == IDPTypeApple
|
||||
}
|
||||
|
||||
type IDPIntentState int32
|
||||
|
||||
const (
|
||||
|
@ -3,9 +3,9 @@ package form
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
|
||||
"github.com/gorilla/schema"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
type Parser struct {
|
||||
|
68
internal/idp/providers/apple/apple.go
Normal file
68
internal/idp/providers/apple/apple.go
Normal file
@ -0,0 +1,68 @@
|
||||
package apple
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/crypto"
|
||||
openid "github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/idp"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/oidc"
|
||||
)
|
||||
|
||||
const (
|
||||
name = "Apple"
|
||||
issuer = "https://appleid.apple.com"
|
||||
)
|
||||
|
||||
var _ idp.Provider = (*Provider)(nil)
|
||||
|
||||
// Provider is the [idp.Provider] implementation for Apple
|
||||
type Provider struct {
|
||||
*oidc.Provider
|
||||
}
|
||||
|
||||
func New(clientID, teamID, keyID, callbackURL string, key []byte, scopes []string, options ...oidc.ProviderOpts) (*Provider, error) {
|
||||
secret, err := clientSecretFromPrivateKey(key, teamID, clientID, keyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
options = append(options, oidc.WithResponseMode("form_post"))
|
||||
rp, err := oidc.New(name, issuer, clientID, secret, callbackURL, scopes, oidc.DefaultMapper, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Provider{
|
||||
Provider: rp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// clientSecretFromPrivateKey uses the private key to create and sign a JWT, which has to be used as client_secret at Apple.
|
||||
func clientSecretFromPrivateKey(key []byte, teamID, clientID, keyID string) (string, error) {
|
||||
block, _ := pem.Decode(key)
|
||||
b := block.Bytes
|
||||
pk, err := x509.ParsePKCS8PrivateKey(b)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
signingKey := jose.SigningKey{
|
||||
Algorithm: jose.ES256,
|
||||
Key: &jose.JSONWebKey{Key: pk, KeyID: keyID},
|
||||
}
|
||||
signer, err := jose.NewSigner(signingKey, &jose.SignerOptions{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
iat := time.Now()
|
||||
exp := iat.Add(time.Hour)
|
||||
return crypto.Sign(&openid.JWTTokenRequest{
|
||||
Issuer: teamID,
|
||||
Subject: clientID,
|
||||
Audience: []string{issuer},
|
||||
ExpiresAt: openid.FromTime(exp),
|
||||
IssuedAt: openid.FromTime(iat),
|
||||
}, signer)
|
||||
}
|
69
internal/idp/providers/apple/apple_test.go
Normal file
69
internal/idp/providers/apple/apple_test.go
Normal file
@ -0,0 +1,69 @@
|
||||
package apple
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/idp"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/oidc"
|
||||
)
|
||||
|
||||
const (
|
||||
privateKey = `-----BEGIN PRIVATE KEY-----
|
||||
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgXn/LDURaetCoymSj
|
||||
fRslBiBwzBSa8ifiyfYGIWNStYGgCgYIKoZIzj0DAQehRANCAATymZXIsGrXnl6b
|
||||
+80miSiVOCcLnyaYa2uQBQvQwgB7GibXhrzF+D/MRTV4P7P8+Lg1K9Khkjc59eNK
|
||||
4RrQP4g7
|
||||
-----END PRIVATE KEY-----
|
||||
`
|
||||
)
|
||||
|
||||
func TestProvider_BeginAuth(t *testing.T) {
|
||||
type fields struct {
|
||||
clientID string
|
||||
teamID string
|
||||
keyID string
|
||||
privateKey []byte
|
||||
redirectURI string
|
||||
scopes []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want idp.Session
|
||||
}{
|
||||
{
|
||||
name: "successful auth",
|
||||
fields: fields{
|
||||
clientID: "clientID",
|
||||
teamID: "teamID",
|
||||
keyID: "keyID",
|
||||
privateKey: []byte(privateKey),
|
||||
redirectURI: "redirectURI",
|
||||
scopes: []string{"openid"},
|
||||
},
|
||||
want: &Session{
|
||||
Session: &oidc.Session{
|
||||
AuthURL: "https://appleid.apple.com/auth/authorize?client_id=clientID&redirect_uri=redirectURI&response_mode=form_post&response_type=code&scope=openid&state=testState",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
r := require.New(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")
|
||||
r.NoError(err)
|
||||
|
||||
a.Equal(tt.want.GetAuthURL(), session.GetAuthURL())
|
||||
})
|
||||
}
|
||||
}
|
64
internal/idp/providers/apple/session.go
Normal file
64
internal/idp/providers/apple/session.go
Normal file
@ -0,0 +1,64 @@
|
||||
package apple
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
openid "github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/idp"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/oidc"
|
||||
)
|
||||
|
||||
// Session extends the [oidc.Session] with the formValues returned from the callback.
|
||||
// This enables to parse the user (name and email), which Apple only returns as form params on registration
|
||||
type Session struct {
|
||||
*oidc.Session
|
||||
UserFormValue string
|
||||
}
|
||||
|
||||
type userFormValue struct {
|
||||
Name userNamesFormValue `json:"name,omitempty" schema:"name"`
|
||||
}
|
||||
|
||||
type userNamesFormValue struct {
|
||||
FirstName string `json:"firstName,omitempty" schema:"firstName"`
|
||||
LastName string `json:"lastName,omitempty" schema:"lastName"`
|
||||
}
|
||||
|
||||
// FetchUser implements the [idp.Session] interface.
|
||||
// It will execute an OIDC code exchange if needed to retrieve the tokens,
|
||||
// extract the information from the id_token and if available also from the `user` form value.
|
||||
// The information will be mapped into an [idp.User].
|
||||
func (s *Session) FetchUser(ctx context.Context) (user idp.User, err error) {
|
||||
if s.Tokens == nil {
|
||||
if err = s.Authorize(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
info := s.Tokens.IDTokenClaims.GetUserInfo()
|
||||
userName := userFormValue{}
|
||||
if s.UserFormValue != "" {
|
||||
if err = json.Unmarshal([]byte(s.UserFormValue), &userName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return NewUser(info, userName.Name), nil
|
||||
}
|
||||
|
||||
func NewUser(info *openid.UserInfo, names userNamesFormValue) *User {
|
||||
user := oidc.NewUser(info)
|
||||
user.GivenName = names.FirstName
|
||||
user.FamilyName = names.LastName
|
||||
return &User{User: user}
|
||||
}
|
||||
|
||||
// User extends the [oidc.User] by returning the email as preferred_username, since Apple does not return the latter.
|
||||
type User struct {
|
||||
*oidc.User
|
||||
}
|
||||
|
||||
func (u *User) GetPreferredUsername() string {
|
||||
return u.Email
|
||||
}
|
217
internal/idp/providers/apple/session_test.go
Normal file
217
internal/idp/providers/apple/session_test.go
Normal file
@ -0,0 +1,217 @@
|
||||
package apple
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/h2non/gock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
openid "github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/oidc"
|
||||
)
|
||||
|
||||
func TestSession_FetchUser(t *testing.T) {
|
||||
type fields struct {
|
||||
clientID string
|
||||
teamID string
|
||||
keyID string
|
||||
privateKey []byte
|
||||
redirectURI string
|
||||
scopes []string
|
||||
httpMock func()
|
||||
authURL string
|
||||
code string
|
||||
tokens *openid.Tokens[*openid.IDTokenClaims]
|
||||
userFormValue string
|
||||
}
|
||||
type want struct {
|
||||
err error
|
||||
id string
|
||||
firstName string
|
||||
lastName string
|
||||
displayName string
|
||||
nickName string
|
||||
preferredUsername string
|
||||
email string
|
||||
isEmailVerified bool
|
||||
phone string
|
||||
isPhoneVerified bool
|
||||
preferredLanguage language.Tag
|
||||
avatarURL string
|
||||
profile string
|
||||
nonceSupported bool
|
||||
isPrivateEmail bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "unauthenticated session, error",
|
||||
fields: fields{
|
||||
clientID: "clientID",
|
||||
teamID: "teamID",
|
||||
keyID: "keyID",
|
||||
privateKey: []byte(privateKey),
|
||||
redirectURI: "redirectURI",
|
||||
scopes: []string{"openid"},
|
||||
httpMock: func() {},
|
||||
authURL: "https://appleid.apple.com/auth/authorize?client_id=clientID&redirect_uri=redirectURI&response_mode=form_post&response_type=code&scope=openid&state=testState",
|
||||
tokens: nil,
|
||||
},
|
||||
want: want{
|
||||
err: oidc.ErrCodeMissing,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no user param",
|
||||
fields: fields{
|
||||
clientID: "clientID",
|
||||
teamID: "teamID",
|
||||
keyID: "keyID",
|
||||
privateKey: []byte(privateKey),
|
||||
redirectURI: "redirectURI",
|
||||
scopes: []string{"openid"},
|
||||
httpMock: func() {},
|
||||
authURL: "https://appleid.apple.com/auth/authorize?client_id=clientID&redirect_uri=redirectURI&response_mode=form_post&response_type=code&scope=openid&state=testState",
|
||||
tokens: &openid.Tokens[*openid.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: openid.BearerToken,
|
||||
},
|
||||
IDTokenClaims: id_token(),
|
||||
},
|
||||
userFormValue: "",
|
||||
},
|
||||
want: want{
|
||||
id: "sub",
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
displayName: "",
|
||||
nickName: "",
|
||||
preferredUsername: "email",
|
||||
email: "email",
|
||||
isEmailVerified: true,
|
||||
phone: "",
|
||||
isPhoneVerified: false,
|
||||
preferredLanguage: language.Und,
|
||||
avatarURL: "",
|
||||
profile: "",
|
||||
nonceSupported: true,
|
||||
isPrivateEmail: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with user param",
|
||||
fields: fields{
|
||||
clientID: "clientID",
|
||||
teamID: "teamID",
|
||||
keyID: "keyID",
|
||||
privateKey: []byte(privateKey),
|
||||
redirectURI: "redirectURI",
|
||||
scopes: []string{"openid"},
|
||||
httpMock: func() {},
|
||||
authURL: "https://appleid.apple.com/auth/authorize?client_id=clientID&redirect_uri=redirectURI&response_mode=form_post&response_type=code&scope=openid&state=testState",
|
||||
tokens: &openid.Tokens[*openid.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: openid.BearerToken,
|
||||
},
|
||||
IDTokenClaims: id_token(),
|
||||
},
|
||||
userFormValue: `{"name": {"firstName": "firstName", "lastName": "lastName"}}`,
|
||||
},
|
||||
want: want{
|
||||
id: "sub",
|
||||
firstName: "firstName",
|
||||
lastName: "lastName",
|
||||
displayName: "",
|
||||
nickName: "",
|
||||
preferredUsername: "email",
|
||||
email: "email",
|
||||
isEmailVerified: true,
|
||||
phone: "",
|
||||
isPhoneVerified: false,
|
||||
preferredLanguage: language.Und,
|
||||
avatarURL: "",
|
||||
profile: "",
|
||||
nonceSupported: true,
|
||||
isPrivateEmail: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer gock.Off()
|
||||
tt.fields.httpMock()
|
||||
a := assert.New(t)
|
||||
|
||||
// call the real discovery endpoint
|
||||
gock.New(issuer).Get(openid.DiscoveryEndpoint).EnableNetworking()
|
||||
provider, err := New(tt.fields.clientID, tt.fields.teamID, tt.fields.keyID, tt.fields.redirectURI, tt.fields.privateKey, tt.fields.scopes)
|
||||
require.NoError(t, err)
|
||||
|
||||
session := &Session{
|
||||
Session: &oidc.Session{
|
||||
Provider: provider.Provider,
|
||||
AuthURL: tt.fields.authURL,
|
||||
Code: tt.fields.code,
|
||||
Tokens: tt.fields.tokens,
|
||||
},
|
||||
UserFormValue: tt.fields.userFormValue,
|
||||
}
|
||||
|
||||
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())
|
||||
a.Equal(tt.want.firstName, user.GetFirstName())
|
||||
a.Equal(tt.want.lastName, user.GetLastName())
|
||||
a.Equal(tt.want.displayName, user.GetDisplayName())
|
||||
a.Equal(tt.want.nickName, user.GetNickname())
|
||||
a.Equal(tt.want.preferredUsername, user.GetPreferredUsername())
|
||||
a.Equal(domain.EmailAddress(tt.want.email), user.GetEmail())
|
||||
a.Equal(tt.want.isEmailVerified, user.IsEmailVerified())
|
||||
a.Equal(domain.PhoneNumber(tt.want.phone), user.GetPhone())
|
||||
a.Equal(tt.want.isPhoneVerified, user.IsPhoneVerified())
|
||||
a.Equal(tt.want.preferredLanguage, user.GetPreferredLanguage())
|
||||
a.Equal(tt.want.avatarURL, user.GetAvatarURL())
|
||||
a.Equal(tt.want.profile, user.GetProfile())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func id_token() *openid.IDTokenClaims {
|
||||
return &openid.IDTokenClaims{
|
||||
TokenClaims: openid.TokenClaims{
|
||||
Issuer: issuer,
|
||||
Subject: "sub",
|
||||
Audience: []string{"clientID"},
|
||||
Expiration: openid.FromTime(time.Now().Add(1 * time.Hour)),
|
||||
IssuedAt: openid.FromTime(time.Now().Add(-1 * time.Second)),
|
||||
AuthTime: openid.FromTime(time.Now().Add(-1 * time.Second)),
|
||||
Nonce: "nonce",
|
||||
ClientID: "clientID",
|
||||
},
|
||||
UserInfoEmail: openid.UserInfoEmail{
|
||||
Email: "email",
|
||||
EmailVerified: true,
|
||||
},
|
||||
Claims: map[string]any{
|
||||
"nonce_supported": true,
|
||||
"is_private_email": true,
|
||||
},
|
||||
}
|
||||
}
|
@ -77,6 +77,14 @@ func WithSelectAccount() ProviderOpts {
|
||||
}
|
||||
}
|
||||
|
||||
// WithResponseMode sets the `response_mode` params in the auth request
|
||||
func WithResponseMode(mode oidc.ResponseMode) ProviderOpts {
|
||||
return func(p *Provider) {
|
||||
paramOpt := rp.WithResponseModeURLParam(mode)
|
||||
p.authOptions = append(p.authOptions, rp.AuthURLOpt(paramOpt))
|
||||
}
|
||||
}
|
||||
|
||||
type UserInfoMapper func(info *oidc.UserInfo) idp.User
|
||||
|
||||
var DefaultMapper UserInfoMapper = func(info *oidc.UserInfo) idp.User {
|
||||
|
@ -34,7 +34,7 @@ func (s *Session) GetAuthURL() string {
|
||||
// call the userinfo endpoint and map the received information into an [idp.User].
|
||||
func (s *Session) FetchUser(ctx context.Context) (user idp.User, err error) {
|
||||
if s.Tokens == nil {
|
||||
if err = s.authorize(ctx); err != nil {
|
||||
if err = s.Authorize(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@ -54,7 +54,7 @@ func (s *Session) FetchUser(ctx context.Context) (user idp.User, err error) {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (s *Session) authorize(ctx context.Context) (err error) {
|
||||
func (s *Session) Authorize(ctx context.Context) (err error) {
|
||||
if s.Code == "" {
|
||||
return ErrCodeMissing
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ type IDPTemplate struct {
|
||||
*GitLabSelfHostedIDPTemplate
|
||||
*GoogleIDPTemplate
|
||||
*LDAPIDPTemplate
|
||||
*AppleIDPTemplate
|
||||
}
|
||||
|
||||
type IDPTemplates struct {
|
||||
@ -140,6 +141,15 @@ type LDAPIDPTemplate struct {
|
||||
idp.LDAPAttributes
|
||||
}
|
||||
|
||||
type AppleIDPTemplate struct {
|
||||
IDPID string
|
||||
ClientID string
|
||||
TeamID string
|
||||
KeyID string
|
||||
PrivateKey *crypto.CryptoValue
|
||||
Scopes database.StringArray
|
||||
}
|
||||
|
||||
var (
|
||||
idpTemplateTable = table{
|
||||
name: projection.IDPTemplateTable,
|
||||
@ -605,6 +615,41 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
appleIdpTemplateTable = table{
|
||||
name: projection.IDPTemplateAppleTable,
|
||||
instanceIDCol: projection.AppleInstanceIDCol,
|
||||
}
|
||||
AppleIDCol = Column{
|
||||
name: projection.AppleIDCol,
|
||||
table: appleIdpTemplateTable,
|
||||
}
|
||||
AppleInstanceIDCol = Column{
|
||||
name: projection.AppleInstanceIDCol,
|
||||
table: appleIdpTemplateTable,
|
||||
}
|
||||
AppleClientIDCol = Column{
|
||||
name: projection.AppleClientIDCol,
|
||||
table: appleIdpTemplateTable,
|
||||
}
|
||||
AppleTeamIDCol = Column{
|
||||
name: projection.AppleTeamIDCol,
|
||||
table: appleIdpTemplateTable,
|
||||
}
|
||||
AppleKeyIDCol = Column{
|
||||
name: projection.AppleKeyIDCol,
|
||||
table: appleIdpTemplateTable,
|
||||
}
|
||||
ApplePrivateKeyCol = Column{
|
||||
name: projection.ApplePrivateKeyCol,
|
||||
table: appleIdpTemplateTable,
|
||||
}
|
||||
AppleScopesCol = Column{
|
||||
name: projection.AppleScopesCol,
|
||||
table: appleIdpTemplateTable,
|
||||
}
|
||||
)
|
||||
|
||||
// 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)
|
||||
@ -799,6 +844,13 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
|
||||
LDAPPreferredLanguageAttributeCol.identifier(),
|
||||
LDAPAvatarURLAttributeCol.identifier(),
|
||||
LDAPProfileAttributeCol.identifier(),
|
||||
// apple
|
||||
AppleIDCol.identifier(),
|
||||
AppleClientIDCol.identifier(),
|
||||
AppleTeamIDCol.identifier(),
|
||||
AppleKeyIDCol.identifier(),
|
||||
ApplePrivateKeyCol.identifier(),
|
||||
AppleScopesCol.identifier(),
|
||||
).From(idpTemplateTable.identifier()).
|
||||
LeftJoin(join(OAuthIDCol, IDPTemplateIDCol)).
|
||||
LeftJoin(join(OIDCIDCol, IDPTemplateIDCol)).
|
||||
@ -809,7 +861,8 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
|
||||
LeftJoin(join(GitLabIDCol, IDPTemplateIDCol)).
|
||||
LeftJoin(join(GitLabSelfHostedIDCol, IDPTemplateIDCol)).
|
||||
LeftJoin(join(GoogleIDCol, IDPTemplateIDCol)).
|
||||
LeftJoin(join(LDAPIDCol, IDPTemplateIDCol) + db.Timetravel(call.Took(ctx))).
|
||||
LeftJoin(join(LDAPIDCol, IDPTemplateIDCol)).
|
||||
LeftJoin(join(AppleIDCol, IDPTemplateIDCol) + db.Timetravel(call.Took(ctx))).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
func(row *sql.Row) (*IDPTemplate, error) {
|
||||
idpTemplate := new(IDPTemplate)
|
||||
@ -898,6 +951,13 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
|
||||
ldapAvatarURLAttribute := sql.NullString{}
|
||||
ldapProfileAttribute := sql.NullString{}
|
||||
|
||||
appleID := sql.NullString{}
|
||||
appleClientID := sql.NullString{}
|
||||
appleTeamID := sql.NullString{}
|
||||
appleKeyID := sql.NullString{}
|
||||
applePrivateKey := new(crypto.CryptoValue)
|
||||
appleScopes := database.StringArray{}
|
||||
|
||||
err := row.Scan(
|
||||
&idpTemplate.ID,
|
||||
&idpTemplate.ResourceOwner,
|
||||
@ -994,6 +1054,13 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
|
||||
&ldapPreferredLanguageAttribute,
|
||||
&ldapAvatarURLAttribute,
|
||||
&ldapProfileAttribute,
|
||||
// apple
|
||||
&appleID,
|
||||
&appleClientID,
|
||||
&appleTeamID,
|
||||
&appleKeyID,
|
||||
&applePrivateKey,
|
||||
&appleScopes,
|
||||
)
|
||||
if err != nil {
|
||||
if errs.Is(err, sql.ErrNoRows) {
|
||||
@ -1118,6 +1185,16 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
|
||||
},
|
||||
}
|
||||
}
|
||||
if appleID.Valid {
|
||||
idpTemplate.AppleIDPTemplate = &AppleIDPTemplate{
|
||||
IDPID: appleID.String,
|
||||
ClientID: appleClientID.String,
|
||||
TeamID: appleTeamID.String,
|
||||
KeyID: appleKeyID.String,
|
||||
PrivateKey: applePrivateKey,
|
||||
Scopes: appleScopes,
|
||||
}
|
||||
}
|
||||
|
||||
return idpTemplate, nil
|
||||
}
|
||||
@ -1220,6 +1297,14 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
|
||||
LDAPPreferredLanguageAttributeCol.identifier(),
|
||||
LDAPAvatarURLAttributeCol.identifier(),
|
||||
LDAPProfileAttributeCol.identifier(),
|
||||
// apple
|
||||
AppleIDCol.identifier(),
|
||||
AppleClientIDCol.identifier(),
|
||||
AppleTeamIDCol.identifier(),
|
||||
AppleKeyIDCol.identifier(),
|
||||
ApplePrivateKeyCol.identifier(),
|
||||
AppleScopesCol.identifier(),
|
||||
// count
|
||||
countColumn.identifier(),
|
||||
).From(idpTemplateTable.identifier()).
|
||||
LeftJoin(join(OAuthIDCol, IDPTemplateIDCol)).
|
||||
@ -1231,7 +1316,8 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
|
||||
LeftJoin(join(GitLabIDCol, IDPTemplateIDCol)).
|
||||
LeftJoin(join(GitLabSelfHostedIDCol, IDPTemplateIDCol)).
|
||||
LeftJoin(join(GoogleIDCol, IDPTemplateIDCol)).
|
||||
LeftJoin(join(LDAPIDCol, IDPTemplateIDCol) + db.Timetravel(call.Took(ctx))).
|
||||
LeftJoin(join(LDAPIDCol, IDPTemplateIDCol)).
|
||||
LeftJoin(join(AppleIDCol, IDPTemplateIDCol) + db.Timetravel(call.Took(ctx))).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
func(rows *sql.Rows) (*IDPTemplates, error) {
|
||||
templates := make([]*IDPTemplate, 0)
|
||||
@ -1323,6 +1409,13 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
|
||||
ldapAvatarURLAttribute := sql.NullString{}
|
||||
ldapProfileAttribute := sql.NullString{}
|
||||
|
||||
appleID := sql.NullString{}
|
||||
appleClientID := sql.NullString{}
|
||||
appleTeamID := sql.NullString{}
|
||||
appleKeyID := sql.NullString{}
|
||||
applePrivateKey := new(crypto.CryptoValue)
|
||||
appleScopes := database.StringArray{}
|
||||
|
||||
err := rows.Scan(
|
||||
&idpTemplate.ID,
|
||||
&idpTemplate.ResourceOwner,
|
||||
@ -1419,6 +1512,13 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
|
||||
&ldapPreferredLanguageAttribute,
|
||||
&ldapAvatarURLAttribute,
|
||||
&ldapProfileAttribute,
|
||||
// apple
|
||||
&appleID,
|
||||
&appleClientID,
|
||||
&appleTeamID,
|
||||
&appleKeyID,
|
||||
&applePrivateKey,
|
||||
&appleScopes,
|
||||
&count,
|
||||
)
|
||||
|
||||
@ -1542,6 +1642,16 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
|
||||
},
|
||||
}
|
||||
}
|
||||
if appleID.Valid {
|
||||
idpTemplate.AppleIDPTemplate = &AppleIDPTemplate{
|
||||
IDPID: appleID.String,
|
||||
ClientID: appleClientID.String,
|
||||
TeamID: appleTeamID.String,
|
||||
KeyID: appleKeyID.String,
|
||||
PrivateKey: applePrivateKey,
|
||||
Scopes: appleScopes,
|
||||
}
|
||||
}
|
||||
templates = append(templates, idpTemplate)
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,14 @@ var (
|
||||
` projections.idp_templates5_ldap2.phone_verified_attribute,` +
|
||||
` projections.idp_templates5_ldap2.preferred_language_attribute,` +
|
||||
` projections.idp_templates5_ldap2.avatar_url_attribute,` +
|
||||
` projections.idp_templates5_ldap2.profile_attribute` +
|
||||
` projections.idp_templates5_ldap2.profile_attribute,` +
|
||||
// apple
|
||||
` projections.idp_templates5_apple.idp_id,` +
|
||||
` projections.idp_templates5_apple.client_id,` +
|
||||
` projections.idp_templates5_apple.team_id,` +
|
||||
` projections.idp_templates5_apple.key_id,` +
|
||||
` projections.idp_templates5_apple.private_key,` +
|
||||
` projections.idp_templates5_apple.scopes` +
|
||||
` FROM projections.idp_templates5` +
|
||||
` LEFT JOIN projections.idp_templates5_oauth2 ON projections.idp_templates5.id = projections.idp_templates5_oauth2.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_oauth2.instance_id` +
|
||||
` LEFT JOIN projections.idp_templates5_oidc ON projections.idp_templates5.id = projections.idp_templates5_oidc.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_oidc.instance_id` +
|
||||
@ -122,6 +129,7 @@ var (
|
||||
` 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_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'`
|
||||
idpTemplateCols = []string{
|
||||
"id",
|
||||
@ -219,6 +227,13 @@ var (
|
||||
"preferred_language_attribute",
|
||||
"avatar_url_attribute",
|
||||
"profile_attribute",
|
||||
// apple config
|
||||
"idp_id",
|
||||
"client_id",
|
||||
"team_id",
|
||||
"key_id",
|
||||
"private_key",
|
||||
"scopes",
|
||||
}
|
||||
idpTemplatesQuery = `SELECT projections.idp_templates5.id,` +
|
||||
` projections.idp_templates5.resource_owner,` +
|
||||
@ -315,6 +330,13 @@ var (
|
||||
` projections.idp_templates5_ldap2.preferred_language_attribute,` +
|
||||
` projections.idp_templates5_ldap2.avatar_url_attribute,` +
|
||||
` projections.idp_templates5_ldap2.profile_attribute,` +
|
||||
// apple
|
||||
` projections.idp_templates5_apple.idp_id,` +
|
||||
` projections.idp_templates5_apple.client_id,` +
|
||||
` projections.idp_templates5_apple.team_id,` +
|
||||
` projections.idp_templates5_apple.key_id,` +
|
||||
` projections.idp_templates5_apple.private_key,` +
|
||||
` projections.idp_templates5_apple.scopes,` +
|
||||
` COUNT(*) OVER ()` +
|
||||
` FROM projections.idp_templates5` +
|
||||
` LEFT JOIN projections.idp_templates5_oauth2 ON projections.idp_templates5.id = projections.idp_templates5_oauth2.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_oauth2.instance_id` +
|
||||
@ -327,6 +349,7 @@ var (
|
||||
` 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_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'`
|
||||
idpTemplatesCols = []string{
|
||||
"id",
|
||||
@ -424,6 +447,13 @@ var (
|
||||
"preferred_language_attribute",
|
||||
"avatar_url_attribute",
|
||||
"profile_attribute",
|
||||
// apple config
|
||||
"idp_id",
|
||||
"client_id",
|
||||
"team_id",
|
||||
"key_id",
|
||||
"private_key",
|
||||
"scopes",
|
||||
"count",
|
||||
}
|
||||
)
|
||||
@ -560,6 +590,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
),
|
||||
},
|
||||
@ -692,6 +729,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
),
|
||||
},
|
||||
@ -822,6 +866,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
),
|
||||
},
|
||||
@ -951,6 +1002,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
),
|
||||
},
|
||||
@ -1079,6 +1137,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
),
|
||||
},
|
||||
@ -1207,6 +1272,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
),
|
||||
},
|
||||
@ -1336,6 +1408,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
),
|
||||
},
|
||||
@ -1464,6 +1543,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
"lang",
|
||||
"avatar",
|
||||
"profile",
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
),
|
||||
},
|
||||
@ -1509,6 +1595,143 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareIDPTemplateByIDQuery apple 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.IDPTypeApple,
|
||||
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,
|
||||
// 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
|
||||
"idp-id",
|
||||
"client_id",
|
||||
"team_id",
|
||||
"key_id",
|
||||
nil,
|
||||
database.StringArray{"profile"},
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &IDPTemplate{
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211109,
|
||||
ResourceOwner: "ro",
|
||||
ID: "idp-id",
|
||||
State: domain.IDPStateActive,
|
||||
Name: "idp-name",
|
||||
Type: domain.IDPTypeApple,
|
||||
OwnerType: domain.IdentityProviderTypeOrg,
|
||||
IsCreationAllowed: true,
|
||||
IsLinkingAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
AppleIDPTemplate: &AppleIDPTemplate{
|
||||
IDPID: "idp-id",
|
||||
ClientID: "client_id",
|
||||
TeamID: "team_id",
|
||||
KeyID: "key_id",
|
||||
PrivateKey: nil,
|
||||
Scopes: []string{"profile"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareIDPTemplateByIDQuery no config",
|
||||
prepare: prepareIDPTemplateByIDQuery,
|
||||
@ -1612,6 +1835,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
),
|
||||
},
|
||||
@ -1770,6 +2000,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
"lang",
|
||||
"avatar",
|
||||
"profile",
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
},
|
||||
),
|
||||
@ -1927,6 +2164,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
},
|
||||
),
|
||||
@ -2058,6 +2302,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
"lang",
|
||||
"avatar",
|
||||
"profile",
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"idp-id-google",
|
||||
@ -2155,6 +2406,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"idp-id-oauth",
|
||||
@ -2252,6 +2510,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"idp-id-oidc",
|
||||
@ -2349,6 +2614,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"idp-id-jwt",
|
||||
@ -2446,6 +2718,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
},
|
||||
),
|
||||
|
@ -28,6 +28,7 @@ const (
|
||||
IDPTemplateGitLabSelfHostedTable = IDPTemplateTable + "_" + IDPTemplateGitLabSelfHostedSuffix
|
||||
IDPTemplateGoogleTable = IDPTemplateTable + "_" + IDPTemplateGoogleSuffix
|
||||
IDPTemplateLDAPTable = IDPTemplateTable + "_" + IDPTemplateLDAPSuffix
|
||||
IDPTemplateAppleTable = IDPTemplateTable + "_" + IDPTemplateAppleSuffix
|
||||
|
||||
IDPTemplateOAuthSuffix = "oauth2"
|
||||
IDPTemplateOIDCSuffix = "oidc"
|
||||
@ -39,6 +40,7 @@ const (
|
||||
IDPTemplateGitLabSelfHostedSuffix = "gitlab_self_hosted"
|
||||
IDPTemplateGoogleSuffix = "google"
|
||||
IDPTemplateLDAPSuffix = "ldap2"
|
||||
IDPTemplateAppleSuffix = "apple"
|
||||
|
||||
IDPTemplateIDCol = "id"
|
||||
IDPTemplateCreationDateCol = "creation_date"
|
||||
@ -147,6 +149,14 @@ const (
|
||||
LDAPPreferredLanguageAttributeCol = "preferred_language_attribute"
|
||||
LDAPAvatarURLAttributeCol = "avatar_url_attribute"
|
||||
LDAPProfileAttributeCol = "profile_attribute"
|
||||
|
||||
AppleIDCol = "idp_id"
|
||||
AppleInstanceIDCol = "instance_id"
|
||||
AppleClientIDCol = "client_id"
|
||||
AppleTeamIDCol = "team_id"
|
||||
AppleKeyIDCol = "key_id"
|
||||
ApplePrivateKeyCol = "private_key"
|
||||
AppleScopesCol = "scopes"
|
||||
)
|
||||
|
||||
type idpTemplateProjection struct {
|
||||
@ -321,6 +331,19 @@ func newIDPTemplateProjection(ctx context.Context, config crdb.StatementHandlerC
|
||||
IDPTemplateLDAPSuffix,
|
||||
crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys()),
|
||||
),
|
||||
crdb.NewSuffixedTable([]*crdb.Column{
|
||||
crdb.NewColumn(AppleIDCol, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(AppleInstanceIDCol, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(AppleClientIDCol, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(AppleTeamIDCol, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(AppleKeyIDCol, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(ApplePrivateKeyCol, crdb.ColumnTypeJSONB),
|
||||
crdb.NewColumn(AppleScopesCol, crdb.ColumnTypeTextArray, crdb.Nullable()),
|
||||
},
|
||||
crdb.NewPrimaryKey(AppleInstanceIDCol, AppleIDCol),
|
||||
IDPTemplateAppleSuffix,
|
||||
crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys()),
|
||||
),
|
||||
)
|
||||
p.StatementHandler = crdb.NewStatementHandler(ctx, config)
|
||||
return p
|
||||
@ -443,6 +466,14 @@ func (p *idpTemplateProjection) reducers() []handler.AggregateReducer {
|
||||
Event: instance.LDAPIDPChangedEventType,
|
||||
Reduce: p.reduceLDAPIDPChanged,
|
||||
},
|
||||
{
|
||||
Event: instance.AppleIDPAddedEventType,
|
||||
Reduce: p.reduceAppleIDPAdded,
|
||||
},
|
||||
{
|
||||
Event: instance.AppleIDPChangedEventType,
|
||||
Reduce: p.reduceAppleIDPChanged,
|
||||
},
|
||||
{
|
||||
Event: instance.IDPConfigRemovedEventType,
|
||||
Reduce: p.reduceIDPConfigRemoved,
|
||||
@ -572,6 +603,14 @@ func (p *idpTemplateProjection) reducers() []handler.AggregateReducer {
|
||||
Event: org.LDAPIDPChangedEventType,
|
||||
Reduce: p.reduceLDAPIDPChanged,
|
||||
},
|
||||
{
|
||||
Event: org.AppleIDPAddedEventType,
|
||||
Reduce: p.reduceAppleIDPAdded,
|
||||
},
|
||||
{
|
||||
Event: org.AppleIDPChangedEventType,
|
||||
Reduce: p.reduceAppleIDPChanged,
|
||||
},
|
||||
{
|
||||
Event: org.IDPConfigRemovedEventType,
|
||||
Reduce: p.reduceIDPConfigRemoved,
|
||||
@ -1858,6 +1897,97 @@ func (p *idpTemplateProjection) reduceLDAPIDPChanged(event eventstore.Event) (*h
|
||||
ops...,
|
||||
), nil
|
||||
}
|
||||
|
||||
func (p *idpTemplateProjection) reduceAppleIDPAdded(event eventstore.Event) (*handler.Statement, error) {
|
||||
var idpEvent idp.AppleIDPAddedEvent
|
||||
var idpOwnerType domain.IdentityProviderType
|
||||
switch e := event.(type) {
|
||||
case *org.AppleIDPAddedEvent:
|
||||
idpEvent = e.AppleIDPAddedEvent
|
||||
idpOwnerType = domain.IdentityProviderTypeOrg
|
||||
case *instance.AppleIDPAddedEvent:
|
||||
idpEvent = e.AppleIDPAddedEvent
|
||||
idpOwnerType = domain.IdentityProviderTypeSystem
|
||||
default:
|
||||
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-SFvg3", "reduce.wrong.event.type %v", []eventstore.EventType{org.AppleIDPAddedEventType /*, instance.AppleIDPAddedEventType*/})
|
||||
}
|
||||
|
||||
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.IDPTypeApple),
|
||||
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(AppleIDCol, idpEvent.ID),
|
||||
handler.NewCol(AppleInstanceIDCol, idpEvent.Aggregate().InstanceID),
|
||||
handler.NewCol(AppleClientIDCol, idpEvent.ClientID),
|
||||
handler.NewCol(AppleTeamIDCol, idpEvent.TeamID),
|
||||
handler.NewCol(AppleKeyIDCol, idpEvent.KeyID),
|
||||
handler.NewCol(ApplePrivateKeyCol, idpEvent.PrivateKey),
|
||||
handler.NewCol(AppleScopesCol, database.StringArray(idpEvent.Scopes)),
|
||||
},
|
||||
crdb.WithTableSuffix(IDPTemplateAppleSuffix),
|
||||
),
|
||||
), nil
|
||||
}
|
||||
|
||||
func (p *idpTemplateProjection) reduceAppleIDPChanged(event eventstore.Event) (*handler.Statement, error) {
|
||||
var idpEvent idp.AppleIDPChangedEvent
|
||||
switch e := event.(type) {
|
||||
case *org.AppleIDPChangedEvent:
|
||||
idpEvent = e.AppleIDPChangedEvent
|
||||
case *instance.AppleIDPChangedEvent:
|
||||
idpEvent = e.AppleIDPChangedEvent
|
||||
default:
|
||||
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-GBez3", "reduce.wrong.event.type %v", []eventstore.EventType{org.AppleIDPChangedEventType /*, instance.AppleIDPChangedEventType*/})
|
||||
}
|
||||
|
||||
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),
|
||||
},
|
||||
),
|
||||
)
|
||||
appleCols := reduceAppleIDPChangedColumns(idpEvent)
|
||||
if len(appleCols) > 0 {
|
||||
ops = append(ops,
|
||||
crdb.AddUpdateStatement(
|
||||
appleCols,
|
||||
[]handler.Condition{
|
||||
handler.NewCond(AppleIDCol, idpEvent.ID),
|
||||
handler.NewCond(AppleInstanceIDCol, idpEvent.Aggregate().InstanceID),
|
||||
},
|
||||
crdb.WithTableSuffix(IDPTemplateAppleSuffix),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return crdb.NewMultiStatement(
|
||||
&idpEvent,
|
||||
ops...,
|
||||
), nil
|
||||
}
|
||||
|
||||
func (p *idpTemplateProjection) reduceIDPConfigRemoved(event eventstore.Event) (*handler.Statement, error) {
|
||||
var idpEvent idpconfig.IDPConfigRemovedEvent
|
||||
switch e := event.(type) {
|
||||
@ -2176,3 +2306,23 @@ func reduceLDAPIDPChangedColumns(idpEvent idp.LDAPIDPChangedEvent) []handler.Col
|
||||
}
|
||||
return ldapCols
|
||||
}
|
||||
|
||||
func reduceAppleIDPChangedColumns(idpEvent idp.AppleIDPChangedEvent) []handler.Column {
|
||||
appleCols := make([]handler.Column, 0, 5)
|
||||
if idpEvent.ClientID != nil {
|
||||
appleCols = append(appleCols, handler.NewCol(AppleClientIDCol, *idpEvent.ClientID))
|
||||
}
|
||||
if idpEvent.TeamID != nil {
|
||||
appleCols = append(appleCols, handler.NewCol(AppleTeamIDCol, *idpEvent.TeamID))
|
||||
}
|
||||
if idpEvent.KeyID != nil {
|
||||
appleCols = append(appleCols, handler.NewCol(AppleKeyIDCol, *idpEvent.KeyID))
|
||||
}
|
||||
if idpEvent.PrivateKey != nil {
|
||||
appleCols = append(appleCols, handler.NewCol(ApplePrivateKeyCol, *idpEvent.PrivateKey))
|
||||
}
|
||||
if idpEvent.Scopes != nil {
|
||||
appleCols = append(appleCols, handler.NewCol(AppleScopesCol, database.StringArray(idpEvent.Scopes)))
|
||||
}
|
||||
return appleCols
|
||||
}
|
||||
|
@ -2440,6 +2440,268 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIDPTemplateProjection_reducesApple(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 reduceAppleIDPAdded",
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(instance.AppleIDPAddedEventType),
|
||||
instance.AggregateType,
|
||||
[]byte(`{
|
||||
"id": "idp-id",
|
||||
"clientId": "client_id",
|
||||
"teamId": "team_id",
|
||||
"keyId": "key_id",
|
||||
"privateKey": {
|
||||
"cryptoType": 0,
|
||||
"algorithm": "RSA-265",
|
||||
"keyId": "key-id"
|
||||
},
|
||||
"scopes": ["name"],
|
||||
"isCreationAllowed": true,
|
||||
"isLinkingAllowed": true,
|
||||
"isAutoCreation": true,
|
||||
"isAutoUpdate": true
|
||||
}`),
|
||||
), instance.AppleIDPAddedEventMapper),
|
||||
},
|
||||
reduce: (&idpTemplateProjection{}).reduceAppleIDPAdded,
|
||||
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,
|
||||
"",
|
||||
domain.IdentityProviderTypeSystem,
|
||||
domain.IDPTypeApple,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.idp_templates5_apple (idp_id, instance_id, client_id, team_id, key_id, private_key, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
expectedArgs: []interface{}{
|
||||
"idp-id",
|
||||
"instance-id",
|
||||
"client_id",
|
||||
"team_id",
|
||||
"key_id",
|
||||
anyArg{},
|
||||
database.StringArray{"name"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org reduceAppleIDPAdded",
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(org.AppleIDPAddedEventType),
|
||||
org.AggregateType,
|
||||
[]byte(`{
|
||||
"id": "idp-id",
|
||||
"clientId": "client_id",
|
||||
"teamId": "team_id",
|
||||
"keyId": "key_id",
|
||||
"privateKey": {
|
||||
"cryptoType": 0,
|
||||
"algorithm": "RSA-265",
|
||||
"keyId": "key-id"
|
||||
},
|
||||
"scopes": ["name"],
|
||||
"isCreationAllowed": true,
|
||||
"isLinkingAllowed": true,
|
||||
"isAutoCreation": true,
|
||||
"isAutoUpdate": true
|
||||
}`),
|
||||
), org.AppleIDPAddedEventMapper),
|
||||
},
|
||||
reduce: (&idpTemplateProjection{}).reduceAppleIDPAdded,
|
||||
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,
|
||||
"",
|
||||
domain.IdentityProviderTypeOrg,
|
||||
domain.IDPTypeApple,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.idp_templates5_apple (idp_id, instance_id, client_id, team_id, key_id, private_key, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
expectedArgs: []interface{}{
|
||||
"idp-id",
|
||||
"instance-id",
|
||||
"client_id",
|
||||
"team_id",
|
||||
"key_id",
|
||||
anyArg{},
|
||||
database.StringArray{"name"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "instance reduceAppleIDPChanged minimal",
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(instance.AppleIDPChangedEventType),
|
||||
instance.AggregateType,
|
||||
[]byte(`{
|
||||
"id": "idp-id",
|
||||
"isCreationAllowed": true,
|
||||
"clientId": "id"
|
||||
}`),
|
||||
), instance.AppleIDPChangedEventMapper),
|
||||
},
|
||||
reduce: (&idpTemplateProjection{}).reduceAppleIDPChanged,
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("instance"),
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: idpTemplateUpdateMinimalStmt,
|
||||
expectedArgs: []interface{}{
|
||||
true,
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
"idp-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.idp_templates5_apple SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
"id",
|
||||
"idp-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "instance reduceAppleIDPChanged",
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(instance.AppleIDPChangedEventType),
|
||||
instance.AggregateType,
|
||||
[]byte(`{
|
||||
"id": "idp-id",
|
||||
"name": "name",
|
||||
"clientId": "client_id",
|
||||
"teamId": "team_id",
|
||||
"keyId": "key_id",
|
||||
"privateKey": {
|
||||
"cryptoType": 0,
|
||||
"algorithm": "RSA-265",
|
||||
"keyId": "key-id"
|
||||
},
|
||||
"scopes": ["name"],
|
||||
"isCreationAllowed": true,
|
||||
"isLinkingAllowed": true,
|
||||
"isAutoCreation": true,
|
||||
"isAutoUpdate": true
|
||||
}`),
|
||||
), instance.AppleIDPChangedEventMapper),
|
||||
},
|
||||
reduce: (&idpTemplateProjection{}).reduceAppleIDPChanged,
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("instance"),
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: idpTemplateUpdateStmt,
|
||||
expectedArgs: []interface{}{
|
||||
"name",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
"idp-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.idp_templates5_apple SET (client_id, team_id, key_id, private_key, scopes) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)",
|
||||
expectedArgs: []interface{}{
|
||||
"client_id",
|
||||
"team_id",
|
||||
"key_id",
|
||||
anyArg{},
|
||||
database.StringArray{"name"},
|
||||
"idp-id",
|
||||
"instance-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
|
||||
|
164
internal/repository/idp/apple.go
Normal file
164
internal/repository/idp/apple.go
Normal file
@ -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 AppleIDPAddedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
ClientID string `json:"clientId"`
|
||||
TeamID string `json:"teamId"`
|
||||
KeyID string `json:"keyId"`
|
||||
PrivateKey *crypto.CryptoValue `json:"privateKey"`
|
||||
Scopes []string `json:"scopes,omitempty"`
|
||||
Options
|
||||
}
|
||||
|
||||
func NewAppleIDPAddedEvent(
|
||||
base *eventstore.BaseEvent,
|
||||
id,
|
||||
name,
|
||||
clientID,
|
||||
teamID,
|
||||
keyID string,
|
||||
privateKey *crypto.CryptoValue,
|
||||
scopes []string,
|
||||
options Options,
|
||||
) *AppleIDPAddedEvent {
|
||||
return &AppleIDPAddedEvent{
|
||||
BaseEvent: *base,
|
||||
ID: id,
|
||||
Name: name,
|
||||
ClientID: clientID,
|
||||
TeamID: teamID,
|
||||
KeyID: keyID,
|
||||
PrivateKey: privateKey,
|
||||
Scopes: scopes,
|
||||
Options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *AppleIDPAddedEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *AppleIDPAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func AppleIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
e := &AppleIDPAddedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
|
||||
err := json.Unmarshal(event.Data, e)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "IDP-Beqss", "unable to unmarshal event")
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
type AppleIDPChangedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
ID string `json:"id"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
ClientID *string `json:"clientId,omitempty"`
|
||||
TeamID *string `json:"teamId,omitempty"`
|
||||
KeyID *string `json:"keyId,omitempty"`
|
||||
PrivateKey *crypto.CryptoValue `json:"privateKey,omitempty"`
|
||||
Scopes []string `json:"scopes,omitempty"`
|
||||
OptionChanges
|
||||
}
|
||||
|
||||
func NewAppleIDPChangedEvent(
|
||||
base *eventstore.BaseEvent,
|
||||
id string,
|
||||
changes []AppleIDPChanges,
|
||||
) (*AppleIDPChangedEvent, error) {
|
||||
if len(changes) == 0 {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "IDP-SF3h2", "Errors.NoChangesFound")
|
||||
}
|
||||
changedEvent := &AppleIDPChangedEvent{
|
||||
BaseEvent: *base,
|
||||
ID: id,
|
||||
}
|
||||
for _, change := range changes {
|
||||
change(changedEvent)
|
||||
}
|
||||
return changedEvent, nil
|
||||
}
|
||||
|
||||
type AppleIDPChanges func(*AppleIDPChangedEvent)
|
||||
|
||||
func ChangeAppleName(name string) func(*AppleIDPChangedEvent) {
|
||||
return func(e *AppleIDPChangedEvent) {
|
||||
e.Name = &name
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeAppleClientID(clientID string) func(*AppleIDPChangedEvent) {
|
||||
return func(e *AppleIDPChangedEvent) {
|
||||
e.ClientID = &clientID
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeAppleTeamID(teamID string) func(*AppleIDPChangedEvent) {
|
||||
return func(e *AppleIDPChangedEvent) {
|
||||
e.TeamID = &teamID
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeAppleKeyID(keyID string) func(*AppleIDPChangedEvent) {
|
||||
return func(e *AppleIDPChangedEvent) {
|
||||
e.KeyID = &keyID
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeApplePrivateKey(privateKey *crypto.CryptoValue) func(*AppleIDPChangedEvent) {
|
||||
return func(e *AppleIDPChangedEvent) {
|
||||
e.PrivateKey = privateKey
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeAppleScopes(scopes []string) func(*AppleIDPChangedEvent) {
|
||||
return func(e *AppleIDPChangedEvent) {
|
||||
e.Scopes = scopes
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeAppleOptions(options OptionChanges) func(*AppleIDPChangedEvent) {
|
||||
return func(e *AppleIDPChangedEvent) {
|
||||
e.OptionChanges = options
|
||||
}
|
||||
}
|
||||
|
||||
func (e *AppleIDPChangedEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *AppleIDPChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func AppleIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
e := &AppleIDPChangedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
|
||||
err := json.Unmarshal(event.Data, e)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "IDP-NBe1s", "unable to unmarshal event")
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
@ -92,6 +92,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
|
||||
RegisterFilterEventMapper(AggregateType, GoogleIDPChangedEventType, GoogleIDPChangedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, LDAPIDPAddedEventType, LDAPIDPAddedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, LDAPIDPChangedEventType, LDAPIDPChangedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, AppleIDPAddedEventType, AppleIDPAddedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, AppleIDPChangedEventType, AppleIDPChangedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, IDPRemovedEventType, IDPRemovedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, LoginPolicyIDPProviderAddedEventType, IdentityProviderAddedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, LoginPolicyIDPProviderRemovedEventType, IdentityProviderRemovedEventMapper).
|
||||
|
@ -33,6 +33,8 @@ const (
|
||||
GoogleIDPChangedEventType eventstore.EventType = "instance.idp.google.changed"
|
||||
LDAPIDPAddedEventType eventstore.EventType = "instance.idp.ldap.v2.added"
|
||||
LDAPIDPChangedEventType eventstore.EventType = "instance.idp.ldap.v2.changed"
|
||||
AppleIDPAddedEventType eventstore.EventType = "instance.idp.apple.added"
|
||||
AppleIDPChangedEventType eventstore.EventType = "instance.idp.apple.changed"
|
||||
IDPRemovedEventType eventstore.EventType = "instance.idp.removed"
|
||||
)
|
||||
|
||||
@ -920,6 +922,86 @@ func LDAPIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error
|
||||
return &LDAPIDPChangedEvent{LDAPIDPChangedEvent: *e.(*idp.LDAPIDPChangedEvent)}, nil
|
||||
}
|
||||
|
||||
type AppleIDPAddedEvent struct {
|
||||
idp.AppleIDPAddedEvent
|
||||
}
|
||||
|
||||
func NewAppleIDPAddedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
id,
|
||||
name,
|
||||
clientID,
|
||||
teamID,
|
||||
keyID string,
|
||||
privateKey *crypto.CryptoValue,
|
||||
scopes []string,
|
||||
options idp.Options,
|
||||
) *AppleIDPAddedEvent {
|
||||
|
||||
return &AppleIDPAddedEvent{
|
||||
AppleIDPAddedEvent: *idp.NewAppleIDPAddedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
AppleIDPAddedEventType,
|
||||
),
|
||||
id,
|
||||
name,
|
||||
clientID,
|
||||
teamID,
|
||||
keyID,
|
||||
privateKey,
|
||||
scopes,
|
||||
options,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func AppleIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
e, err := idp.AppleIDPAddedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AppleIDPAddedEvent{AppleIDPAddedEvent: *e.(*idp.AppleIDPAddedEvent)}, nil
|
||||
}
|
||||
|
||||
type AppleIDPChangedEvent struct {
|
||||
idp.AppleIDPChangedEvent
|
||||
}
|
||||
|
||||
func NewAppleIDPChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
id string,
|
||||
changes []idp.AppleIDPChanges,
|
||||
) (*AppleIDPChangedEvent, error) {
|
||||
|
||||
changedEvent, err := idp.NewAppleIDPChangedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
AppleIDPChangedEventType,
|
||||
),
|
||||
id,
|
||||
changes,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &AppleIDPChangedEvent{AppleIDPChangedEvent: *changedEvent}, nil
|
||||
}
|
||||
|
||||
func AppleIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
e, err := idp.AppleIDPChangedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AppleIDPChangedEvent{AppleIDPChangedEvent: *e.(*idp.AppleIDPChangedEvent)}, nil
|
||||
}
|
||||
|
||||
type IDPRemovedEvent struct {
|
||||
idp.RemovedEvent
|
||||
}
|
||||
|
@ -101,6 +101,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
|
||||
RegisterFilterEventMapper(AggregateType, GoogleIDPChangedEventType, GoogleIDPChangedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, LDAPIDPAddedEventType, LDAPIDPAddedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, LDAPIDPChangedEventType, LDAPIDPChangedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, AppleIDPAddedEventType, AppleIDPAddedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, AppleIDPChangedEventType, AppleIDPChangedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, IDPRemovedEventType, IDPRemovedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, TriggerActionsSetEventType, TriggerActionsSetEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, TriggerActionsCascadeRemovedEventType, TriggerActionsCascadeRemovedEventMapper).
|
||||
|
@ -33,6 +33,8 @@ const (
|
||||
GoogleIDPChangedEventType eventstore.EventType = "org.idp.google.changed"
|
||||
LDAPIDPAddedEventType eventstore.EventType = "org.idp.ldap.added"
|
||||
LDAPIDPChangedEventType eventstore.EventType = "org.idp.ldap.changed"
|
||||
AppleIDPAddedEventType eventstore.EventType = "org.idp.apple.added"
|
||||
AppleIDPChangedEventType eventstore.EventType = "org.idp.apple.changed"
|
||||
IDPRemovedEventType eventstore.EventType = "org.idp.removed"
|
||||
)
|
||||
|
||||
@ -920,6 +922,86 @@ func LDAPIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error
|
||||
return &LDAPIDPChangedEvent{LDAPIDPChangedEvent: *e.(*idp.LDAPIDPChangedEvent)}, nil
|
||||
}
|
||||
|
||||
type AppleIDPAddedEvent struct {
|
||||
idp.AppleIDPAddedEvent
|
||||
}
|
||||
|
||||
func NewAppleIDPAddedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
id,
|
||||
name,
|
||||
clientID,
|
||||
teamID,
|
||||
keyID string,
|
||||
privateKey *crypto.CryptoValue,
|
||||
scopes []string,
|
||||
options idp.Options,
|
||||
) *AppleIDPAddedEvent {
|
||||
|
||||
return &AppleIDPAddedEvent{
|
||||
AppleIDPAddedEvent: *idp.NewAppleIDPAddedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
AppleIDPAddedEventType,
|
||||
),
|
||||
id,
|
||||
name,
|
||||
clientID,
|
||||
teamID,
|
||||
keyID,
|
||||
privateKey,
|
||||
scopes,
|
||||
options,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func AppleIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
e, err := idp.AppleIDPAddedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AppleIDPAddedEvent{AppleIDPAddedEvent: *e.(*idp.AppleIDPAddedEvent)}, nil
|
||||
}
|
||||
|
||||
type AppleIDPChangedEvent struct {
|
||||
idp.AppleIDPChangedEvent
|
||||
}
|
||||
|
||||
func NewAppleIDPChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
id string,
|
||||
changes []idp.AppleIDPChanges,
|
||||
) (*AppleIDPChangedEvent, error) {
|
||||
|
||||
changedEvent, err := idp.NewAppleIDPChangedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
AppleIDPChangedEventType,
|
||||
),
|
||||
id,
|
||||
changes,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &AppleIDPChangedEvent{AppleIDPChangedEvent: *changedEvent}, nil
|
||||
}
|
||||
|
||||
func AppleIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
e, err := idp.AppleIDPChangedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AppleIDPChangedEvent{AppleIDPChangedEvent: *e.(*idp.AppleIDPChangedEvent)}, nil
|
||||
}
|
||||
|
||||
type IDPRemovedEvent struct {
|
||||
idp.RemovedEvent
|
||||
}
|
||||
|
@ -201,6 +201,10 @@ Errors:
|
||||
InvalidCharacter: 'Само буквено-цифрови знаци, . '
|
||||
IDP:
|
||||
InvalidSearchQuery: Невалидна заявка за търсене
|
||||
ClientIDMissing: Липсва ClientID
|
||||
TeamIDMissing: TeamID липсва
|
||||
KeyIDMissing: Липсва KeyID
|
||||
PrivateKeyMissing: Липсва частен ключ
|
||||
LoginPolicy:
|
||||
NotFound: Правилата за влизане не са намерени
|
||||
Invalid: Правилата за влизане са невалидни
|
||||
|
@ -199,6 +199,10 @@ Errors:
|
||||
InvalidCharacter: Nur alphanumerische Zeichen, . und - sind für eine Domäne erlaubt
|
||||
IDP:
|
||||
InvalidSearchQuery: Ungültiger Suchparameter
|
||||
ClientIDMissing: ClientID fehlt
|
||||
TeamIDMissing: TeamID fehlt
|
||||
KeyIDMissing: KeyID fehlt
|
||||
PrivateKeyMissing: Private Key fehlt
|
||||
LoginPolicy:
|
||||
NotFound: Login Policy konnte nicht gefunden werden
|
||||
Invalid: Login Policy ist ungültig
|
||||
|
@ -199,6 +199,10 @@ Errors:
|
||||
InvalidCharacter: Only alphanumeric characters, . and - are allowed for a domain
|
||||
IDP:
|
||||
InvalidSearchQuery: Invalid search query
|
||||
ClientIDMissing: ClientID missing
|
||||
TeamIDMissing: TeamID missing
|
||||
KeyIDMissing: KeyID missing
|
||||
PrivateKeyMissing: Private Key missing
|
||||
LoginPolicy:
|
||||
NotFound: Login Policy not found
|
||||
Invalid: Login Policy is invalid
|
||||
|
@ -199,6 +199,10 @@ Errors:
|
||||
InvalidCharacter: Solo caracteres alfanuméricos, . y - se permiten para un dominio
|
||||
IDP:
|
||||
InvalidSearchQuery: Consulta de búsqueda no válida
|
||||
ClientIDMissing: Falta ClientID
|
||||
TeamIDMissing: Falta TeamID
|
||||
KeyIDMissing: Falta KeyID
|
||||
PrivateKeyMissing: Falta la clave privada
|
||||
LoginPolicy:
|
||||
NotFound: Política de inicio de sesión no encontrada
|
||||
Invalid: Política de inicio de sesión no es válida
|
||||
|
@ -199,6 +199,10 @@ Errors:
|
||||
InvalidCharacter: Seuls les caractères alphanumériques, . et - sont autorisés pour un domaine
|
||||
IDP:
|
||||
InvalidSearchQuery: Paramètre de recherche non valide
|
||||
ClientIDMissing: ID client manquant
|
||||
TeamIDMissing: TeamID manquant
|
||||
KeyIDMissing: ID de clé manquant
|
||||
PrivateKeyMissing: clé privée manquante
|
||||
LoginPolicy:
|
||||
NotFound: Politique de connexion non trouvée
|
||||
Invalid: La politique de connexion n'est pas valide
|
||||
|
@ -199,6 +199,10 @@ Errors:
|
||||
IDP:
|
||||
InvalidSearchQuery: Parametro di ricerca non valido
|
||||
InvalidCharacter: Per un dominio sono ammessi solo caratteri alfanumerici, . e -
|
||||
ClientIDMissing: ClientID mancante
|
||||
TeamIDMissing: TeamID mancante
|
||||
KeyIDMissing: ID chiave mancante
|
||||
PrivateKeyMissing: Chiave privata mancante
|
||||
LoginPolicy:
|
||||
NotFound: Impostazioni di accesso non trovati
|
||||
Invalid: Impostazioni di accesso non sono validi
|
||||
|
@ -191,6 +191,10 @@ Errors:
|
||||
InvalidCharacter: ドメインは英数字、'.'、'-'のみ使用可能です。
|
||||
IDP:
|
||||
InvalidSearchQuery: 無効な検索クエリです
|
||||
ClientIDMissing: クライアントIDがありません
|
||||
TeamIDMissing: チームIDがありません
|
||||
KeyIDMissing: キーIDがありません
|
||||
PrivateKeyMissing: 秘密キーがありません
|
||||
LoginPolicy:
|
||||
NotFound: ログインポリシーが見つかりません
|
||||
Invalid: 無効なログインポリシーです
|
||||
|
@ -199,6 +199,10 @@ Errors:
|
||||
InvalidCharacter: Дозволени се само алфанумерички знаци, . и - се дозволени за домен
|
||||
IDP:
|
||||
InvalidSearchQuery: Невалидно пребарување
|
||||
ClientID Missing: ClientID недостасува
|
||||
TeamIDMissing: TeamID недостасува
|
||||
Клучен ID Недостасува: Недостасува ID на клуч
|
||||
PrivateKeyMissing: Недостасува приватен клуч
|
||||
LoginPolicy:
|
||||
NotFound: Политиката за најавување не е пронајдена
|
||||
Invalid: Политиката за најавување е невалидна
|
||||
|
@ -199,6 +199,10 @@ Errors:
|
||||
InvalidCharacter: Tylko znaki alfanumeryczne, . i - są dozwolone dla domeny
|
||||
IDP:
|
||||
InvalidSearchQuery: Nieprawidłowe zapytanie wyszukiwania
|
||||
ClientIDMissing: Brak ClientID
|
||||
TeamIDMissing: Brak TeamID
|
||||
KeyIDMissing: Brak KeyID
|
||||
PrivateKeyMissing: Brak klucza prywatnego
|
||||
LoginPolicy:
|
||||
NotFound: Polityka logowania nie znaleziona
|
||||
Invalid: Polityka logowania jest nieprawidłowa
|
||||
|
@ -197,6 +197,10 @@ Errors:
|
||||
InvalidCharacter: Apenas caracteres alfanuméricos, . e - são permitidos para um domínio
|
||||
IDP:
|
||||
InvalidSearchQuery: Consulta de pesquisa inválida
|
||||
ClientIDMissing: ClientID ausente
|
||||
TeamIDMissing: TeamID ausente
|
||||
KeyIDMissing: KeyID ausente
|
||||
PrivateKeyMissing: Chave privada ausente
|
||||
LoginPolicy:
|
||||
NotFound: Política de login não encontrada
|
||||
Invalid: Política de login é inválida
|
||||
|
@ -199,6 +199,10 @@ Errors:
|
||||
InvalidCharacter: 只有字母数字字符,.和 - 允许用于域名中
|
||||
IDP:
|
||||
InvalidSearchQuery: 无效的搜索查询
|
||||
ClientIDMissing: 客户端 ID 丢失
|
||||
TeamIDMissing: 团队 ID 丢失
|
||||
KeyIDMissing: 密钥 ID 丢失
|
||||
PrivateKeyMissing: 私钥丢失
|
||||
LoginPolicy:
|
||||
NotFound: 未找到登录策略
|
||||
Invalid: 登录策略无效
|
||||
|
@ -1648,6 +1648,42 @@ service AdminService {
|
||||
};
|
||||
}
|
||||
|
||||
// Add a new Apple identity provider on the instance
|
||||
rpc AddAppleProvider(AddAppleProviderRequest) returns (AddAppleProviderResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/idps/apple"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "iam.idp.write"
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "Identity Providers";
|
||||
summary: "Add Apple Identity Provider";
|
||||
description: "";
|
||||
};
|
||||
}
|
||||
|
||||
// Change an existing Apple identity provider on the instance
|
||||
rpc UpdateAppleProvider(UpdateAppleProviderRequest) returns (UpdateAppleProviderResponse) {
|
||||
option (google.api.http) = {
|
||||
put: "/idps/apple/{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 Apple Identity Provider";
|
||||
description: "";
|
||||
};
|
||||
}
|
||||
|
||||
// Remove an identity provider
|
||||
// Will remove all linked providers of this configuration on the users
|
||||
rpc DeleteProvider(DeleteProviderRequest) returns (DeleteProviderResponse) {
|
||||
@ -5564,6 +5600,134 @@ message UpdateLDAPProviderResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
message AddAppleProviderRequest {
|
||||
// Apple will be used as default, if no name is provided
|
||||
string name = 1 [
|
||||
(validate.rules).string = {max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
max_length: 200;
|
||||
example: "\"Apple\"";
|
||||
description: "Apple will be used as default, if no name is provided";
|
||||
}
|
||||
];
|
||||
string client_id = 2 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
min_length: 1;
|
||||
max_length: 200;
|
||||
example: "\"client-id\"";
|
||||
description: "Client id (App ID or Service ID) provided by Apple";
|
||||
}
|
||||
];
|
||||
string team_id = 3 [
|
||||
(validate.rules).string = {len: 10},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
min_length: 10;
|
||||
max_length: 10;
|
||||
example: "\"ALT03JV3OS\"";
|
||||
description: "(10-character) Team ID provided by Apple";
|
||||
}
|
||||
];
|
||||
string key_id = 4 [
|
||||
(validate.rules).string = {len: 10},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
min_length: 10;
|
||||
max_length: 10;
|
||||
example: "\"OGKDK25KD\"";
|
||||
description: "(10-character) ID of the private key generated by Apple";
|
||||
}
|
||||
];
|
||||
bytes private_key = 5 [
|
||||
(validate.rules).bytes = {min_len: 1, max_len: 5000},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
min_length: 1;
|
||||
max_length: 5000;
|
||||
example: "\"LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1...\"";
|
||||
description: "Private Key generated by Apple";
|
||||
}
|
||||
];
|
||||
repeated string scopes = 6 [
|
||||
(validate.rules).repeated = {max_items: 20, items: {string: {min_len: 1, max_len: 100}}},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
max_items: 20,
|
||||
example: "[\"name\", \"email\"]";
|
||||
description: "The scopes requested by ZITADEL during the request to Apple";
|
||||
}
|
||||
];
|
||||
zitadel.idp.v1.Options provider_options = 7;
|
||||
}
|
||||
|
||||
message AddAppleProviderResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
string id = 2;
|
||||
}
|
||||
|
||||
message UpdateAppleProviderRequest {
|
||||
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
string name = 2 [
|
||||
(validate.rules).string = {max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
max_length: 200,
|
||||
example: "\"Apple\"";
|
||||
}
|
||||
];
|
||||
string client_id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
min_length: 1;
|
||||
max_length: 200;
|
||||
example: "\"client-id\"";
|
||||
description: "Client id (App ID or Service ID) provided by Apple";
|
||||
}
|
||||
];
|
||||
string team_id = 4 [
|
||||
(validate.rules).string = {len: 10},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
min_length: 10;
|
||||
max_length: 10;
|
||||
example: "\"ALT03JV3OS\"";
|
||||
description: "(10-character) Team ID provided by Apple";
|
||||
}
|
||||
];
|
||||
string key_id = 5 [
|
||||
(validate.rules).string = {len: 10},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
min_length: 10;
|
||||
max_length: 10;
|
||||
example: "\"OGKDK25KD\"";
|
||||
description: "(10-character) ID of the private key generated by Apple";
|
||||
}
|
||||
];
|
||||
bytes private_key = 6 [
|
||||
(validate.rules).bytes = {max_len: 5000},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
max_length: 5000,
|
||||
example: "\"LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1...\"";
|
||||
description: "Private Key generated by Apple";
|
||||
}
|
||||
];
|
||||
repeated string scopes = 7 [
|
||||
(validate.rules).repeated = {max_items: 20, items: {string: {min_len: 1, max_len: 100}}},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
max_items: 20,
|
||||
example: "[\"openid\", \"profile\", \"email\"]";
|
||||
description: "The scopes requested by ZITADEL during the request to Apple";
|
||||
}
|
||||
];
|
||||
zitadel.idp.v1.Options provider_options = 8;
|
||||
}
|
||||
|
||||
message UpdateAppleProviderResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
message DeleteProviderRequest {
|
||||
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
}
|
||||
|
@ -266,6 +266,7 @@ enum ProviderType {
|
||||
PROVIDER_TYPE_GITLAB = 8;
|
||||
PROVIDER_TYPE_GITLAB_SELF_HOSTED = 9;
|
||||
PROVIDER_TYPE_GOOGLE = 10;
|
||||
PROVIDER_TYPE_APPLE = 11;
|
||||
}
|
||||
|
||||
message ProviderConfig {
|
||||
@ -281,6 +282,7 @@ message ProviderConfig {
|
||||
GitLabConfig gitlab = 9;
|
||||
GitLabSelfHostedConfig gitlab_self_hosted = 10;
|
||||
AzureADConfig azure_ad = 11;
|
||||
AppleConfig apple = 12;
|
||||
}
|
||||
}
|
||||
|
||||
@ -517,3 +519,30 @@ message AzureADTenant {
|
||||
string tenant_id = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message AppleConfig {
|
||||
string client_id = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"com.client.id\"";
|
||||
description: "Client id (App ID or Service ID) provided by Apple";
|
||||
}
|
||||
];
|
||||
string team_id = 2 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"ALT03JV3OS\"";
|
||||
description: "Team ID provided by Apple";
|
||||
}
|
||||
];
|
||||
string key_id = 3 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"OGKDK25KD\"";
|
||||
description: "ID of the private key generated by Apple";
|
||||
}
|
||||
];
|
||||
repeated string scopes = 4 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "[\"name\", \"email\"]";
|
||||
description: "the scopes requested by ZITADEL during the request to Apple";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -7058,6 +7058,42 @@ service ManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
// Add a new Apple identity provider in the organization
|
||||
rpc AddAppleProvider(AddAppleProviderRequest) returns (AddAppleProviderResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/idps/apple"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "org.idp.write"
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "Identity Providers";
|
||||
summary: "Add Apple Identity Provider";
|
||||
description: "";
|
||||
};
|
||||
}
|
||||
|
||||
// Change an existing Apple identity provider in the organization
|
||||
rpc UpdateAppleProvider(UpdateAppleProviderRequest) returns (UpdateAppleProviderResponse) {
|
||||
option (google.api.http) = {
|
||||
put: "/idps/apple/{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 Apple Identity Provider";
|
||||
description: "";
|
||||
};
|
||||
}
|
||||
|
||||
// Remove an identity provider
|
||||
// Will remove all linked providers of this configuration on the users
|
||||
rpc DeleteProvider(DeleteProviderRequest) returns (DeleteProviderResponse) {
|
||||
@ -12448,6 +12484,134 @@ message UpdateLDAPProviderResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
message AddAppleProviderRequest {
|
||||
// Apple will be used as default, if no name is provided
|
||||
string name = 1 [
|
||||
(validate.rules).string = {max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
max_length: 200;
|
||||
example: "\"Apple\"";
|
||||
description: "Apple will be used as default, if no name is provided";
|
||||
}
|
||||
];
|
||||
string client_id = 2 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
min_length: 1;
|
||||
max_length: 200;
|
||||
example: "\"com.client.id\"";
|
||||
description: "Client id (App ID or Service ID) provided by Apple";
|
||||
}
|
||||
];
|
||||
string team_id = 3 [
|
||||
(validate.rules).string = {len: 10},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
min_length: 10;
|
||||
max_length: 10;
|
||||
example: "\"ALT03JV3OS\"";
|
||||
description: "(10-character) Team ID provided by Apple";
|
||||
}
|
||||
];
|
||||
string key_id = 4 [
|
||||
(validate.rules).string = {len: 10},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
min_length: 10;
|
||||
max_length: 10;
|
||||
example: "\"OGKDK25KD\"";
|
||||
description: "(10-character) ID of the private key generated by Apple";
|
||||
}
|
||||
];
|
||||
bytes private_key = 5 [
|
||||
(validate.rules).bytes = {min_len: 1, max_len: 5000},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
min_length: 1;
|
||||
max_length: 5000;
|
||||
example: "\"LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1...\"";
|
||||
description: "Private Key generated by Apple";
|
||||
}
|
||||
];
|
||||
repeated string scopes = 6 [
|
||||
(validate.rules).repeated = {max_items: 20, items: {string: {min_len: 1, max_len: 100}}},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
max_items: 20,
|
||||
example: "[\"name\", \"email\"]";
|
||||
description: "The scopes requested by ZITADEL during the request to Apple";
|
||||
}
|
||||
];
|
||||
zitadel.idp.v1.Options provider_options = 7;
|
||||
}
|
||||
|
||||
message AddAppleProviderResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
string id = 2;
|
||||
}
|
||||
|
||||
message UpdateAppleProviderRequest {
|
||||
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
string name = 2 [
|
||||
(validate.rules).string = {max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
max_length: 200,
|
||||
example: "\"Apple\"";
|
||||
}
|
||||
];
|
||||
string client_id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
min_length: 1;
|
||||
max_length: 200,
|
||||
example: "\"client-id\"";
|
||||
description: "Client id (App ID or Service ID) provided by Apple";
|
||||
}
|
||||
];
|
||||
string team_id = 4 [
|
||||
(validate.rules).string = {len: 10},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
min_length: 10;
|
||||
max_length: 10;
|
||||
example: "\"ALT03JV3OS\"";
|
||||
description: "(10-character) Team ID provided by Apple";
|
||||
}
|
||||
];
|
||||
string key_id = 5 [
|
||||
(validate.rules).string = {len: 10},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
min_length: 10;
|
||||
max_length: 10;
|
||||
example: "\"OGKDK25KD\"";
|
||||
description: "(10-character) ID of the private key generated by Apple";
|
||||
}
|
||||
];
|
||||
bytes private_key = 6 [
|
||||
(validate.rules).bytes = {max_len: 5000},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
max_length: 5000,
|
||||
example: "\"LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1...\"";
|
||||
description: "Private Key generated by Apple";
|
||||
}
|
||||
];
|
||||
repeated string scopes = 7 [
|
||||
(validate.rules).repeated = {max_items: 20, items: {string: {min_len: 1, max_len: 100}}},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
max_items: 20,
|
||||
example: "[\"openid\", \"profile\", \"email\"]";
|
||||
description: "The scopes requested by ZITADEL during the request to Apple";
|
||||
}
|
||||
];
|
||||
zitadel.idp.v1.Options provider_options = 8;
|
||||
}
|
||||
|
||||
message UpdateAppleProviderResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
message DeleteProviderRequest {
|
||||
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user