mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-15 02:15:14 +00:00
feat(saml): implementation of saml for ZITADEL v2 (#3618)
This commit is contained in:
@@ -5,6 +5,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { AbstractControl, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { Buffer } from 'buffer';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
import { debounceTime, takeUntil } from 'rxjs/operators';
|
||||
import { RadioItemAuthType } from 'src/app/modules/app-radio/app-auth-method-radio/app-auth-method-radio.component';
|
||||
@@ -14,12 +15,14 @@ import {
|
||||
OIDCAuthMethodType,
|
||||
OIDCGrantType,
|
||||
OIDCResponseType,
|
||||
SAMLConfig,
|
||||
} from 'src/app/proto/generated/zitadel/app_pb';
|
||||
import {
|
||||
AddAPIAppRequest,
|
||||
AddAPIAppResponse,
|
||||
AddOIDCAppRequest,
|
||||
AddOIDCAppResponse,
|
||||
AddSAMLAppRequest,
|
||||
} from 'src/app/proto/generated/zitadel/management_pb';
|
||||
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
@@ -35,7 +38,9 @@ import {
|
||||
PKCE_METHOD,
|
||||
POST_METHOD,
|
||||
} from '../authmethods';
|
||||
import { API_TYPE, AppCreateType, NATIVE_TYPE, RadioItemAppType, USER_AGENT_TYPE, WEB_TYPE } from '../authtypes';
|
||||
import { API_TYPE, AppCreateType, NATIVE_TYPE, RadioItemAppType, SAML_TYPE, USER_AGENT_TYPE, WEB_TYPE } from '../authtypes';
|
||||
|
||||
const MAX_ALLOWED_SIZE = 1 * 1024 * 1024;
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-app-create',
|
||||
@@ -49,11 +54,11 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
public projectId: string = '';
|
||||
public loading: boolean = false;
|
||||
|
||||
public createSteps: number = 4;
|
||||
public currentCreateStep: number = 1;
|
||||
|
||||
public oidcAppRequest: AddOIDCAppRequest.AsObject = new AddOIDCAppRequest().toObject();
|
||||
public apiAppRequest: AddAPIAppRequest.AsObject = new AddAPIAppRequest().toObject();
|
||||
public oidcAppRequest: AddOIDCAppRequest = new AddOIDCAppRequest();
|
||||
public apiAppRequest: AddAPIAppRequest = new AddAPIAppRequest();
|
||||
public samlAppRequest: AddSAMLAppRequest = new AddSAMLAppRequest();
|
||||
|
||||
public oidcResponseTypes: { type: OIDCResponseType; checked: boolean; disabled: boolean }[] = [
|
||||
{ type: OIDCResponseType.OIDC_RESPONSE_TYPE_CODE, checked: false, disabled: false },
|
||||
@@ -66,7 +71,7 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
OIDCAppType.OIDC_APP_TYPE_NATIVE,
|
||||
OIDCAppType.OIDC_APP_TYPE_USER_AGENT,
|
||||
];
|
||||
public appTypes: any = [WEB_TYPE, NATIVE_TYPE, USER_AGENT_TYPE, API_TYPE];
|
||||
public appTypes: any = [WEB_TYPE, NATIVE_TYPE, USER_AGENT_TYPE, API_TYPE, SAML_TYPE];
|
||||
|
||||
public authMethods: RadioItemAuthType[] = [PKCE_METHOD, CODE_METHOD, PK_JWT_METHOD, POST_METHOD];
|
||||
|
||||
@@ -84,8 +89,12 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
];
|
||||
|
||||
// stepper
|
||||
firstFormGroup!: UntypedFormGroup;
|
||||
secondFormGroup!: UntypedFormGroup;
|
||||
public firstFormGroup!: UntypedFormGroup;
|
||||
public secondFormGroup!: UntypedFormGroup;
|
||||
public samlConfigForm!: UntypedFormGroup;
|
||||
|
||||
public redirectUrisList: string[] = [];
|
||||
public postLogoutRedirectUrisList: string[] = [];
|
||||
|
||||
// devmode
|
||||
public form!: UntypedFormGroup;
|
||||
@@ -121,10 +130,13 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
) {
|
||||
this.form = this.fb.group({
|
||||
name: ['', [Validators.required]],
|
||||
responseTypesList: ['', [Validators.required]],
|
||||
grantTypesList: ['', [Validators.required]],
|
||||
appType: ['', [Validators.required]],
|
||||
authMethodType: ['', [Validators.required]],
|
||||
// apptype OIDC
|
||||
responseTypesList: ['', []],
|
||||
grantTypesList: ['', []],
|
||||
authMethodType: ['', []],
|
||||
// apptype SAML
|
||||
metadataUrl: ['', []],
|
||||
});
|
||||
|
||||
this.initForm();
|
||||
@@ -134,25 +146,30 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
appType: [WEB_TYPE, [Validators.required]],
|
||||
});
|
||||
|
||||
this.samlConfigForm = this.fb.group({
|
||||
metadataUrl: ['', []],
|
||||
});
|
||||
|
||||
this.firstFormGroup.valueChanges.subscribe((value) => {
|
||||
if (this.firstFormGroup.valid) {
|
||||
this.oidcAppRequest.name = this.name?.value;
|
||||
this.apiAppRequest.name = this.name?.value;
|
||||
this.oidcAppRequest.setName(this.name?.value);
|
||||
this.apiAppRequest.setName(this.name?.value);
|
||||
this.samlAppRequest.setName(this.name?.value);
|
||||
|
||||
if (this.isStepperOIDC) {
|
||||
const oidcAppType = (this.appType?.value as RadioItemAppType).oidcAppType;
|
||||
if (oidcAppType !== undefined) {
|
||||
this.oidcAppRequest.appType = oidcAppType;
|
||||
this.oidcAppRequest.setAppType(oidcAppType);
|
||||
}
|
||||
|
||||
switch (this.oidcAppRequest.appType) {
|
||||
switch (this.appType?.value.oidcAppType) {
|
||||
case OIDCAppType.OIDC_APP_TYPE_NATIVE:
|
||||
this.authMethods = [PKCE_METHOD];
|
||||
|
||||
// automatically set to PKCE and skip step
|
||||
this.oidcAppRequest.responseTypesList = [OIDCResponseType.OIDC_RESPONSE_TYPE_CODE];
|
||||
this.oidcAppRequest.grantTypesList = [OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE];
|
||||
this.oidcAppRequest.authMethodType = OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE;
|
||||
this.oidcAppRequest.setResponseTypesList([OIDCResponseType.OIDC_RESPONSE_TYPE_CODE]);
|
||||
this.oidcAppRequest.setGrantTypesList([OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE]);
|
||||
this.oidcAppRequest.setAuthMethodType(OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE);
|
||||
|
||||
break;
|
||||
case OIDCAppType.OIDC_APP_TYPE_WEB:
|
||||
@@ -179,19 +196,28 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
this.secondFormGroup = this.fb.group({
|
||||
authMethod: [this.authMethods[0].key, [Validators.required]],
|
||||
});
|
||||
|
||||
this.secondFormGroup.valueChanges.subscribe((form) => {
|
||||
const partialConfig = getPartialConfigFromAuthMethod(form.authMethod);
|
||||
|
||||
if (this.isStepperOIDC && partialConfig && partialConfig.oidc) {
|
||||
this.oidcAppRequest.responseTypesList = partialConfig.oidc?.responseTypesList ?? [];
|
||||
this.oidcAppRequest.setResponseTypesList(partialConfig.oidc?.responseTypesList ?? []);
|
||||
|
||||
this.oidcAppRequest.grantTypesList = partialConfig.oidc?.grantTypesList ?? [];
|
||||
this.oidcAppRequest.setGrantTypesList(partialConfig.oidc?.grantTypesList ?? []);
|
||||
|
||||
this.oidcAppRequest.authMethodType =
|
||||
partialConfig.oidc?.authMethodType ?? OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE;
|
||||
this.oidcAppRequest.setAuthMethodType(
|
||||
partialConfig.oidc?.authMethodType ?? OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE,
|
||||
);
|
||||
} else if (this.isStepperAPI && partialConfig && partialConfig.api) {
|
||||
this.apiAppRequest.authMethodType =
|
||||
partialConfig.api?.authMethodType ?? APIAuthMethodType.API_AUTH_METHOD_TYPE_BASIC;
|
||||
this.apiAppRequest.setAuthMethodType(
|
||||
partialConfig.api?.authMethodType ?? APIAuthMethodType.API_AUTH_METHOD_TYPE_BASIC,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
this.samlConfigForm.valueChanges.subscribe((form) => {
|
||||
if (form.metadataUrl && form.metadataUrl.length > 0) {
|
||||
this.samlAppRequest.setMetadataUrl(form.metadataUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -225,18 +251,23 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
|
||||
public initForm(): void {
|
||||
this.form.valueChanges.pipe(takeUntil(this.destroyed$), debounceTime(150)).subscribe(() => {
|
||||
this.oidcAppRequest.name = this.formname?.value;
|
||||
this.apiAppRequest.name = this.formname?.value;
|
||||
this.oidcAppRequest.setName(this.formname?.value);
|
||||
this.apiAppRequest.setName(this.formname?.value);
|
||||
this.samlAppRequest.setName(this.formname?.value);
|
||||
|
||||
this.oidcAppRequest.responseTypesList = this.formresponseTypesList?.value;
|
||||
this.oidcAppRequest.grantTypesList = this.formgrantTypesList?.value;
|
||||
this.oidcAppRequest.setResponseTypesList(this.formresponseTypesList?.value);
|
||||
this.oidcAppRequest.setGrantTypesList(this.grantTypesList?.value);
|
||||
|
||||
this.oidcAppRequest.authMethodType = this.formauthMethodType?.value;
|
||||
this.apiAppRequest.authMethodType = this.formauthMethodType?.value;
|
||||
this.oidcAppRequest.setAuthMethodType(this.authMethodType?.value);
|
||||
this.apiAppRequest.setAuthMethodType(this.authMethodType?.value);
|
||||
|
||||
if (this.formMetadataUrl?.value) {
|
||||
this.samlAppRequest.setMetadataUrl(this.formMetadataUrl?.value);
|
||||
}
|
||||
|
||||
const oidcAppType = (this.formappType?.value as RadioItemAppType).oidcAppType;
|
||||
if (oidcAppType !== undefined) {
|
||||
this.oidcAppRequest.appType = oidcAppType;
|
||||
this.oidcAppRequest.setAppType(oidcAppType);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -282,17 +313,41 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
|
||||
private async getData({ projectid }: Params): Promise<void> {
|
||||
this.projectId = projectid;
|
||||
this.oidcAppRequest.projectId = projectid;
|
||||
this.apiAppRequest.projectId = projectid;
|
||||
this.oidcAppRequest.setProjectId(projectid);
|
||||
this.apiAppRequest.setProjectId(projectid);
|
||||
this.samlAppRequest.setProjectId(projectid);
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this._location.back();
|
||||
}
|
||||
|
||||
public onDropXML(filelist: FileList): void {
|
||||
const file = filelist.item(0);
|
||||
this.metadataUrl?.setValue('');
|
||||
if (file) {
|
||||
if (file.size > MAX_ALLOWED_SIZE) {
|
||||
this.toast.showInfo('POLICY.PRIVATELABELING.MAXSIZEEXCEEDED', true);
|
||||
} else {
|
||||
const reader = new FileReader();
|
||||
reader.onload = ((aXML) => {
|
||||
return (e) => {
|
||||
const xmlBase64 = e.target?.result;
|
||||
if (xmlBase64 && typeof xmlBase64 === 'string') {
|
||||
const cropped = xmlBase64.replace('data:text/xml;base64,', '');
|
||||
this.samlAppRequest.setMetadataXml(cropped);
|
||||
}
|
||||
};
|
||||
})(file);
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public createApp(): void {
|
||||
const appOIDCCheck = this.devmode ? this.isDevOIDC : this.isStepperOIDC;
|
||||
const appAPICheck = this.devmode ? this.isDevAPI : this.isStepperAPI;
|
||||
const appSAMLCheck = this.devmode ? this.isDevSAML : this.isStepperSAML;
|
||||
|
||||
if (appOIDCCheck) {
|
||||
this.requestRedirectValuesSubject$.next();
|
||||
@@ -331,6 +386,19 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
this.loading = false;
|
||||
this.toast.showError(error);
|
||||
});
|
||||
} else if (appSAMLCheck) {
|
||||
this.loading = true;
|
||||
this.toast.showInfo('APP.TOAST.CREATED', true);
|
||||
this.mgmtService
|
||||
.addSAMLApp(this.samlAppRequest)
|
||||
.then((resp) => {
|
||||
this.loading = false;
|
||||
this.router.navigate(['projects', this.projectId, 'apps', resp.appId]);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.loading = false;
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,19 +449,27 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
get formname(): AbstractControl | null {
|
||||
return this.form.get('name');
|
||||
}
|
||||
|
||||
get formresponseTypesList(): AbstractControl | null {
|
||||
return this.form.get('responseTypesList');
|
||||
}
|
||||
get formgrantTypesList(): AbstractControl | null {
|
||||
|
||||
get grantTypesList(): AbstractControl | null {
|
||||
return this.form.get('grantTypesList');
|
||||
}
|
||||
|
||||
get formappType(): AbstractControl | null {
|
||||
return this.form.get('appType');
|
||||
}
|
||||
|
||||
get formMetadataUrl(): AbstractControl | null {
|
||||
return this.form.get('metadataUrl');
|
||||
}
|
||||
// get formapplicationType(): AbstractControl | null {
|
||||
// return this.form.get('applicationType');
|
||||
// }
|
||||
get formauthMethodType(): AbstractControl | null {
|
||||
|
||||
get authMethodType(): AbstractControl | null {
|
||||
return this.form.get('authMethodType');
|
||||
}
|
||||
|
||||
@@ -409,7 +485,35 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
return (this.formappType?.value as RadioItemAppType).createType === AppCreateType.API;
|
||||
}
|
||||
|
||||
get isDevSAML(): boolean {
|
||||
return (this.formappType?.value as RadioItemAppType).createType === AppCreateType.SAML;
|
||||
}
|
||||
|
||||
get isStepperAPI(): boolean {
|
||||
return (this.appType?.value as RadioItemAppType).createType === AppCreateType.API;
|
||||
}
|
||||
|
||||
get isStepperSAML(): boolean {
|
||||
return (this.appType?.value as RadioItemAppType).createType === AppCreateType.SAML;
|
||||
}
|
||||
|
||||
get decodedBase64(): string {
|
||||
const samlReq = this.samlAppRequest.toObject();
|
||||
if (samlReq && samlReq.metadataXml && typeof samlReq.metadataXml === 'string') {
|
||||
return Buffer.from(samlReq.metadataXml, 'base64').toString('ascii');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
set decodedBase64(xmlString) {
|
||||
if (this.samlAppRequest) {
|
||||
const base64 = Buffer.from(xmlString, 'ascii').toString('base64');
|
||||
this.samlAppRequest.setMetadataXml(base64);
|
||||
}
|
||||
}
|
||||
|
||||
public get metadataUrl(): AbstractControl | null {
|
||||
return this.samlConfigForm.get('metadataUrl');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user