feat: enable iframe use (#4766)

* feat: enable iframe use

* cleanup

* fix mocks

* fix linting

* docs: add iframe usage to solution scenarios configurations

* improve api

* feat(console): security policy

* description

* remove unnecessary line

* disable input button and urls when not enabled

* add image to docs

Co-authored-by: Max Peintner <[email protected]>
Co-authored-by: Fabi <[email protected]>
This commit is contained in:
Livio Spring
2022-12-14 07:17:36 +01:00
committed by GitHub
co-authored by Max Peintner Fabi
parent 33e973f015
commit 632639ae7f
40 changed files with 1151 additions and 45 deletions
@@ -0,0 +1,61 @@
<h2>{{ 'SETTINGS.LIST.SECURITY' | translate }}</h2>
<div class="spinner-wr">
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
</div>
<div class="security-wrapper">
<cnsl-info-section [type]="InfoSectionType.ALERT">{{ 'SETTING.SECURITY.DESCRIPTION' | translate }}</cnsl-info-section>
<mat-checkbox
card-actions
class="security-policy-toggle"
color="primary"
ngDefaultControl
(change)="enabledChanged($event)"
[(ngModel)]="enabled"
[disabled]="(['iam.policy.write'] | hasRole | async) === false"
>
{{ 'SETTING.SECURITY.IFRAMEENABLED' | translate }}
</mat-checkbox>
<form class="security-allowed-originsform" (ngSubmit)="add(redInput)">
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'SETTING.SECURITY.ALLOWEDORIGINS' | translate }}</cnsl-label>
<input #redInput cnslInput placeholder="ex. https://" [formControl]="originsControl" />
</cnsl-form-field>
<button
matTooltip="{{ 'ACTIONS.ADD' | translate }}"
type="submit"
mat-icon-button
[disabled]="!enabled || originsControl.invalid || (['iam.policy.write'] | hasRole | async) === false"
>
<mat-icon>add</mat-icon>
</button>
</form>
<div class="security-allowed-uris-list">
<div *ngFor="let uri of originsList" class="uri-line" [ngClass]="{ disabled: !enabled }">
<span class="uri">{{ uri }}</span>
<span class="fill-space"></span>
<button matTooltip="{{ 'ACTIONS.DELETE' | translate }}" mat-icon-button (click)="remove(uri)" class="icon-button">
<mat-icon class="icon">cancel</mat-icon>
</button>
</div>
</div>
</div>
<div class="general-btn-container">
<button
class="save-button"
(click)="savePolicy()"
color="primary"
type="submit"
mat-raised-button
[disabled]="(['iam.policy.write'] | hasRole | async) === false"
>
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
@@ -0,0 +1,97 @@
@use '@angular/material' as mat;
@mixin security-policy-theme($theme) {
$foreground: map-get($theme, foreground);
$background: map-get($theme, background);
$is-dark-theme: map-get($theme, is-dark);
$warn: map-get($theme, warn);
$warn-color: map-get($warn, 500);
$button-text-color: map-get($foreground, text);
$button-disabled-text-color: map-get($foreground, disabled-button);
$divider-color: map-get($foreground, dividers);
$secondary-text: map-get($foreground, secondary-text);
.security-wrapper {
max-width: 500px;
.security-policy-toggle {
margin-top: 0.5rem;
}
.security-allowed-uris-list {
width: 100%;
.uri-line {
display: flex;
align-items: center;
margin: 0.5rem 0;
padding: 0 0 0 0.75rem;
border-radius: 4px;
background: map-get($background, infosection);
height: 30px;
box-sizing: border-box;
.uri {
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 14px;
overflow-x: auto;
}
.fill-space {
flex: 1;
}
.icon-button {
height: 30px;
line-height: 30px;
.icon {
font-size: 1rem;
margin-bottom: 3px;
}
&:not(:hover) {
color: $secondary-text;
}
}
&.disabled {
opacity: 0.5;
.icon-button {
display: none;
}
}
}
}
.security-allowed-originsform {
display: flex;
align-items: flex-end;
min-width: 320px;
.formfield {
width: 500px;
}
button {
margin-bottom: 14px;
margin-right: -0.5rem;
}
}
}
}
.spinner-wr {
margin: 0.5rem 0;
}
.general-btn-container {
display: flex;
justify-content: flex-start;
margin-top: 1rem;
.save-button {
display: block;
}
}
@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SecurityPolicyComponent } from './security-policy.component';
describe('SecurityPolicyComponent', () => {
let component: SecurityPolicyComponent;
let fixture: ComponentFixture<SecurityPolicyComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [SecurityPolicyComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SecurityPolicyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,95 @@
import { Component, Input, OnInit } from '@angular/core';
import { UntypedFormControl } from '@angular/forms';
import { MatCheckboxChange } from '@angular/material/checkbox';
import { SetDefaultLanguageResponse, SetSecurityPolicyRequest } from 'src/app/proto/generated/zitadel/admin_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ToastService } from 'src/app/services/toast.service';
import { InfoSectionType } from '../../info-section/info-section.component';
@Component({
selector: 'cnsl-security-policy',
templateUrl: './security-policy.component.html',
styleUrls: ['./security-policy.component.scss'],
})
export class SecurityPolicyComponent implements OnInit {
public originsList: string[] = [];
public enabled: boolean = false;
public loading: boolean = false;
public InfoSectionType: any = InfoSectionType;
@Input() public originsControl: UntypedFormControl = new UntypedFormControl({ value: [], disabled: true });
constructor(private service: AdminService, private toast: ToastService) {}
ngOnInit(): void {
this.fetchData();
}
private fetchData(): void {
this.service.getSecurityPolicy().then((securityPolicy) => {
if (securityPolicy.policy) {
this.enabled = securityPolicy.policy?.enableIframeEmbedding;
this.originsList = securityPolicy.policy?.allowedOriginsList;
if (securityPolicy.policy.enableIframeEmbedding) {
this.originsControl.enable();
} else {
this.originsControl.disable();
}
}
});
}
private updateData(): Promise<SetDefaultLanguageResponse.AsObject> {
const req = new SetSecurityPolicyRequest();
req.setAllowedOriginsList(this.originsList);
req.setEnableIframeEmbedding(this.enabled);
return (this.service as AdminService).setSecurityPolicy(req);
}
public savePolicy(): void {
const prom = this.updateData();
this.loading = true;
if (prom) {
prom
.then(() => {
this.toast.showInfo('POLICY.SECURITY_POLICY.SAVED', true);
this.loading = false;
setTimeout(() => {
this.fetchData();
}, 2000);
})
.catch((error) => {
this.loading = false;
this.toast.showError(error);
});
}
}
public add(input: any): void {
if (this.originsControl.valid) {
if (input.value !== '' && input.value !== ' ' && input.value !== '/') {
this.originsList.push(input.value);
}
if (input) {
input.value = '';
}
}
}
public remove(redirect: any): void {
const index = this.originsList.indexOf(redirect);
if (index >= 0) {
this.originsList.splice(index, 1);
}
}
public enabledChanged(event: MatCheckboxChange) {
if (event.checked) {
this.originsControl.enable();
} else {
this.originsControl.disable();
}
}
}
@@ -0,0 +1,41 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { CardModule } from '../../card/card.module';
import { FormFieldModule } from '../../form-field/form-field.module';
import { InfoSectionModule } from '../../info-section/info-section.module';
import { InputModule } from '../../input/input.module';
import { SecurityPolicyComponent } from './security-policy.component';
@NgModule({
declarations: [SecurityPolicyComponent],
imports: [
CommonModule,
CardModule,
FormsModule,
InfoSectionModule,
MatCheckboxModule,
FormsModule,
ReactiveFormsModule,
MatButtonModule,
FormFieldModule,
InputModule,
MatIconModule,
MatProgressSpinnerModule,
MatSelectModule,
HasRolePipeModule,
MatTooltipModule,
TranslateModule,
],
exports: [SecurityPolicyComponent],
})
export class SecurityPolicyModule {}
@@ -35,7 +35,9 @@
<ng-container *ngIf="currentSetting === 'secrets' && serviceType === PolicyComponentServiceType.ADMIN">
<cnsl-secret-generator></cnsl-secret-generator>
</ng-container>
<ng-container *ngIf="currentSetting === 'security' && serviceType === PolicyComponentServiceType.ADMIN">
<cnsl-security-policy></cnsl-security-policy>
</ng-container>
<ng-container *ngIf="currentSetting === 'branding'">
<cnsl-private-labeling-policy [serviceType]="serviceType"></cnsl-private-labeling-policy>
</ng-container>
@@ -18,6 +18,7 @@ import { PasswordLockoutPolicyModule } from '../policies/password-lockout-policy
import { PrivacyPolicyModule } from '../policies/privacy-policy/privacy-policy.module';
import { PrivateLabelingPolicyModule } from '../policies/private-labeling-policy/private-labeling-policy.module';
import { SecretGeneratorModule } from '../policies/secret-generator/secret-generator.module';
import { SecurityPolicyModule } from '../policies/security-policy/security-policy.module';
import { SidenavModule } from '../sidenav/sidenav.module';
import { SettingsListComponent } from './settings-list.component';
@@ -36,6 +37,7 @@ import { SettingsListComponent } from './settings-list.component';
IdpSettingsModule,
PrivacyPolicyModule,
MessageTextsPolicyModule,
SecurityPolicyModule,
LoginTextsPolicyModule,
DomainPolicyModule,
TranslateModule,
@@ -25,6 +25,14 @@ export const SECRETS: SidenavSetting = {
},
};
export const SECURITY: SidenavSetting = {
id: 'security',
i18nKey: 'SETTINGS.LIST.SECURITY',
requiredRoles: {
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
},
};
export const LOGIN: SidenavSetting = {
id: 'login',
i18nKey: 'SETTINGS.LIST.LOGIN',