1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-09-30 04:28:19 +02:00

Refactor orgnaization policy management (#1147)

This commit is contained in:
Oscar Hinton 2021-08-25 16:10:17 +02:00 committed by GitHub
parent 8a259516df
commit 2cbe023a38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 687 additions and 437 deletions

View File

@ -20,10 +20,10 @@ import { ProviderUserType } from 'jslib-common/enums/providerUserType';
import { ValidationService } from 'jslib-angular/services/validation.service'; import { ValidationService } from 'jslib-angular/services/validation.service';
import { Organization } from 'jslib-common/models/domain/organization';
import { import {
ProviderOrganizationOrganizationDetailsResponse ProviderOrganizationOrganizationDetailsResponse
} from 'jslib-common/models/response/provider/providerOrganizationResponse'; } from 'jslib-common/models/response/provider/providerOrganizationResponse';
import { Organization } from 'jslib-common/models/domain/organization';
import { ModalComponent } from 'src/app/modal.component'; import { ModalComponent } from 'src/app/modal.component';
@ -88,7 +88,7 @@ export class ClientsComponent implements OnInit {
.map(o => o.id)); .map(o => o.id));
this.addableOrganizations = candidateOrgs.filter(o => allowedOrgsIds.includes(o.id)); this.addableOrganizations = candidateOrgs.filter(o => allowedOrgsIds.includes(o.id));
this.showAddExisting = this.addableOrganizations.length != 0; this.showAddExisting = this.addableOrganizations.length !== 0;
this.loading = false; this.loading = false;
} }

View File

