fix(console): keep default idps active (#7663)

* fix(console): keep default idps active

* rename var

* rename var

* use Promise.all()

* lint

---------

Co-authored-by: Max Peintner <max@caos.ch>
This commit is contained in:
Elio Bischof
2024-04-11 10:29:25 +02:00
committed by GitHub
parent d229da6af7
commit 2de66dcf95
5 changed files with 137 additions and 157 deletions

View File

@@ -36,7 +36,7 @@ import { ContextChangedWorkflowOverlays } from 'src/app/services/overlay/workflo
import { PageEvent, PaginatorComponent } from '../paginator/paginator.component'; import { PageEvent, PaginatorComponent } from '../paginator/paginator.component';
import { PolicyComponentServiceType } from '../policies/policy-component-types.enum'; import { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component'; import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component';
import { ActivateIdpService } from '../../services/activate-idp.service'; import { LoginPolicyService } from '../../services/login-policy.service';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
@Component({ @Component({
@@ -72,7 +72,7 @@ export class IdpTableComponent implements OnInit, OnDestroy {
private toast: ToastService, private toast: ToastService,
private dialog: MatDialog, private dialog: MatDialog,
private router: Router, private router: Router,
private activateIdpSvc: ActivateIdpService, private loginPolicySvc: LoginPolicyService,
) { ) {
this.selection.changed.subscribe(() => { this.selection.changed.subscribe(() => {
this.changedSelection.emit(this.selection.selected); this.changedSelection.emit(this.selection.selected);
@@ -302,7 +302,7 @@ export class IdpTableComponent implements OnInit, OnDestroy {
} }
public addIdp(idp: Provider.AsObject): Promise<any> { public addIdp(idp: Provider.AsObject): Promise<any> {
return firstValueFrom(this.activateIdpSvc.activateIdp(this.service, idp.id, idp.owner, this.loginPolicy)) return firstValueFrom(this.loginPolicySvc.activateIdp(this.service, idp.id, idp.owner, this.loginPolicy))
.then(() => { .then(() => {
this.toast.showInfo('IDP.TOAST.ADDED', true); this.toast.showInfo('IDP.TOAST.ADDED', true);
setTimeout(() => { setTimeout(() => {
@@ -328,8 +328,8 @@ export class IdpTableComponent implements OnInit, OnDestroy {
switch (this.serviceType) { switch (this.serviceType) {
case PolicyComponentServiceType.MGMT: case PolicyComponentServiceType.MGMT:
if (this.isDefault) { if (this.isDefault) {
this.activateIdpSvc this.loginPolicySvc
.addLoginPolicy(this.service as ManagementService, this.loginPolicy) .createCustomLoginPolicy(this.service as ManagementService, this.loginPolicy)
.then(() => { .then(() => {
this.loginPolicy.isDefault = false; this.loginPolicy.isDefault = false;
return (this.service as ManagementService) return (this.service as ManagementService)

View File

@@ -26,6 +26,7 @@ import { PolicyComponentServiceType } from '../policy-component-types.enum';
import { LoginMethodComponentType } from './factor-table/factor-table.component'; import { LoginMethodComponentType } from './factor-table/factor-table.component';
import { catchError, map, takeUntil } from 'rxjs/operators'; import { catchError, map, takeUntil } from 'rxjs/operators';
import { error } from 'console'; import { error } from 'console';
import { LoginPolicyService } from '../../../services/login-policy.service';
const minValueValidator = (minValue: number) => (control: AbstractControl) => { const minValueValidator = (minValue: number) => (control: AbstractControl) => {
const value = control.value; const value = control.value;
@@ -71,6 +72,7 @@ export class LoginPolicyComponent implements OnInit, OnDestroy {
private fb: UntypedFormBuilder, private fb: UntypedFormBuilder,
private authService: GrpcAuthService, private authService: GrpcAuthService,
private dialog: MatDialog, private dialog: MatDialog,
private loginPolicySvc: LoginPolicyService,
) {} ) {}
ngOnDestroy(): void { ngOnDestroy(): void {
@@ -172,45 +174,13 @@ export class LoginPolicyComponent implements OnInit, OnDestroy {
} }
private async updateData(): Promise<any> { private async updateData(): Promise<any> {
const calls: Observable<any>[] = []; const calls: Promise<any>[] = [];
if (this.loginData) { if (this.loginData) {
switch (this.serviceType) { switch (this.serviceType) {
case PolicyComponentServiceType.MGMT: case PolicyComponentServiceType.MGMT:
if (this.isDefault) { if (this.isDefault) {
const mgmtreq = new AddCustomLoginPolicyRequest(); calls.push(this.loginPolicySvc.createCustomLoginPolicy(this.service as ManagementService, this.loginData));
mgmtreq.setAllowExternalIdp(this.loginData.allowExternalIdp);
mgmtreq.setAllowRegister(this.loginData.allowRegister);
mgmtreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
mgmtreq.setForceMfa(this.loginData.forceMfa);
mgmtreq.setForceMfaLocalOnly(this.loginData.forceMfaLocalOnly);
mgmtreq.setPasswordlessType(this.loginData.passwordlessType);
mgmtreq.setHidePasswordReset(this.loginData.hidePasswordReset);
mgmtreq.setMultiFactorsList(this.loginData.multiFactorsList);
mgmtreq.setSecondFactorsList(this.loginData.secondFactorsList);
mgmtreq.setDisableLoginWithEmail(this.loginData.disableLoginWithEmail);
mgmtreq.setDisableLoginWithPhone(this.loginData.disableLoginWithPhone);
const pcl = new Duration().setSeconds((this.passwordCheckLifetime?.value ?? 0) * 60 * 60);
mgmtreq.setPasswordCheckLifetime(pcl);
const elcl = new Duration().setSeconds((this.externalLoginCheckLifetime?.value ?? 0) * 60 * 60);
mgmtreq.setExternalLoginCheckLifetime(elcl);
const misl = new Duration().setSeconds((this.mfaInitSkipLifetime?.value ?? 0) * 60 * 60);
mgmtreq.setMfaInitSkipLifetime(misl);
const sfcl = new Duration().setSeconds((this.secondFactorCheckLifetime?.value ?? 0) * 60 * 60);
mgmtreq.setSecondFactorCheckLifetime(sfcl);
const mficl = new Duration().setSeconds((this.multiFactorCheckLifetime?.value ?? 0) * 60 * 60);
mgmtreq.setMultiFactorCheckLifetime(mficl);
mgmtreq.setAllowDomainDiscovery(this.loginData.allowDomainDiscovery);
mgmtreq.setIgnoreUnknownUsernames(this.loginData.ignoreUnknownUsernames);
mgmtreq.setDefaultRedirectUri(this.loginData.defaultRedirectUri);
calls.push(from((this.service as ManagementService).addCustomLoginPolicy(mgmtreq)));
break; break;
} else { } else {
const mgmtreq = new UpdateCustomLoginPolicyRequest(); const mgmtreq = new UpdateCustomLoginPolicyRequest();
@@ -243,7 +213,7 @@ export class LoginPolicyComponent implements OnInit, OnDestroy {
mgmtreq.setIgnoreUnknownUsernames(this.loginData.ignoreUnknownUsernames); mgmtreq.setIgnoreUnknownUsernames(this.loginData.ignoreUnknownUsernames);
mgmtreq.setDefaultRedirectUri(this.loginData.defaultRedirectUri); mgmtreq.setDefaultRedirectUri(this.loginData.defaultRedirectUri);
calls.push(from((this.service as ManagementService).updateCustomLoginPolicy(mgmtreq))); calls.push((this.service as ManagementService).updateCustomLoginPolicy(mgmtreq));
break; break;
} }
case PolicyComponentServiceType.ADMIN: case PolicyComponentServiceType.ADMIN:
@@ -276,21 +246,19 @@ export class LoginPolicyComponent implements OnInit, OnDestroy {
adminreq.setIgnoreUnknownUsernames(this.loginData.ignoreUnknownUsernames); adminreq.setIgnoreUnknownUsernames(this.loginData.ignoreUnknownUsernames);
adminreq.setDefaultRedirectUri(this.loginData.defaultRedirectUri); adminreq.setDefaultRedirectUri(this.loginData.defaultRedirectUri);
calls.push(from((this.service as AdminService).setRestrictions(!this.allowOrgRegistration))); calls.push((this.service as AdminService).setRestrictions(!this.allowOrgRegistration));
calls.push(from((this.service as AdminService).updateLoginPolicy(adminreq))); calls.push((this.service as AdminService).updateLoginPolicy(adminreq));
break; break;
} }
} else { } else {
calls.push(from(Promise.reject())); calls.push(Promise.reject());
} }
return firstValueFrom( return Promise.all(calls).catch((err) => {
forkJoin(calls).pipe( if (err?.message?.includes('INSTANCE-5M9vdd')) {
catchError((error, caught) => { return true;
// We just ignore the policy not changed error! }
return (error as { message: string }).message.includes('INSTANCE-5M9vdd') ? of(true) : caught; throw err;
}), });
),
);
} }
public savePolicy(): void { public savePolicy(): void {

View File

@@ -8,7 +8,7 @@ import { AdminService } from '../../../services/admin.service';
import { IDPOwnerType } from '../../../proto/generated/zitadel/idp_pb'; import { IDPOwnerType } from '../../../proto/generated/zitadel/idp_pb';
import { ToastService } from '../../../services/toast.service'; import { ToastService } from '../../../services/toast.service';
import { Data, ParamMap } from '@angular/router'; import { Data, ParamMap } from '@angular/router';
import { ActivateIdpService } from '../../../services/activate-idp.service'; import { LoginPolicyService } from '../../../services/login-policy.service';
import { PolicyComponentServiceType } from '../../policies/policy-component-types.enum'; import { PolicyComponentServiceType } from '../../policies/policy-component-types.enum';
@Injectable({ @Injectable({
@@ -18,7 +18,7 @@ export class ProviderNextService {
constructor( constructor(
private env: EnvironmentService, private env: EnvironmentService,
private toast: ToastService, private toast: ToastService,
private addIdpSvc: ActivateIdpService, private loginPolicySvc: LoginPolicyService,
private injector: Injector, private injector: Injector,
) {} ) {}
@@ -117,7 +117,7 @@ export class ProviderNextService {
if (!id) { if (!id) {
throw new Error('No ID'); throw new Error('No ID');
} }
return this.addIdpSvc.activateIdp( return this.loginPolicySvc.activateIdp(
service, service,
id, id,
service instanceof AdminService ? IDPOwnerType.IDP_OWNER_TYPE_SYSTEM : IDPOwnerType.IDP_OWNER_TYPE_ORG, service instanceof AdminService ? IDPOwnerType.IDP_OWNER_TYPE_SYSTEM : IDPOwnerType.IDP_OWNER_TYPE_ORG,

View File

@@ -1,103 +0,0 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, combineLatest, from, map, Observable, of, switchMap } from 'rxjs';
import { ManagementService } from './mgmt.service';
import { AddCustomLoginPolicyRequest, AddCustomLoginPolicyResponse } from '../proto/generated/zitadel/management_pb';
import { Duration } from 'google-protobuf/google/protobuf/duration_pb';
import { IDPOwnerType, Provider } from '../proto/generated/zitadel/idp_pb';
import { AdminService } from './admin.service';
import { catchError } from 'rxjs/operators';
import { LoginPolicy } from '../proto/generated/zitadel/policy_pb';
@Injectable({
providedIn: 'root',
})
export class ActivateIdpService {
constructor() {}
public activateIdp(
service: AdminService | ManagementService,
id: string,
owner?: IDPOwnerType,
policy?: LoginPolicy.AsObject,
): Observable<any> {
const isAdmin = service instanceof AdminService;
if (isAdmin) {
service.addIDPToLoginPolicy(id);
}
if (!isAdmin && owner !== IDPOwnerType.IDP_OWNER_TYPE_SYSTEM && owner !== IDPOwnerType.IDP_OWNER_TYPE_ORG) {
throw new Error('Must specify owner for management service');
}
return from(service.addIDPToLoginPolicy(id!, owner!)).pipe(
catchError((error) => {
if (isAdmin || error.code != 5) {
throw error;
}
// No org policy was found, so we create a new one
return from(policy ? of(policy) : from(service.getLoginPolicy()).pipe(map((policy) => policy.policy))).pipe(
switchMap((policy) => {
if (!policy?.isDefault) {
// There is already an org policy
throw error;
}
return from(this.addLoginPolicy(service, policy)).pipe(
switchMap(() => from(service.addIDPToLoginPolicy(id!, owner!))),
);
}),
);
}),
);
}
public addLoginPolicy(
service: ManagementService,
policy: LoginPolicy.AsObject,
): Promise<AddCustomLoginPolicyResponse.AsObject> {
const mgmtreq = new AddCustomLoginPolicyRequest();
mgmtreq.setAllowExternalIdp(policy.allowExternalIdp);
mgmtreq.setAllowRegister(policy.allowRegister);
mgmtreq.setAllowUsernamePassword(policy.allowUsernamePassword);
mgmtreq.setForceMfa(policy.forceMfa);
mgmtreq.setPasswordlessType(policy.passwordlessType);
mgmtreq.setHidePasswordReset(policy.hidePasswordReset);
mgmtreq.setMultiFactorsList(policy.multiFactorsList);
mgmtreq.setSecondFactorsList(policy.secondFactorsList);
const pcl = new Duration()
.setSeconds(policy.passwordCheckLifetime?.seconds ?? 0)
.setNanos(policy.passwordCheckLifetime?.nanos ?? 0);
mgmtreq.setPasswordCheckLifetime(pcl);
const elcl = new Duration()
.setSeconds(policy.externalLoginCheckLifetime?.seconds ?? 0)
.setNanos(policy.externalLoginCheckLifetime?.nanos ?? 0);
mgmtreq.setExternalLoginCheckLifetime(elcl);
const misl = new Duration()
.setSeconds(policy.mfaInitSkipLifetime?.seconds ?? 0)
.setNanos(policy.mfaInitSkipLifetime?.nanos ?? 0);
mgmtreq.setMfaInitSkipLifetime(misl);
const sfcl = new Duration()
.setSeconds(policy.secondFactorCheckLifetime?.seconds ?? 0)
.setNanos(policy.secondFactorCheckLifetime?.nanos ?? 0);
mgmtreq.setSecondFactorCheckLifetime(sfcl);
const mficl = new Duration()
.setSeconds(policy.multiFactorCheckLifetime?.seconds ?? 0)
.setNanos(policy.multiFactorCheckLifetime?.nanos ?? 0);
mgmtreq.setMultiFactorCheckLifetime(mficl);
mgmtreq.setAllowDomainDiscovery(policy.allowDomainDiscovery);
mgmtreq.setIgnoreUnknownUsernames(policy.ignoreUnknownUsernames);
mgmtreq.setDefaultRedirectUri(policy.defaultRedirectUri);
mgmtreq.setDisableLoginWithEmail(policy.disableLoginWithEmail);
mgmtreq.setDisableLoginWithPhone(policy.disableLoginWithPhone);
mgmtreq.setForceMfaLocalOnly(policy.forceMfaLocalOnly);
return service.addCustomLoginPolicy(mgmtreq);
}
}

View File

@@ -0,0 +1,115 @@
import { Injectable } from '@angular/core';
import { from, map, Observable, of, switchMap } from 'rxjs';
import { ManagementService } from './mgmt.service';
import { AddCustomLoginPolicyRequest, AddCustomLoginPolicyResponse } from '../proto/generated/zitadel/management_pb';
import { Duration } from 'google-protobuf/google/protobuf/duration_pb';
import { IDPOwnerType } from '../proto/generated/zitadel/idp_pb';
import { AdminService } from './admin.service';
import { catchError } from 'rxjs/operators';
import { LoginPolicy } from '../proto/generated/zitadel/policy_pb';
@Injectable({
providedIn: 'root',
})
export class LoginPolicyService {
constructor() {}
public activateIdp(
service: AdminService | ManagementService,
id: string,
owner?: IDPOwnerType,
policy?: LoginPolicy.AsObject,
): Observable<any> {
const isAdmin = service instanceof AdminService;
if (isAdmin) {
service.addIDPToLoginPolicy(id);
}
if (!isAdmin && owner !== IDPOwnerType.IDP_OWNER_TYPE_SYSTEM && owner !== IDPOwnerType.IDP_OWNER_TYPE_ORG) {
throw new Error('Must specify owner for management service');
}
return from(service.addIDPToLoginPolicy(id!, owner!)).pipe(
catchError((error) => {
if (isAdmin || error.code != 5) {
throw error;
}
// No org policy was found, so we create a new one
return from(policy ? of(policy) : from(service.getLoginPolicy()).pipe(map((policy) => policy.policy))).pipe(
switchMap((policy) => {
if (!policy?.isDefault) {
// There is already an org policy
throw error;
}
return from(this.createCustomLoginPolicy(service, policy, id));
}),
);
}),
);
}
public createCustomLoginPolicy(
service: ManagementService,
fromDefaultPolicy: LoginPolicy.AsObject,
activateOrgIdp?: string,
): Promise<AddCustomLoginPolicyResponse.AsObject> {
const mgmtreq = new AddCustomLoginPolicyRequest();
mgmtreq.setAllowExternalIdp(fromDefaultPolicy.allowExternalIdp);
mgmtreq.setAllowRegister(fromDefaultPolicy.allowRegister);
mgmtreq.setAllowUsernamePassword(fromDefaultPolicy.allowUsernamePassword);
mgmtreq.setForceMfa(fromDefaultPolicy.forceMfa);
mgmtreq.setPasswordlessType(fromDefaultPolicy.passwordlessType);
mgmtreq.setHidePasswordReset(fromDefaultPolicy.hidePasswordReset);
mgmtreq.setMultiFactorsList(fromDefaultPolicy.multiFactorsList);
mgmtreq.setSecondFactorsList(fromDefaultPolicy.secondFactorsList);
const pcl = new Duration()
.setSeconds(fromDefaultPolicy.passwordCheckLifetime?.seconds ?? 0)
.setNanos(fromDefaultPolicy.passwordCheckLifetime?.nanos ?? 0);
mgmtreq.setPasswordCheckLifetime(pcl);
const elcl = new Duration()
.setSeconds(fromDefaultPolicy.externalLoginCheckLifetime?.seconds ?? 0)
.setNanos(fromDefaultPolicy.externalLoginCheckLifetime?.nanos ?? 0);
mgmtreq.setExternalLoginCheckLifetime(elcl);
const misl = new Duration()
.setSeconds(fromDefaultPolicy.mfaInitSkipLifetime?.seconds ?? 0)
.setNanos(fromDefaultPolicy.mfaInitSkipLifetime?.nanos ?? 0);
mgmtreq.setMfaInitSkipLifetime(misl);
const sfcl = new Duration()
.setSeconds(fromDefaultPolicy.secondFactorCheckLifetime?.seconds ?? 0)
.setNanos(fromDefaultPolicy.secondFactorCheckLifetime?.nanos ?? 0);
mgmtreq.setSecondFactorCheckLifetime(sfcl);
const mficl = new Duration()
.setSeconds(fromDefaultPolicy.multiFactorCheckLifetime?.seconds ?? 0)
.setNanos(fromDefaultPolicy.multiFactorCheckLifetime?.nanos ?? 0);
mgmtreq.setMultiFactorCheckLifetime(mficl);
mgmtreq.setAllowDomainDiscovery(fromDefaultPolicy.allowDomainDiscovery);
mgmtreq.setIgnoreUnknownUsernames(fromDefaultPolicy.ignoreUnknownUsernames);
mgmtreq.setDefaultRedirectUri(fromDefaultPolicy.defaultRedirectUri);
mgmtreq.setDisableLoginWithEmail(fromDefaultPolicy.disableLoginWithEmail);
mgmtreq.setDisableLoginWithPhone(fromDefaultPolicy.disableLoginWithPhone);
mgmtreq.setForceMfaLocalOnly(fromDefaultPolicy.forceMfaLocalOnly);
mgmtreq.setIdpsList(
fromDefaultPolicy.idpsList.map((idp) => addIdpMessage(idp.idpId, IDPOwnerType.IDP_OWNER_TYPE_SYSTEM)),
);
if (activateOrgIdp) {
mgmtreq.addIdps(addIdpMessage(activateOrgIdp, IDPOwnerType.IDP_OWNER_TYPE_ORG));
}
return service.addCustomLoginPolicy(mgmtreq);
}
}
function addIdpMessage(id: string, owner: IDPOwnerType): AddCustomLoginPolicyRequest.IDP {
const addIdp = new AddCustomLoginPolicyRequest.IDP();
addIdp.setIdpId(id);
addIdp.setOwnertype(owner);
return addIdp;
}