feat(actions): add token customization flow and extend functionally with modules (#4337)

* fix: potential memory leak

* feat(actions): possibility to parse json
feat(actions): possibility to perform http calls

* add query call

* feat(api): list flow and trigger types
fix(api): switch flow and trigger types to dynamic objects

* fix(translations): add action translations

* use `domain.FlowType`

* localizers

* localization

* trigger types

* options on `query.Action`

* add functions for actions

* feat: management api: add list flow and trigger  (#4352)

* console changes

* cleanup

* fix: wrong localization

Co-authored-by: Max Peintner <max@caos.ch>

* id token works

* check if claims not nil

* feat(actions): metadata api

* refactor(actions): modules

* fix: allow prerelease

* fix: test

* feat(actions): deny list for http hosts

* feat(actions): deny list for http hosts

* refactor: actions

* fix: different error ids

* fix: rename statusCode to status

* Actions objects as options (#4418)

* fix: rename statusCode to status

* fix(actions): objects as options

* fix(actions): objects as options

* fix(actions): set fields

* add http client to old actions

* fix(actions): add log module

* fix(actions): add user to context where possible

* fix(actions): add user to ctx in external authorization/pre creation

* fix(actions): query correct flow in claims

* test: actions

* fix(id-generator): panic if no machine id

* tests

* maybe this?

* fix linting

* refactor: improve code

* fix: metadata and usergrant usage in actions

* fix: appendUserGrant

* fix: allowedToFail and timeout in action execution

* fix: allowed to fail in token complement flow

* docs: add action log claim

* Update defaults.yaml

* fix log claim

* remove prerelease build

Co-authored-by: Max Peintner <max@caos.ch>
Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Silvan
2022-10-06 14:23:59 +02:00
committed by GitHub
parent bffb10a4b4
commit 43fb3fd1a6
62 changed files with 2806 additions and 636 deletions

View File

@@ -21,12 +21,12 @@
<p class="desc cnsl-secondary-text">{{ 'FLOWS.FLOWSDESCRIPTION' | translate }}</p>
<ng-template cnslHasRole [hasRole]="['org.flow.read']">
<div *ngIf="flow" class="flow">
<div class="flow">
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'FLOWS.FLOWTYPE' | translate }}</cnsl-label>
<mat-select [formControl]="typeControl">
<mat-option *ngFor="let type of typesForSelection" [value]="type">
{{ 'FLOWS.TYPES.' + type | translate }}
{{ type.name?.localizedMessage }}
</mat-option>
</mat-select>
</cnsl-form-field>
@@ -36,8 +36,14 @@
<div class="flow-type">
<i class="type-icon las la-dot-circle"></i>
<span>{{ 'FLOWS.TYPES.' + flow.type | translate }}</span>
<button matTooltip="{{ 'ACTIONS.CLEAR' | translate }}" mat-icon-button color="warn" (click)="clearFlow()">
<span>{{ flow.type?.name?.localizedMessage }}</span>
<button
*ngIf="flow.type && (flow.triggerActionsList?.length ?? 0) > 0"
matTooltip="{{ 'ACTIONS.CLEAR' | translate }}"
mat-icon-button
color="warn"
(click)="clearFlow(flow.type.id)"
>
<i class="type-button-icon las la-trash"></i>
</button>
</div>
@@ -45,7 +51,11 @@
<cnsl-card *ngFor="let trigger of flow.triggerActionsList; index as i" class="trigger">
<div class="trigger-top">
<mat-icon svgIcon="mdi_arrow_right_bottom" class="icon"></mat-icon>
<span>{{ 'FLOWS.TRIGGERTYPES.' + trigger.triggerType | translate }}</span>
<span>{{ trigger.triggerType?.name?.localizedMessage }}</span>
<span class="fill-space"></span>
<button color="warn" mat-icon-button (click)="removeTriggerActionsList(i)">
<i class="las la-trash"></i>
</button>
</div>
<span class="fill-space"></span>
<div class="flow-action-wrapper" cdkDropList (cdkDropListDropped)="drop(i, trigger.actionsList, $event)">
@@ -72,7 +82,13 @@
</div>
</cnsl-card>
<button class="add-btn cnsl-action-button" mat-raised-button color="primary" (click)="openAddTrigger()">
<button
*ngIf="flow.type"
class="add-btn cnsl-action-button"
mat-raised-button
color="primary"
(click)="openAddTrigger(flow.type)"
>
<mat-icon>add</mat-icon>
<span>{{ 'FLOWS.ADDTRIGGER' | translate }}</span>
<span *ngIf="selection && selection.length">&nbsp;({{ selection.length }})</span>

View File

@@ -68,6 +68,10 @@ h1 {
align-items: center;
margin-bottom: 0.5rem;
padding-left: 7px;
.fill-space {
flex: 1;
}
}
.icon {

View File

@@ -1,7 +1,8 @@
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { Component } from '@angular/core';
import { Component, OnDestroy } from '@angular/core';
import { UntypedFormControl } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { Subject, takeUntil } from 'rxjs';
import { ActionKeysType } from 'src/app/modules/action-keys/action-keys.component';
import { InfoSectionType } from 'src/app/modules/info-section/info-section.component';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
@@ -18,13 +19,12 @@ import { AddFlowDialogComponent } from './add-flow-dialog/add-flow-dialog.compon
templateUrl: './actions.component.html',
styleUrls: ['./actions.component.scss'],
})
export class ActionsComponent {
export class ActionsComponent implements OnDestroy {
public flow!: Flow.AsObject;
public flowType: FlowType = FlowType.FLOW_TYPE_EXTERNAL_AUTHENTICATION;
public typeControl: UntypedFormControl = new UntypedFormControl(FlowType.FLOW_TYPE_EXTERNAL_AUTHENTICATION);
public typeControl: UntypedFormControl = new UntypedFormControl();
public typesForSelection: FlowType[] = [FlowType.FLOW_TYPE_EXTERNAL_AUTHENTICATION];
public typesForSelection: FlowType.AsObject[] = [];
public selection: Action.AsObject[] = [];
public InfoSectionType: any = InfoSectionType;
@@ -32,7 +32,7 @@ export class ActionsComponent {
public maxActions: number | null = null;
public ActionState: any = ActionState;
private destroy$: Subject<void> = new Subject();
constructor(
private mgmtService: ManagementService,
breadcrumbService: BreadcrumbService,
@@ -45,16 +45,42 @@ export class ActionsComponent {
};
breadcrumbService.setBreadcrumb([bread]);
this.loadFlow();
}
this.getFlowTypes();
private loadFlow() {
this.mgmtService.getFlow(this.flowType).then((flowResponse) => {
if (flowResponse.flow) this.flow = flowResponse.flow;
this.typeControl.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
this.loadFlow((value as FlowType.AsObject).id);
});
}
public clearFlow(): void {
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
private getFlowTypes(): Promise<void> {
return this.mgmtService
.listFlowTypes()
.then((resp) => {
this.typesForSelection = resp.resultList;
if (!this.flow && resp.resultList[0]) {
const type = resp.resultList[0];
this.typeControl.setValue(type);
}
})
.catch((error: any) => {
this.toast.showError(error);
});
}
private loadFlow(id: string) {
this.mgmtService.getFlow(id).then((flowResponse) => {
if (flowResponse.flow) {
this.flow = flowResponse.flow;
}
});
}
public clearFlow(id: string): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.CLEAR',
@@ -68,10 +94,10 @@ export class ActionsComponent {
dialogRef.afterClosed().subscribe((resp) => {
if (resp) {
this.mgmtService
.clearFlow(this.flowType)
.then((resp) => {
.clearFlow(id)
.then(() => {
this.toast.showInfo('FLOWS.FLOWCLEARED', true);
this.loadFlow();
this.loadFlow(id);
})
.catch((error: any) => {
this.toast.showError(error);
@@ -80,11 +106,10 @@ export class ActionsComponent {
});
}
public openAddTrigger(): void {
public openAddTrigger(flow: FlowType.AsObject, trigger?: TriggerType.AsObject): void {
const dialogRef = this.dialog.open(AddFlowDialogComponent, {
data: {
flowType: this.flowType,
triggerType: TriggerType.TRIGGER_TYPE_POST_AUTHENTICATION,
flowType: flow,
actions: this.selection && this.selection.length ? this.selection : [],
},
width: '400px',
@@ -96,7 +121,7 @@ export class ActionsComponent {
.setTriggerActions(req.getActionIdsList(), req.getFlowType(), req.getTriggerType())
.then((resp) => {
this.toast.showInfo('FLOWS.FLOWCHANGED', true);
this.loadFlow();
this.loadFlow(flow.id);
})
.catch((error: any) => {
this.toast.showError(error);
@@ -111,17 +136,52 @@ export class ActionsComponent {
}
saveFlow(index: number) {
this.mgmtService
.setTriggerActions(
this.flow.triggerActionsList[index].actionsList.map((action) => action.id),
this.flowType,
this.flow.triggerActionsList[index].triggerType,
)
.then((updateResponse) => {
this.toast.showInfo('FLOWS.TOAST.ACTIONSSET', true);
})
.catch((error) => {
this.toast.showError(error);
if (
this.flow.type &&
this.flow.triggerActionsList &&
this.flow.triggerActionsList[index] &&
this.flow.triggerActionsList[index]?.triggerType
) {
this.mgmtService
.setTriggerActions(
this.flow.triggerActionsList[index].actionsList.map((action) => action.id),
this.flow.type.id,
this.flow.triggerActionsList[index].triggerType?.id ?? '',
)
.then(() => {
this.toast.showInfo('FLOWS.TOAST.ACTIONSSET', true);
})
.catch((error) => {
this.toast.showError(error);
});
}
}
public removeTriggerActionsList(index: number) {
if (this.flow.type && this.flow.triggerActionsList && this.flow.triggerActionsList[index]) {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.CLEAR',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'FLOWS.DIALOG.REMOVEACTIONSLIST.TITLE',
descriptionKey: 'FLOWS.DIALOG.REMOVEACTIONSLIST.DESCRIPTION',
},
width: '400px',
});
dialogRef.afterClosed().subscribe((resp) => {
if (resp) {
this.mgmtService
.setTriggerActions([], this.flow?.type?.id ?? '', this.flow.triggerActionsList[index].triggerType?.id ?? '')
.then(() => {
this.toast.showInfo('FLOWS.TOAST.ACTIONSSET', true);
this.loadFlow(this.flow?.type?.id ?? '');
})
.catch((error) => {
this.toast.showError(error);
});
}
});
}
}
}

View File

@@ -2,20 +2,11 @@
<div mat-dialog-content>
<form *ngIf="form" [formGroup]="form">
<cnsl-form-field class="form-field">
<cnsl-label>{{ 'FLOWS.FLOWTYPE' | translate }}</cnsl-label>
<mat-select formControlName="flowType">
<mat-option *ngFor="let type of typesForSelection" [value]="type">
{{ 'FLOWS.TYPES.' + type | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="form-field">
<cnsl-label>{{ 'FLOWS.TRIGGERTYPE' | translate }}</cnsl-label>
<mat-select formControlName="triggerType" name="triggerType">
<mat-option *ngFor="let type of triggerTypesForSelection" [value]="type">
{{ 'FLOWS.TRIGGERTYPES.' + type | translate }}
{{ type.name?.localizedMessage }}
</mat-option>
</mat-select>
</cnsl-form-field>

View File

@@ -12,13 +12,10 @@ import { ToastService } from 'src/app/services/toast.service';
styleUrls: ['./add-flow-dialog.component.scss'],
})
export class AddFlowDialogComponent {
public flowType?: FlowType.AsObject;
public actions: Action.AsObject[] = [];
public typesForSelection: FlowType[] = [FlowType.FLOW_TYPE_EXTERNAL_AUTHENTICATION];
public triggerTypesForSelection: TriggerType[] = [
TriggerType.TRIGGER_TYPE_POST_AUTHENTICATION,
TriggerType.TRIGGER_TYPE_POST_CREATION,
TriggerType.TRIGGER_TYPE_PRE_CREATION,
];
public typesForSelection: FlowType.AsObject[] = [];
public triggerTypesForSelection: TriggerType.AsObject[] = [];
public form!: UntypedFormGroup;
constructor(
@@ -28,14 +25,30 @@ export class AddFlowDialogComponent {
public dialogRef: MatDialogRef<AddFlowDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
) {
if (data.flowType) {
this.flowType = data.flowType as FlowType.AsObject;
this.getTriggerTypes((data.flowType as FlowType.AsObject).id);
}
this.form = this.fb.group({
flowType: [data.flowType ? data.flowType : '', [Validators.required]],
triggerType: [data.triggerType ? data.triggerType : '', [Validators.required]],
actionIdsList: [data.actions ? (data.actions as Action.AsObject[]).map((a) => a.id) : [], [Validators.required]],
});
this.getActionIds();
}
private getTriggerTypes(id: string): Promise<void> {
return this.mgmtService
.listFlowTriggerTypes(id)
.then((resp) => {
this.triggerTypesForSelection = resp.resultList;
})
.catch((error: any) => {
this.toast.showError(error);
});
}
private getActionIds(): Promise<void> {
return this.mgmtService
.listActions()
@@ -52,11 +65,13 @@ export class AddFlowDialogComponent {
}
public closeDialogWithSuccess(): void {
const req = new SetTriggerActionsRequest();
req.setActionIdsList(this.form.get('actionIdsList')?.value);
req.setFlowType(this.form.get('flowType')?.value);
req.setTriggerType(this.form.get('triggerType')?.value);
if (this.flowType) {
const req = new SetTriggerActionsRequest();
req.setActionIdsList(this.form.get('actionIdsList')?.value);
req.setFlowType(this.flowType.id);
req.setTriggerType((this.form.get('triggerType')?.value as TriggerType.AsObject).id);
this.dialogRef.close(req);
this.dialogRef.close(req);
}
}
}

View File

@@ -192,6 +192,10 @@ import {
ListAppKeysResponse,
ListAppsRequest,
ListAppsResponse,
ListFlowTriggerTypesRequest,
ListFlowTriggerTypesResponse,
ListFlowTypesRequest,
ListFlowTypesResponse,
ListGrantedProjectRolesRequest,
ListGrantedProjectRolesResponse,
ListGrantedProjectsRequest,
@@ -982,13 +986,24 @@ export class ManagementService {
return this.grpcService.mgmt.listActions(req, null).then((resp) => resp.toObject());
}
public getFlow(type: FlowType): Promise<GetFlowResponse.AsObject> {
public listFlowTypes(): Promise<ListFlowTypesResponse.AsObject> {
const req = new ListFlowTypesRequest();
return this.grpcService.mgmt.listFlowTypes(req, null).then((resp) => resp.toObject());
}
public listFlowTriggerTypes(type: string): Promise<ListFlowTriggerTypesResponse.AsObject> {
const req = new ListFlowTriggerTypesRequest();
req.setType(type);
return this.grpcService.mgmt.listFlowTriggerTypes(req, null).then((resp) => resp.toObject());
}
public getFlow(type: string): Promise<GetFlowResponse.AsObject> {
const req = new GetFlowRequest();
req.setType(type);
return this.grpcService.mgmt.getFlow(req, null).then((resp) => resp.toObject());
}
public clearFlow(type: FlowType): Promise<ClearFlowResponse.AsObject> {
public clearFlow(type: string): Promise<ClearFlowResponse.AsObject> {
const req = new ClearFlowRequest();
req.setType(type);
return this.grpcService.mgmt.clearFlow(req, null).then((resp) => resp.toObject());
@@ -996,8 +1011,8 @@ export class ManagementService {
public setTriggerActions(
actionIdsList: string[],
type: FlowType,
triggerType: TriggerType,
type: string,
triggerType: string,
): Promise<SetTriggerActionsResponse.AsObject> {
const req = new SetTriggerActionsRequest();
req.setActionIdsList(actionIdsList);

View File

@@ -654,15 +654,6 @@
"1": "inaktiv",
"2": "aktiv"
},
"TYPES": {
"0": "Unspezifisch",
"1": "Externe Authentifizierung"
},
"TRIGGERTYPES": {
"1": "Post Authentication",
"2": "Pre Creation",
"3": "Post Creation"
},
"ADDTRIGGER": "Trigger hinzufügen",
"FLOWCHANGED": "Der Ablauf wurde geändert",
"FLOWCLEARED": "Der Ablauf wurde zurückgesetzt",
@@ -689,6 +680,10 @@
"CLEAR": {
"TITLE": "Flow zurücksetzen?",
"DESCRIPTION": "Sie sind im Begriff den Flow mitsamt seinen Triggern und Aktionen zurückzusetzen. Diese Änderung kann nicht wiederhergestellt werden."
},
"REMOVEACTIONSLIST": {
"TITLE": "Ausgewählte Aktionen löschen?",
"DESCRIPTION": "Wollen Sie die gewählten Aktionen wirklich löschen?"
}
},
"TOAST": {

View File

@@ -654,15 +654,6 @@
"1": "inactive",
"2": "active"
},
"TYPES": {
"0": "Unspecified Type",
"1": "External Authentication"
},
"TRIGGERTYPES": {
"1": "Post Authentication",
"2": "Pre Creation",
"3": "Post Creation"
},
"ADDTRIGGER": "Add trigger",
"FLOWCHANGED": "The flow was changed successfully",
"FLOWCLEARED": "The flow was reset successfully",
@@ -689,6 +680,10 @@
"CLEAR": {
"TITLE": "Clear flow?",
"DESCRIPTION": "You are about to reset the flow along with its triggers and actions. This change cannot be restored. Are you sure?"
},
"REMOVEACTIONSLIST": {
"TITLE": "Delete selected Actions?",
"DESCRIPTION": "Are you sure you want to delete the selected actions from the flow?"
}
},
"TOAST": {

View File

@@ -654,15 +654,6 @@
"1": "inactif",
"2": "actif"
},
"TYPES": {
"0": "Type non spécifié",
"1": "Authentification externe"
},
"TRIGGERTYPES": {
"1": "Post Authentification",
"2": "Pré création",
"3": "Post création"
},
"ADDTRIGGER": "Ajouter un déclencheur",
"FLOWCHANGED": "Le processus a été modifié",
"FLOWCLEARED": "Le processus a été supprimé avec succès",
@@ -689,6 +680,10 @@
"CLEAR": {
"TITLE": "Effacer le flux ?",
"DESCRIPTION": "Vous êtes sur le point de réinitialiser le flux ainsi que ses déclencheurs et actions. Ce changement ne peut pas être restauré. Etes-vous sûr ?"
},
"REMOVEACTIONSLIST": {
"TITLE": "Supprimer les actions sélectionnées ?",
"DESCRIPTION": "Voulez-vous vraiment supprimer les actions sélectionnées du flux ?"
}
},
"TOAST": {

View File

@@ -654,15 +654,6 @@
"1": "inattivo",
"2": "attivo"
},
"TYPES": {
"0": "Non specifico",
"1": "Autenticazione esterna"
},
"TRIGGERTYPES": {
"1": "Post autenticazione",
"2": "Pre creazione",
"3": "Post creazione"
},
"ADDTRIGGER": "Aggiungi trigger",
"FLOWCHANGED": "Il processo è stato modificato con successo.",
"FLOWCLEARED": "Il processo è stato eliminato con successo.",
@@ -689,6 +680,10 @@
"CLEAR": {
"TITLE": "Elimina processo",
"DESCRIPTION": "Stai per eliminare un processo. Questa azione non può essere annullata. Vuoi continuare?"
},
"REMOVEACTIONSLIST": {
"TITLE": "Elimina le azioni selezionate?",
"DESCRIPTION": "Sei sicuro di voler eliminare le azioni selezionate dal processo?"
}
},
"TOAST": {

View File

@@ -654,15 +654,6 @@
"1": "停用",
"2": "激活"
},
"TYPES": {
"0": "未指定类型",
"1": "外部认证"
},
"TRIGGERTYPES": {
"1": "身份验证后",
"2": "创建之前",
"3": "创建时"
},
"ADDTRIGGER": "添加触发器",
"FLOWCHANGED": "流量已成功更改",
"FLOWCLEARED": "流已成功删除",
@@ -689,6 +680,10 @@
"CLEAR": {
"TITLE": "清理流程?",
"DESCRIPTION": "您即将重置流程及其触发器和动作。此更改无法恢复。你确定吗?"
},
"REMOVEACTIONSLIST": {
"TITLE": "删除选定的操作?",
"DESCRIPTION": "您确定要从流中删除选定的操作吗?"
}
},
"TOAST": {