mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-29 18:42:15 +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 <[email protected]>
This commit is contained in:
co-authored by
peintnermax
parent
0d94947d3c
commit
e17b49e4ca
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user