@ -63,7 +63,7 @@ export class SetupComponent implements OnInit {
this.providerId = qParams.providerId; this.providerId = qParams.providerId;
this.token = qParams.token; this.token = qParams.token;
// Check if provider exists, redirect if it does // Check if provider exists, redirect if it does
try { try {
const provider = await this.apiService.getProvider(this.providerId); const provider = await this.apiService.getProvider(this.providerId);

2
jslib

@ -1 +1 @@
Subproject commit 1f0127966e85aa29f9e50144de9b2a03b00de5d4 Subproject commit add4b2f3e9d85a4a68d67a0da09be14cefe9a6b3

View File

@ -46,8 +46,19 @@ import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.serv
import { ConstantsService } from 'jslib-common/services/constants.service'; import { ConstantsService } from 'jslib-common/services/constants.service';
import { PolicyListService } from './services/policy-list.service';
import { RouterService } from './services/router.service'; import { RouterService } from './services/router.service';
import { DisableSendPolicy } from './organizations/policies/disable-send.component';
import { MasterPasswordPolicy } from './organizations/policies/master-password.component';
import { PasswordGeneratorPolicy } from './organizations/policies/password-generator.component';
import { PersonalOwnershipPolicy } from './organizations/policies/personal-ownership.component';
import { RequireSsoPolicy } from './organizations/policies/require-sso.component';
import { ResetPasswordPolicy } from './organizations/policies/reset-password.component';
import { SendOptionsPolicy } from './organizations/policies/send-options.component';
import { SingleOrgPolicy } from './organizations/policies/single-org.component';
import { TwoFactorAuthenticationPolicy } from './organizations/policies/two-factor-authentication.component';
const BroadcasterSubscriptionId = 'AppComponent'; const BroadcasterSubscriptionId = 'AppComponent';
const IdleTimeout = 60000 * 10; // 10 minutes const IdleTimeout = 60000 * 10; // 10 minutes
@ -56,6 +67,7 @@ const IdleTimeout = 60000 * 10; // 10 minutes
templateUrl: 'app.component.html', templateUrl: 'app.component.html',
}) })
export class AppComponent implements OnDestroy, OnInit { export class AppComponent implements OnDestroy, OnInit {
toasterConfig: ToasterConfig = new ToasterConfig({ toasterConfig: ToasterConfig = new ToasterConfig({
showCloseButton: true, showCloseButton: true,
mouseoverTimerStop: true, mouseoverTimerStop: true,
@ -80,7 +92,7 @@ export class AppComponent implements OnDestroy, OnInit {
private sanitizer: DomSanitizer, private searchService: SearchService, private sanitizer: DomSanitizer, private searchService: SearchService,
private notificationsService: NotificationsService, private routerService: RouterService, private notificationsService: NotificationsService, private routerService: RouterService,
private stateService: StateService, private eventService: EventService, private stateService: StateService, private eventService: EventService,
private policyService: PolicyService) { } private policyService: PolicyService, protected policyListService: PolicyListService) { }
ngOnInit() { ngOnInit() {
this.ngZone.runOutsideAngular(() => { this.ngZone.runOutsideAngular(() => {
@ -170,6 +182,18 @@ export class AppComponent implements OnDestroy, OnInit {
} }
}); });
this.policyListService.addPolicies([
new TwoFactorAuthenticationPolicy(),
new MasterPasswordPolicy(),
new PasswordGeneratorPolicy(),
new SingleOrgPolicy(),
new RequireSsoPolicy(),
new PersonalOwnershipPolicy(),
new DisableSendPolicy(),
new SendOptionsPolicy(),
new ResetPasswordPolicy(),
]);
this.setFullWidth(); this.setFullWidth();
} }

View File

@ -1,8 +1,3 @@
<app-callout *ngIf="userCanAccessBusinessPortal" [type]="'warning'">
<p>{{'webPoliciesDeprecationWarning' | i18n}}</p>
<button type="button" class="btn btn-outline-secondary"
(click)="goToEnterprisePortal()">{{'businessPortal' | i18n}}</button>
</app-callout>
<div class="page-header d-flex"> <div class="page-header d-flex">
<h1>{{'policies' | i18n}}</h1> <h1>{{'policies' | i18n}}</h1>
</div> </div>
@ -13,10 +8,10 @@
<table class="table table-hover table-list" *ngIf="!loading"> <table class="table table-hover table-list" *ngIf="!loading">
<tbody> <tbody>
<tr *ngFor="let p of policies"> <tr *ngFor="let p of policies">
<td *ngIf="p.display"> <td *ngIf="p.display(organization)">
<a href="#" appStopClick (click)="edit(p)">{{p.name}}</a> <a href="#" appStopClick (click)="edit(p)">{{p.name | i18n}}</a>
<span class="badge badge-success" *ngIf="p.enabled">{{'enabled' | i18n}}</span> <span class="badge badge-success" *ngIf="policiesEnabledMap.get(p.type)">{{'enabled' | i18n}}</span>
<small class="text-muted d-block">{{p.description}}</small> <small class="text-muted d-block">{{p.description | i18n}}</small>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

@ -12,8 +12,8 @@ import {
import { PolicyType } from 'jslib-common/enums/policyType'; import { PolicyType } from 'jslib-common/enums/policyType';
import { EnvironmentService } from 'jslib-common/abstractions';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from 'jslib-common/abstractions/user.service';
@ -22,8 +22,13 @@ import { PolicyResponse } from 'jslib-common/models/response/policyResponse';
import { ModalComponent } from '../../modal.component'; import { ModalComponent } from '../../modal.component';
import { Organization } from 'jslib-common/models/domain/organization';
import { PolicyEditComponent } from './policy-edit.component'; import { PolicyEditComponent } from './policy-edit.component';
import { PolicyListService } from 'src/app/services/policy-list.service';
import { BasePolicy } from '../policies/base-policy.component';
@Component({ @Component({
selector: 'app-org-policies', selector: 'app-org-policies',
templateUrl: 'policies.component.html', templateUrl: 'policies.component.html',
@ -33,11 +38,11 @@ export class PoliciesComponent implements OnInit {
loading = true; loading = true;
organizationId: string; organizationId: string;
policies: any[]; policies: BasePolicy[];
organization: Organization;
// Remove when removing deprecation warning // Remove when removing deprecation warning
enterpriseTokenPromise: Promise<any>; enterpriseTokenPromise: Promise<any>;
userCanAccessBusinessPortal = false;
private enterpriseUrl: string; private enterpriseUrl: string;
@ -48,81 +53,20 @@ export class PoliciesComponent implements OnInit {
constructor(private apiService: ApiService, private route: ActivatedRoute, constructor(private apiService: ApiService, private route: ActivatedRoute,
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver, private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
private platformUtilsService: PlatformUtilsService, private userService: UserService, private platformUtilsService: PlatformUtilsService, private userService: UserService,
private router: Router, private environmentService: EnvironmentService) { } private policyListService: PolicyListService, private router: Router,
private environmentService: EnvironmentService) { }
async ngOnInit() { async ngOnInit() {
this.route.parent.parent.params.subscribe(async params => { this.route.parent.parent.params.subscribe(async params => {
this.organizationId = params.organizationId; this.organizationId = params.organizationId;
const organization = await this.userService.getOrganization(this.organizationId); this.organization = await this.userService.getOrganization(this.organizationId);
if (organization == null || !organization.usePolicies) { if (this.organization == null || !this.organization.usePolicies) {
this.router.navigate(['/organizations', this.organizationId]); this.router.navigate(['/organizations', this.organizationId]);
return; return;
} }
this.userCanAccessBusinessPortal = organization.canAccessBusinessPortal;
this.policies = [ this.policies = this.policyListService.getPolicies();
{
name: this.i18nService.t('twoStepLogin'),
description: this.i18nService.t('twoStepLoginPolicyDesc'),
type: PolicyType.TwoFactorAuthentication,
enabled: false,
display: true,
},
{
name: this.i18nService.t('masterPass'),
description: this.i18nService.t('masterPassPolicyDesc'),
type: PolicyType.MasterPassword,
enabled: false,
display: true,
},
{
name: this.i18nService.t('passwordGenerator'),
description: this.i18nService.t('passwordGeneratorPolicyDesc'),
type: PolicyType.PasswordGenerator,
enabled: false,
display: true,
},
{
name: this.i18nService.t('singleOrg'),
description: this.i18nService.t('singleOrgDesc'),
type: PolicyType.SingleOrg,
enabled: false,
display: true,
},
{
name: this.i18nService.t('requireSso'),
description: this.i18nService.t('requireSsoPolicyDesc'),
type: PolicyType.RequireSso,
enabled: false,
display: organization.useSso,
},
{
name: this.i18nService.t('personalOwnership'),
description: this.i18nService.t('personalOwnershipPolicyDesc'),
type: PolicyType.PersonalOwnership,
enabled: false,
display: true,
},
{
name: this.i18nService.t('disableSend'),
description: this.i18nService.t('disableSendPolicyDesc'),
type: PolicyType.DisableSend,
enabled: false,
display: true,
},
{
name: this.i18nService.t('sendOptions'),
description: this.i18nService.t('sendOptionsPolicyDesc'),
type: PolicyType.SendOptions,
enabled: false,
display: true,
}, {
name: this.i18nService.t('resetPasswordPolicy'),
description: this.i18nService.t('resetPasswordPolicyDescription'),
type: PolicyType.ResetPassword,
enabled: false,
display: organization.useResetPassword,
},
];
await this.load(); await this.load();
// Handle policies component launch from Event message // Handle policies component launch from Event message
@ -158,13 +102,11 @@ export class PoliciesComponent implements OnInit {
this.orgPolicies.forEach(op => { this.orgPolicies.forEach(op => {
this.policiesEnabledMap.set(op.type, op.enabled); this.policiesEnabledMap.set(op.type, op.enabled);
}); });
this.policies.forEach(p => {
p.enabled = this.policiesEnabledMap.has(p.type) && this.policiesEnabledMap.get(p.type);
});
this.loading = false; this.loading = false;
} }
edit(p: any) { edit(policy: BasePolicy) {
if (this.modal != null) { if (this.modal != null) {
this.modal.close(); this.modal.close();
} }
@ -174,9 +116,7 @@ export class PoliciesComponent implements OnInit {
const childComponent = this.modal.show<PolicyEditComponent>( const childComponent = this.modal.show<PolicyEditComponent>(
PolicyEditComponent, this.editModalRef); PolicyEditComponent, this.editModalRef);
childComponent.name = p.name; childComponent.policy = policy;
childComponent.description = p.description;
childComponent.type = p.type;
childComponent.organizationId = this.organizationId; childComponent.organizationId = this.organizationId;
childComponent.policiesEnabledMap = this.policiesEnabledMap; childComponent.policiesEnabledMap = this.policiesEnabledMap;
childComponent.onSavedPolicy.subscribe(() => { childComponent.onSavedPolicy.subscribe(() => {

View File

@ -2,178 +2,21 @@
<div class="modal-dialog modal-dialog-scrollable" role="document"> <div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate> <form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div class="modal-header"> <div class="modal-header">
<h2 class="modal-title" id="policiesEditTitle">{{'editPolicy' | i18n}} - {{name}}</h2> <h2 class="modal-title" id="policiesEditTitle">{{'editPolicy' | i18n}} - {{policy.name | i18n}}</h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}"> <button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body" *ngIf="loading">
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i> <div class="modal-body">
<span class="sr-only">{{'loading' | i18n}}</span> <div class="modal-body" *ngIf="loading">
</div> <i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<div class="modal-body" *ngIf="!loading"> <span class="sr-only">{{'loading' | i18n}}</span>
<p>{{description}}</p> </div>
<app-callout type="warning" *ngIf="type === policyType.TwoFactorAuthentication" <div [hidden]="loading">
title="{{'warning' | i18n}}" icon="fa-warning"> <p>{{policy.description | i18n}}</p>
{{'twoStepLoginPolicyWarning' | i18n}} <ng-template #policyForm></ng-template>
</app-callout>
<app-callout type="warning" *ngIf="type === policyType.SingleOrg" title="{{'warning' | i18n}}"
icon="fa-warning">
{{'singleOrgPolicyWarning' | i18n}}
</app-callout>
<ng-container *ngIf="type === policyType.RequireSso">
<app-callout type="tip" title="{{'prerequisite' | i18n}}">
{{'requireSsoPolicyReq' | i18n}}
</app-callout>
<app-callout type="warning">
{{'requireSsoExemption' | i18n}}
</app-callout>
</ng-container>
<app-callout type="warning" *ngIf="type === policyType.PersonalOwnership">
{{'personalOwnershipExemption' | i18n}}
</app-callout>
<app-callout type="warning" *ngIf="type === policyType.DisableSend">
{{'disableSendExemption' | i18n}}
</app-callout>
<app-callout type="warning" *ngIf="type === policyType.SendOptions">
{{'sendOptionsExemption' | i18n}}
</app-callout>
<app-callout type="warning" *ngIf="type === policyType.ResetPassword">
{{'resetPasswordPolicyWarning' | i18n}}
</app-callout>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="enabled" [(ngModel)]="enabled"
name="Enabled">
<label class="form-check-label" for="enabled">{{checkboxDesc}}</label>
</div>
</div> </div>
<ng-container *ngIf="type === policyType.MasterPassword">
<div class="row">
<div class="col-6 form-group">
<label for="masterPassMinComplexity">{{'minComplexityScore' | i18n}}</label>
<select id="masterPassMinComplexity" name="MasterPassMinComplexity"
[(ngModel)]="masterPassMinComplexity" class="form-control">
<option *ngFor="let o of passwordScores" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<div class="col-6 form-group">
<label for="masterPassMinLength">{{'minLength' | i18n}}</label>
<input id="masterPassMinLength" class="form-control" type="number" min="8"
name="MasterPassMinLength" [(ngModel)]="masterPassMinLength">
</div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="masterPassRequireUpper"
[(ngModel)]="masterPassRequireUpper" name="MasterPassRequireUpper">
<label class="form-check-label" for="masterPassRequireUpper">A-Z</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="masterPassRequireLower"
[(ngModel)]="masterPassRequireLower" name="MasterPassRequireLower">
<label class="form-check-label" for="masterPassRequireLower">a-z</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="masterPassRequireNumbers"
[(ngModel)]="masterPassRequireNumbers" name="MasterPassRequireNumbers">
<label class="form-check-label" for="masterPassRequireNumbers">0-9</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="masterPassRequireSpecial"
[(ngModel)]="masterPassRequireSpecial" name="MasterPassRequireSpecial">
<label class="form-check-label" for="masterPassRequireSpecial">!@#$%^&amp;*</label>
</div>
</ng-container>
<ng-container *ngIf="type === policyType.PasswordGenerator">
<div class="row">
<div class="col-6 form-group mb-0">
<label for="passGenDefaultType">{{'defaultType' | i18n}}</label>
<select id="passGenDefaultType" name="PassGenDefaultType" [(ngModel)]="passGenDefaultType"
class="form-control">
<option *ngFor="let o of defaultTypes" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
</div>
<h3 class="mt-4">{{'password' | i18n}}</h3>
<div class="row">
<div class="col-6 form-group">
<label for="passGenMinLength">{{'minLength' | i18n}}</label>
<input id="passGenMinLength" class="form-control" type="number" name="PassGenMinLength"
min="5" max="128" [(ngModel)]="passGenMinLength">
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="passGenMinNumbers">{{'minNumbers' | i18n}}</label>
<input id="passGenMinNumbers" class="form-control" type="number" name="PassGenMinNumbers"
min="0" max="9" [(ngModel)]="passGenMinNumbers">
</div>
<div class="col-6 form-group">
<label for="passGenMinSpecial">{{'minSpecial' | i18n}}</label>
<input id="passGenMinSpecial" class="form-control" type="number" name="PassGenMinSpecial"
min="0" max="9" [(ngModel)]="passGenMinSpecial">
</div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="passGenUseUpper"
[(ngModel)]="passGenUseUpper" name="PassGenUseUpper">
<label class="form-check-label" for="passGenUseUpper">A-Z</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="passGenUseLower"
[(ngModel)]="passGenUseLower" name="PassGenUseLower">
<label class="form-check-label" for="passGenUseLower">a-z</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="passGenUseNumbers"
[(ngModel)]="passGenUseNumbers" name="PassGenUseNumbers">
<label class="form-check-label" for="passGenUseNumbers">0-9</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="passGenUseSpecial"
[(ngModel)]="passGenUseSpecial" name="PassGenUseSpecial">
<label class="form-check-label" for="passGenUseSpecial">!@#$%^&amp;*</label>
</div>
<h3 class="mt-4">{{'passphrase' | i18n}}</h3>
<div class="row">
<div class="col-6 form-group">
<label for="passGenMinNumberWords">{{'minimumNumberOfWords' | i18n}}</label>
<input id="passGenMinNumberWords" class="form-control" type="number"
name="PassGenMinNumberWords" min="3" max="20" [(ngModel)]="passGenMinNumberWords">
</div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="passGenCapitalize"
[(ngModel)]="passGenCapitalize" name="PassGenCapitalize">
<label class="form-check-label" for="passGenCapitalize">{{'capitalize' | i18n}}</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="passGenIncludeNumber"
[(ngModel)]="passGenIncludeNumber" name="PassGenIncludeNumber">
<label class="form-check-label" for="passGenIncludeNumber">{{'includeNumber' | i18n}}</label>
</div>
</ng-container>
<ng-container *ngIf="type === policyType.SendOptions">
<h3 class="mt-4">{{'options' | i18n}}</h3>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="sendDisableHideEmail"
[(ngModel)]="sendDisableHideEmail" name="SendDisableHideEmail">
<label class="form-check-label" for="sendDisableHideEmail">{{'disableHideEmail' | i18n}}</label>
</div>
</ng-container>
<ng-container *ngIf="type === policyType.ResetPassword">
<h3 class="mt-4">{{'resetPasswordPolicyAutoEnroll' | i18n}}</h3>
<p>{{'resetPasswordPolicyAutoEnrollDescription' | i18n}}</p>
<app-callout type="warning">
{{'resetPasswordPolicyAutoEnrollWarning' | i18n}}
</app-callout>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="autoEnrollEnabled"
[(ngModel)]="resetPasswordAutoEnroll" name="AutoEnrollEnabled">
<label class="form-check-label"
for="autoEnrollEnabled">{{'resetPasswordPolicyAutoEnrollCheckbox' | i18n }}</label>
</div>
</ng-container>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading"> <button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">

View File

@ -1,9 +1,12 @@
import { import {
ChangeDetectorRef,
Component, Component,
ComponentFactoryResolver,
EventEmitter, EventEmitter,
Input, Input,
OnInit,
Output, Output,
ViewChild,
ViewContainerRef,
} from '@angular/core'; } from '@angular/core';
import { ToasterService } from 'angular2-toaster'; import { ToasterService } from 'angular2-toaster';
@ -17,119 +20,52 @@ import { PolicyRequest } from 'jslib-common/models/request/policyRequest';
import { PolicyResponse } from 'jslib-common/models/response/policyResponse'; import { PolicyResponse } from 'jslib-common/models/response/policyResponse';
import { BasePolicy, BasePolicyComponent } from '../policies/base-policy.component';
@Component({ @Component({
selector: 'app-policy-edit', selector: 'app-policy-edit',
templateUrl: 'policy-edit.component.html', templateUrl: 'policy-edit.component.html',
}) })
export class PolicyEditComponent implements OnInit { export class PolicyEditComponent {
@Input() name: string; @Input() policy: BasePolicy;
@Input() description: string;
@Input() type: PolicyType;
@Input() organizationId: string; @Input() organizationId: string;
@Input() policiesEnabledMap: Map<PolicyType, boolean> = new Map<PolicyType, boolean>(); @Input() policiesEnabledMap: Map<PolicyType, boolean> = new Map<PolicyType, boolean>();
@Output() onSavedPolicy = new EventEmitter(); @Output() onSavedPolicy = new EventEmitter();
@ViewChild('policyForm', { read: ViewContainerRef, static: true }) policyFormRef: ViewContainerRef;
policyType = PolicyType; policyType = PolicyType;
loading = true; loading = true;
enabled = false; enabled = false;
formPromise: Promise<any>; formPromise: Promise<any>;
passwordScores: any[];
defaultTypes: any[]; defaultTypes: any[];
policyComponent: BasePolicyComponent;
// Master password private policyResponse: PolicyResponse;
masterPassMinComplexity?: number = null;
masterPassMinLength?: number;
masterPassRequireUpper?: number;
masterPassRequireLower?: number;
masterPassRequireNumbers?: number;
masterPassRequireSpecial?: number;
// Password generator
passGenDefaultType?: string;
passGenMinLength?: number;
passGenUseUpper?: boolean;
passGenUseLower?: boolean;
passGenUseNumbers?: boolean;
passGenUseSpecial?: boolean;
passGenMinNumbers?: number;
passGenMinSpecial?: number;
passGenMinNumberWords?: number;
passGenCapitalize?: boolean;
passGenIncludeNumber?: boolean;
// Send options
sendDisableHideEmail?: boolean;
// Reset Password
resetPasswordAutoEnroll?: boolean;
private policy: PolicyResponse;
constructor(private apiService: ApiService, private i18nService: I18nService, constructor(private apiService: ApiService, private i18nService: I18nService,
private toasterService: ToasterService) { private toasterService: ToasterService, private componentFactoryResolver: ComponentFactoryResolver,
this.passwordScores = [ private cdr: ChangeDetectorRef) {
{ name: '-- ' + i18nService.t('select') + ' --', value: null },
{ name: i18nService.t('weak') + ' (0)', value: 0 },
{ name: i18nService.t('weak') + ' (1)', value: 1 },
{ name: i18nService.t('weak') + ' (2)', value: 2 },
{ name: i18nService.t('good') + ' (3)', value: 3 },
{ name: i18nService.t('strong') + ' (4)', value: 4 },
];
this.defaultTypes = [
{ name: i18nService.t('userPreference'), value: null },
{ name: i18nService.t('password'), value: 'password' },
{ name: i18nService.t('passphrase'), value: 'passphrase' },
];
} }
async ngOnInit() { async ngAfterViewInit() {
await this.load(); await this.load();
this.loading = false; this.loading = false;
const factory = this.componentFactoryResolver.resolveComponentFactory(this.policy.component);
this.policyComponent = this.policyFormRef.createComponent(factory).instance as BasePolicyComponent;
this.policyComponent.policy = this.policy;
this.policyComponent.policyResponse = this.policyResponse;
this.cdr.detectChanges();
} }
async load() { async load() {
try { try {
this.policy = await this.apiService.getPolicy(this.organizationId, this.type); this.policyResponse = await this.apiService.getPolicy(this.organizationId, this.policy.type);
if (this.policy != null) {
this.enabled = this.policy.enabled;
if (this.policy.data != null) {
switch (this.type) {
case PolicyType.PasswordGenerator:
this.passGenDefaultType = this.policy.data.defaultType;
this.passGenMinLength = this.policy.data.minLength;
this.passGenUseUpper = this.policy.data.useUpper;
this.passGenUseLower = this.policy.data.useLower;
this.passGenUseNumbers = this.policy.data.useNumbers;
this.passGenUseSpecial = this.policy.data.useSpecial;
this.passGenMinNumbers = this.policy.data.minNumbers;
this.passGenMinSpecial = this.policy.data.minSpecial;
this.passGenMinNumberWords = this.policy.data.minNumberWords;
this.passGenCapitalize = this.policy.data.capitalize;
this.passGenIncludeNumber = this.policy.data.includeNumber;
break;
case PolicyType.MasterPassword:
this.masterPassMinComplexity = this.policy.data.minComplexity;
this.masterPassMinLength = this.policy.data.minLength;
this.masterPassRequireUpper = this.policy.data.requireUpper;
this.masterPassRequireLower = this.policy.data.requireLower;
this.masterPassRequireNumbers = this.policy.data.requireNumbers;
this.masterPassRequireSpecial = this.policy.data.requireSpecial;
break;
case PolicyType.SendOptions:
this.sendDisableHideEmail = this.policy.data.disableHideEmail;
break;
case PolicyType.ResetPassword:
this.resetPasswordAutoEnroll = this.policy.data.autoEnrollEnabled;
break;
default:
break;
}
}
}
} catch (e) { } catch (e) {
if (e.statusCode === 404) { if (e.statusCode === 404) {
this.enabled = false; this.policyResponse = new PolicyResponse({Enabled: false});
} else { } else {
throw e; throw e;
} }
@ -137,94 +73,19 @@ export class PolicyEditComponent implements OnInit {
} }
async submit() { async submit() {
if (this.preValidate()) { let request: PolicyRequest;
const request = new PolicyRequest(); try {
request.enabled = this.enabled; request = await this.policyComponent.buildRequest(this.policiesEnabledMap);
request.type = this.type; } catch (e) {
request.data = null; this.toasterService.pop('error', null, e);
switch (this.type) { return;
case PolicyType.PasswordGenerator:
request.data = {
defaultType: this.passGenDefaultType,
minLength: this.passGenMinLength || null,
useUpper: this.passGenUseUpper,
useLower: this.passGenUseLower,
useNumbers: this.passGenUseNumbers,
useSpecial: this.passGenUseSpecial,
minNumbers: this.passGenMinNumbers || null,
minSpecial: this.passGenMinSpecial || null,
minNumberWords: this.passGenMinNumberWords || null,
capitalize: this.passGenCapitalize,
includeNumber: this.passGenIncludeNumber,
};
break;
case PolicyType.MasterPassword:
request.data = {
minComplexity: this.masterPassMinComplexity || null,
minLength: this.masterPassMinLength || null,
requireUpper: this.masterPassRequireUpper,
requireLower: this.masterPassRequireLower,
requireNumbers: this.masterPassRequireNumbers,
requireSpecial: this.masterPassRequireSpecial,
};
break;
case PolicyType.SendOptions:
request.data = {
disableHideEmail: this.sendDisableHideEmail,
};
break;
case PolicyType.ResetPassword:
request.data = {
autoEnrollEnabled: this.resetPasswordAutoEnroll,
};
break;
default:
break;
}
try {
this.formPromise = this.apiService.putPolicy(this.organizationId, this.type, request);
await this.formPromise;
this.toasterService.popAsync('success', null, this.i18nService.t('editedPolicyId', this.name));
this.onSavedPolicy.emit();
} catch { }
} }
}
get checkboxDesc(): string { try {
return this.type === PolicyType.PersonalOwnership ? this.i18nService.t('personalOwnershipCheckboxDesc') : this.formPromise = this.apiService.putPolicy(this.organizationId, this.policy.type, request);
this.i18nService.t('enabled'); await this.formPromise;
} this.toasterService.popAsync('success', null, this.i18nService.t('editedPolicyId', this.i18nService.t(this.policy.name)));
this.onSavedPolicy.emit();
private preValidate(): boolean { } catch {}
switch (this.type) {
case PolicyType.RequireSso:
// Don't need prevalidation checks if submitting to disable
if (!this.enabled) {
return true;
}
// Have SingleOrg policy enabled?
if (!(this.policiesEnabledMap.has(PolicyType.SingleOrg)
&& this.policiesEnabledMap.get(PolicyType.SingleOrg))) {
this.toasterService.popAsync('error', null, this.i18nService.t('requireSsoPolicyReqError'));
return false;
}
return true;
case PolicyType.SingleOrg:
// Don't need prevalidation checks if submitting to enable
if (this.enabled) {
return true;
}
// If RequireSso Policy is enabled prevent submittal
if (this.policiesEnabledMap.has(PolicyType.RequireSso)
&& this.policiesEnabledMap.get(PolicyType.RequireSso)) {
this.toasterService.popAsync('error', null, this.i18nService.t('disableRequireSsoError'));
return false;
}
return true;
default:
return true;
}
} }
} }

View File

@ -0,0 +1,54 @@
import {
Directive,
Input,
OnInit,
} from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Organization } from 'jslib-common/models/domain/organization';
import { PolicyType } from 'jslib-common/enums/policyType';
import { PolicyRequest } from 'jslib-common/models/request/policyRequest';
import { PolicyResponse } from 'jslib-common/models/response/policyResponse';
export abstract class BasePolicy {
abstract name: string;
abstract description: string;
abstract type: PolicyType;
abstract component: any;
display(organization: Organization) {
return true;
}
}
@Directive()
export abstract class BasePolicyComponent implements OnInit {
@Input() policyResponse: PolicyResponse;
@Input() policy: BasePolicy;
enabled = new FormControl(false);
data: FormGroup = null;
ngOnInit(): void {
this.enabled.setValue(this.policyResponse.enabled);
if (this.data != null) {
this.data.patchValue(this.policyResponse.data ?? {});
}
}
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>) {
const request = new PolicyRequest();
request.enabled = this.enabled.value;
request.type = this.policy.type;
if (this.data != null) {
request.data = this.data.value;
}
return Promise.resolve(request);
}
}

View File

@ -0,0 +1,10 @@
<app-callout type="warning">
{{'disableSendExemption' | i18n}}
</app-callout>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="enabled" [formControl]="enabled" name="Enabled">
<label class="form-check-label" for="enabled">{{'enabled' | i18n}}</label>
</div>
</div>

View File

@ -0,0 +1,19 @@
import { Component } from '@angular/core';
import { PolicyType } from 'jslib-common/enums/policyType';
import { BasePolicy, BasePolicyComponent } from './base-policy.component';
export class DisableSendPolicy extends BasePolicy {
name = 'disableSend';
description = 'disableSendPolicyDesc';
type = PolicyType.DisableSend;
component = DisableSendPolicyComponent;
}
@Component({
selector: 'policy-disable-send',
templateUrl: 'disable-send.component.html',
})
export class DisableSendPolicyComponent extends BasePolicyComponent {
}

View File

@ -0,0 +1,42 @@
<div [formGroup]="data">
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="enabled" [formControl]="enabled" name="Enabled">
<label class="form-check-label" for="enabled">{{'enabled' | i18n}}</label>
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="minComplexity">{{'minComplexityScore' | i18n}}</label>
<select id="minComplexity" name="minComplexity" formControlName="minComplexity" class="form-control">
<option *ngFor="let o of passwordScores" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<div class="col-6 form-group">
<label for="minLength">{{'minLength' | i18n}}</label>
<input id="minLength" class="form-control" type="number" min="8" name="minLength"
formControlName="minLength">
</div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="requireUpper" name="requireUpper"
formControlName="requireUpper">
<label class="form-check-label" for="requireUpper">A-Z</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="requireLower" name="requireLower"
formControlName="requireLower">
<label class="form-check-label" for="requireLower">a-z</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="requireNumbers" name="requireNumbers"
formControlName="requireNumbers">
<label class="form-check-label" for="requireNumbers">0-9</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="requireSpecial" name="requireSpecial"
formControlName="requireSpecial">
<label class="form-check-label" for="requireSpecial">!@#$%^&amp;*</label>
</div>
</div>

View File

@ -0,0 +1,46 @@
import { Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PolicyType } from 'jslib-common/enums/policyType';
import { BasePolicy, BasePolicyComponent } from './base-policy.component';
export class MasterPasswordPolicy extends BasePolicy {
name = 'masterPass';
description = 'masterPassPolicyDesc';
type = PolicyType.MasterPassword;
component = MasterPasswordPolicyComponent;
}
@Component({
selector: 'policy-master-password',
templateUrl: 'master-password.component.html',
})
export class MasterPasswordPolicyComponent extends BasePolicyComponent {
data = this.fb.group({
minComplexity: [null],
minLength: [null],
requireUpper: [null],
requireLower: [null],
requireNumbers: [null],
requireSpecial: [null],
});
passwordScores: { name: string; value: number; }[];
constructor(private fb: FormBuilder, i18nService: I18nService) {
super();
this.passwordScores = [
{ name: '-- ' + i18nService.t('select') + ' --', value: null },
{ name: i18nService.t('weak') + ' (0)', value: 0 },
{ name: i18nService.t('weak') + ' (1)', value: 1 },
{ name: i18nService.t('weak') + ' (2)', value: 2 },
{ name: i18nService.t('good') + ' (3)', value: 3 },
{ name: i18nService.t('strong') + ' (4)', value: 4 },
];
}
}

View File

@ -0,0 +1,72 @@
<div [formGroup]="data">
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="enabled" [formControl]="enabled" name="Enabled">
<label class="form-check-label" for="enabled">{{'enabled' | i18n}}</label>
</div>
</div>
<div class="row">
<div class="col-6 form-group mb-0">
<label for="defaultType">{{'defaultType' | i18n}}</label>
<select id="defaultType" name="defaultType" formControlName="defaultType" class="form-control">
<option *ngFor="let o of defaultTypes" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
</div>
<h3 class="mt-4">{{'password' | i18n}}</h3>
<div class="row">
<div class="col-6 form-group">
<label for="minLength">{{'minLength' | i18n}}</label>
<input id="minLength" class="form-control" type="number" name="minLength" min="5" max="128"
formControlName="minLength">
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="minNumbers">{{'minNumbers' | i18n}}</label>
<input id="minNumbers" class="form-control" type="number" name="minNumbers" min="0" max="9"
formControlName="minNumbers">
</div>
<div class="col-6 form-group">
<label for="minSpecial">{{'minSpecial' | i18n}}</label>
<input id="minSpecial" class="form-control" type="number" name="minSpecial" min="0" max="9"
formControlName="minSpecial">
</div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="useUpper"
formControlName="useUpper" name="useUpper">
<label class="form-check-label" for="useUpper">A-Z</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="useLower" name="useLower" formControlName="useLower">
<label class="form-check-label" for="useLower">a-z</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="useNumbers" name="useNumbers" formControlName="useNumbers">
<label class="form-check-label" for="useNumbers">0-9</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="useSpecial" name="useSpecial" formControlName="useSpecial">
<label class="form-check-label" for="useSpecial">!@#$%^&amp;*</label>
</div>
<h3 class="mt-4">{{'passphrase' | i18n}}</h3>
<div class="row">
<div class="col-6 form-group">
<label for="minNumberWords">{{'minimumNumberOfWords' | i18n}}</label>
<input id="minNumberWords" class="form-control" type="number" name="minNumberWords" min="3" max="20"
formControlName="minNumberWords">
</div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="capitalize" name="capitalize"
formControlName="capitalize">
<label class="form-check-label" for="capitalize">{{'capitalize' | i18n}}</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="includeNumber" name="includeNumber"
formControlName="includeNumber">
<label class="form-check-label" for="includeNumber">{{'includeNumber' | i18n}}</label>
</div>
</div>

View File

@ -0,0 +1,48 @@
import { Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PolicyType } from 'jslib-common/enums/policyType';
import { BasePolicy, BasePolicyComponent } from './base-policy.component';
export class PasswordGeneratorPolicy extends BasePolicy {
name = 'passwordGenerator';
description = 'passwordGeneratorPolicyDesc';
type = PolicyType.PasswordGenerator;
component = PasswordGeneratorPolicyComponent;
}
@Component({
selector: 'policy-password-generator',
templateUrl: 'password-generator.component.html',
})
export class PasswordGeneratorPolicyComponent extends BasePolicyComponent {
data = this.fb.group({
defaultType: [null],
minLength: [null],
useUpper: [null],
useLower: [null],
useNumbers: [null],
useSpecial: [null],
minNumbers: [null],
minSpecial: [null],
minNumberWords: [null],
capitalize: [null],
includeNumber: [null],
});
defaultTypes: { name: string; value: string; }[];
constructor(private fb: FormBuilder, i18nService: I18nService) {
super();
this.defaultTypes = [
{ name: i18nService.t('userPreference'), value: null },
{ name: i18nService.t('password'), value: 'password' },
{ name: i18nService.t('passphrase'), value: 'passphrase' },
];
}
}

View File

@ -0,0 +1,10 @@
<app-callout type="warning">
{{'personalOwnershipExemption' | i18n}}
</app-callout>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="enabled" [formControl]="enabled" name="Enabled">
<label class="form-check-label" for="enabled">{{'personalOwnershipCheckboxDesc' | i18n}}</label>
</div>
</div>

View File

@ -0,0 +1,19 @@
import { Component } from '@angular/core';
import { PolicyType } from 'jslib-common/enums/policyType';
import { BasePolicy, BasePolicyComponent } from './base-policy.component';
export class PersonalOwnershipPolicy extends BasePolicy {
name = 'personalOwnership';
description = 'personalOwnershipPolicyDesc';
type = PolicyType.PersonalOwnership;
component = PersonalOwnershipPolicyComponent;
}
@Component({
selector: 'policy-personal-ownership',
templateUrl: 'personal-ownership.component.html',
})
export class PersonalOwnershipPolicyComponent extends BasePolicyComponent {
}

View File

@ -0,0 +1,13 @@
<app-callout type="tip" title="{{'prerequisite' | i18n}}">
{{'requireSsoPolicyReq' | i18n}}
</app-callout>
<app-callout type="warning">
{{'requireSsoExemption' | i18n}}
</app-callout>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="enabled" [formControl]="enabled" name="Enabled">
<label class="form-check-label" for="enabled">{{'enabled' | i18n}}</label>
</div>
</div>

View File

@ -0,0 +1,40 @@
import { Component } from '@angular/core';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PolicyType } from 'jslib-common/enums/policyType';
import { Organization } from 'jslib-common/models/domain/organization';
import { PolicyRequest } from 'jslib-common/models/request/policyRequest';
import { BasePolicy, BasePolicyComponent } from './base-policy.component';
export class RequireSsoPolicy extends BasePolicy {
name = 'requireSso';
description = 'requireSsoPolicyDesc';
type = PolicyType.RequireSso;
component = RequireSsoPolicyComponent;
display(organization: Organization) {
return organization.useSso;
}
}
@Component({
selector: 'policy-require-sso',
templateUrl: 'require-sso.component.html',
})
export class RequireSsoPolicyComponent extends BasePolicyComponent {
constructor(private i18nService: I18nService) {
super();
}
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> {
const singleOrgEnabled = policiesEnabledMap.get(PolicyType.SingleOrg) ?? false;
if (this.enabled.value && singleOrgEnabled) {
throw new Error(this.i18nService.t('requireSsoPolicyReqError'));
}
return super.buildRequest(policiesEnabledMap);
}
}

View File

@ -0,0 +1,25 @@
<app-callout type="warning">
{{'resetPasswordPolicyWarning' | i18n}}
</app-callout>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="enabled" [formControl]="enabled" name="Enabled">
<label class="form-check-label" for="enabled">{{'enabled' | i18n}}</label>
</div>
</div>
<div [formGroup]="data">
<h3 class="mt-4">{{'resetPasswordPolicyAutoEnroll' | i18n}}</h3>
<p>{{'resetPasswordPolicyAutoEnrollDescription' | i18n}}</p>
<app-callout type="warning">
{{'resetPasswordPolicyAutoEnrollWarning' | i18n}}
</app-callout>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="autoEnrollEnabled" name="AutoEnrollEnabled"
formControlName="autoEnroll">
<label class="form-check-label" for="autoEnrollEnabled">
{{'resetPasswordPolicyAutoEnrollCheckbox' | i18n }}
</label>
</div>
</div>

View File

@ -0,0 +1,36 @@
import { Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { PolicyType } from 'jslib-common/enums/policyType';
import { Organization } from 'jslib-common/models/domain/organization';
import { BasePolicy, BasePolicyComponent } from './base-policy.component';
export class ResetPasswordPolicy extends BasePolicy {
name = 'resetPasswordPolicy';
description = 'resetPasswordPolicyDescription';
type = PolicyType.ResetPassword;
component = ResetPasswordPolicyComponent;
display(organization: Organization) {
return organization.useResetPassword;
}
}
@Component({
selector: 'policy-reset-password',
templateUrl: 'reset-password.component.html',
})
export class ResetPasswordPolicyComponent extends BasePolicyComponent {
data = this.fb.group({
autoEnroll: false,
});
defaultTypes: { name: string; value: string; }[];
constructor(private fb: FormBuilder) {
super();
}
}

View File

@ -0,0 +1,19 @@
<app-callout type="warning">
{{'sendOptionsExemption' | i18n}}
</app-callout>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="enabled" [formControl]="enabled" name="Enabled">
<label class="form-check-label" for="enabled">{{'enabled' | i18n}}</label>
</div>
</div>
<div [formGroup]="data">
<h3 class="mt-4">{{'options' | i18n}}</h3>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="disableHideEmail" name="DisableHideEmail"
formControlName="disableHideEmail">
<label class="form-check-label" for="disableHideEmail">{{'disableHideEmail' | i18n}}</label>
</div>
</div>

View File

@ -0,0 +1,28 @@
import { Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { PolicyType } from 'jslib-common/enums/policyType';
import { BasePolicy, BasePolicyComponent } from './base-policy.component';
export class SendOptionsPolicy extends BasePolicy {
name = 'sendOptions';
description = 'sendOptionsPolicyDesc';
type = PolicyType.SendOptions;
component = SendOptionsPolicyComponent;
}
@Component({
selector: 'policy-send-options',
templateUrl: 'send-options.component.html',
})
export class SendOptionsPolicyComponent extends BasePolicyComponent {
data = this.fb.group({
disableHideEmail: false,
});
constructor(private fb: FormBuilder) {
super();
}
}

View File

@ -0,0 +1,10 @@
<app-callout type="warning">
{{'singleOrgPolicyWarning' | i18n}}
</app-callout>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="enabled" [formControl]="enabled" name="Enabled">
<label class="form-check-label" for="enabled">{{'enabled' | i18n}}</label>
</div>
</div>

View File

@ -0,0 +1,36 @@
import { Component } from '@angular/core';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PolicyType } from 'jslib-common/enums/policyType';
import { PolicyRequest } from 'jslib-common/models/request/policyRequest';
import { BasePolicy, BasePolicyComponent } from './base-policy.component';
export class SingleOrgPolicy extends BasePolicy {
name = 'singleOrg';
description = 'singleOrgDesc';
type = PolicyType.SingleOrg;
component = SingleOrgPolicyComponent;
}
@Component({
selector: 'policy-single-org',
templateUrl: 'single-org.component.html',
})
export class SingleOrgPolicyComponent extends BasePolicyComponent {
constructor(private i18nService: I18nService) {
super();
}
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> {
const requireSsoEnabled = policiesEnabledMap.get(PolicyType.RequireSso) ?? false;
if (!this.enabled.value && requireSsoEnabled) {
throw new Error(this.i18nService.t('disableRequireSsoError'));
}
return super.buildRequest(policiesEnabledMap);
}
}

View File

@ -0,0 +1,10 @@
<app-callout type="warning">
{{'twoStepLoginPolicyWarning' | i18n}}
</app-callout>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="enabled" [formControl]="enabled" name="Enabled">
<label class="form-check-label" for="enabled">{{'enabled' | i18n}}</label>
</div>
</div>

View File

@ -0,0 +1,19 @@
import { Component } from '@angular/core';
import { PolicyType } from 'jslib-common/enums/policyType';
import { BasePolicy, BasePolicyComponent } from './base-policy.component';
export class TwoFactorAuthenticationPolicy extends BasePolicy {
name = 'twoStepLogin';
description = 'twoStepLoginPolicyDesc';
type = PolicyType.TwoFactorAuthentication;
component = TwoFactorAuthenticationPolicyComponent;
}
@Component({
selector: 'policy-two-factor-authentication',
templateUrl: 'two-factor-authentication.component.html',
})
export class TwoFactorAuthenticationPolicyComponent extends BasePolicyComponent {
}

View File

@ -234,6 +234,16 @@ import localeUk from '@angular/common/locales/uk';
import localeZhCn from '@angular/common/locales/zh-Hans'; import localeZhCn from '@angular/common/locales/zh-Hans';
import localeZhTw from '@angular/common/locales/zh-Hant'; import localeZhTw from '@angular/common/locales/zh-Hant';
import { DisableSendPolicyComponent } from './organizations/policies/disable-send.component';
import { MasterPasswordPolicyComponent } from './organizations/policies/master-password.component';
import { PasswordGeneratorPolicyComponent } from './organizations/policies/password-generator.component';
import { PersonalOwnershipPolicyComponent } from './organizations/policies/personal-ownership.component';
import { RequireSsoPolicyComponent } from './organizations/policies/require-sso.component';
import { ResetPasswordPolicyComponent } from './organizations/policies/reset-password.component';
import { SendOptionsPolicyComponent } from './organizations/policies/send-options.component';
import { SingleOrgPolicyComponent } from './organizations/policies/single-org.component';
import { TwoFactorAuthenticationPolicyComponent } from './organizations/policies/two-factor-authentication.component';
registerLocaleData(localeAz, 'az'); registerLocaleData(localeAz, 'az');
registerLocaleData(localeBg, 'bg'); registerLocaleData(localeBg, 'bg');
registerLocaleData(localeCa, 'ca'); registerLocaleData(localeCa, 'ca');
@ -438,6 +448,15 @@ registerLocaleData(localeZhTw, 'zh-TW');
VerifyRecoverDeleteComponent, VerifyRecoverDeleteComponent,
WeakPasswordsReportComponent, WeakPasswordsReportComponent,
ProvidersComponent, ProvidersComponent,
TwoFactorAuthenticationPolicyComponent,
MasterPasswordPolicyComponent,
SingleOrgPolicyComponent,
PasswordGeneratorPolicyComponent,
RequireSsoPolicyComponent,
PersonalOwnershipPolicyComponent,
DisableSendPolicyComponent,
SendOptionsPolicyComponent,
ResetPasswordPolicyComponent,
], ],
exports: [ exports: [
A11yTitleDirective, A11yTitleDirective,

View File

@ -0,0 +1,13 @@
import { BasePolicy } from '../organizations/policies/base-policy.component';
export class PolicyListService {
private policies: BasePolicy[] = [];
addPolicies(policies: BasePolicy[]) {
this.policies.push(...policies);
}
getPolicies(): BasePolicy[] {
return this.policies;
}
}

View File

@ -15,6 +15,7 @@ import { WebPlatformUtilsService } from '../../services/webPlatformUtils.service
import { EventService } from './event.service'; import { EventService } from './event.service';
import { OrganizationGuardService } from './organization-guard.service'; import { OrganizationGuardService } from './organization-guard.service';
import { OrganizationTypeGuardService } from './organization-type-guard.service'; import { OrganizationTypeGuardService } from './organization-type-guard.service';
import { PolicyListService } from './policy-list.service';
import { RouterService } from './router.service'; import { RouterService } from './router.service';
import { AuthGuardService } from 'jslib-angular/services/auth-guard.service'; import { AuthGuardService } from 'jslib-angular/services/auth-guard.service';
@ -191,6 +192,7 @@ export function initFactory(): Function {
RouterService, RouterService,
EventService, EventService,
LockGuardService, LockGuardService,
PolicyListService,
{ provide: AuditServiceAbstraction, useValue: auditService }, { provide: AuditServiceAbstraction, useValue: auditService },
{ provide: AuthServiceAbstraction, useValue: authService }, { provide: AuthServiceAbstraction, useValue: authService },
{ provide: CipherServiceAbstraction, useValue: cipherService }, { provide: CipherServiceAbstraction, useValue: cipherService },

View File

@ -3369,9 +3369,6 @@
"linkSso": { "linkSso": {
"message": "Link SSO" "message": "Link SSO"
}, },
"webPoliciesDeprecationWarning": {
"message": "Policy configuration has been moved, and this page will soon be deprecated. Please click below to use the Business Portal policies page instead."
},
"singleOrg": { "singleOrg": {
"message": "Single Organization" "message": "Single Organization"
}, },