Vault Timeout Policy (#1171)

This commit is contained in:
Oscar Hinton 2021-09-10 15:27:00 +02:00 committed by GitHub
parent 17166dad4d
commit a1c1fea976
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 248 additions and 29 deletions

View File

@ -0,0 +1,20 @@
import { Component } from '@angular/core';
import { AppComponent as BaseAppComponent } from 'src/app/app.component';
import { MaximumVaultTimeoutPolicy } from './policies/maximum-vault-timeout.component';
@Component({
selector: 'app-root',
templateUrl: '../../../src/app/app.component.html',
})
export class AppComponent extends BaseAppComponent {
ngOnInit() {
super.ngOnInit();
this.policyListService.addPolicies([
new MaximumVaultTimeoutPolicy(),
]);
}
}

View File

@ -3,27 +3,36 @@ import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterModule } from '@angular/router';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { MaximumVaultTimeoutPolicyComponent } from './policies/maximum-vault-timeout.component';
import { AppComponent } from 'src/app/app.component';
import { OssRoutingModule } from 'src/app/oss-routing.module';
import { OssModule } from 'src/app/oss.module';
import { ServicesModule } from 'src/app/services/services.module';
@NgModule({
imports: [
OssModule,
BrowserAnimationsModule,
FormsModule,
ReactiveFormsModule,
ServicesModule,
ToasterModule.forRoot(),
InfiniteScrollModule,
DragDropModule,
AppRoutingModule,
OssRoutingModule,
RouterModule,
],
declarations: [
AppComponent,
MaximumVaultTimeoutPolicyComponent,
],
bootstrap: [AppComponent],
})

View File

@ -0,0 +1,27 @@
<app-callout type="tip" title="{{'prerequisite' | i18n}}">
{{'requireSsoPolicyReq' | 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">
<div class="form-group">
<label for="hours">{{'maximumVaultTimeoutLabel' | i18n}}</label>
<div class="row">
<div class="col-6">
<input id="hours" class="form-control" type="number" min="0" name="hours" formControlName="hours">
<small>{{'hours' | i18n }}</small>
</div>
<div class="col-6">
<input id="minutes" class="form-control" type="number" min="0" max="59" name="minutes"
formControlName="minutes">
<small>{{'minutes' | i18n }}</small>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,65 @@
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 { PolicyRequest } from 'jslib-common/models/request/policyRequest';
import { BasePolicy, BasePolicyComponent } from 'src/app/organizations/policies/base-policy.component';
export class MaximumVaultTimeoutPolicy extends BasePolicy {
name = 'maximumVaultTimeout';
description = 'maximumVaultTimeoutDesc';
type = PolicyType.MaximumVaultTimeout;
component = MaximumVaultTimeoutPolicyComponent;
}
@Component({
selector: 'policy-maximum-timeout',
templateUrl: 'maximum-vault-timeout.component.html',
})
export class MaximumVaultTimeoutPolicyComponent extends BasePolicyComponent {
data = this.fb.group({
hours: [null],
minutes: [null],
});
constructor(private fb: FormBuilder, private i18nService: I18nService) {
super();
}
loadData() {
const minutes = this.policyResponse.data?.minutes;
if (minutes == null) {
return;
}
this.data.patchValue({
hours: Math.floor(minutes / 60),
minutes: minutes % 60,
});
}
buildRequestData() {
if (this.data.value.hours == null && this.data.value.minutes == null) {
return null;
}
return {
minutes: this.data.value.hours * 60 + this.data.value.minutes,
};
}
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);
}
}

2
jslib

@ -1 +1 @@
Subproject commit 5f64d956520612a681611a27c5f4f2e5f27b640e
Subproject commit 32774561f37bdcf9abb80276c5d1958b7ec192de

View File

@ -22,6 +22,9 @@ import { ServicesModule } from './services/services.module';
DragDropModule,
OssRoutingModule,
],
declarations: [
AppComponent,
],
bootstrap: [AppComponent],
})
export class AppModule { }

View File

@ -35,19 +35,28 @@ export abstract class BasePolicyComponent implements OnInit {
ngOnInit(): void {
this.enabled.setValue(this.policyResponse.enabled);
if (this.data != null) {
this.data.patchValue(this.policyResponse.data ?? {});
if (this.policyResponse.data != null) {
this.loadData();
}
}
loadData() {
this.data.patchValue(this.policyResponse.data ?? {});
}
buildRequestData() {
if (this.data != null) {
return this.data.value;
}
return null;
}
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;
}
request.data = this.buildRequestData();
return Promise.resolve(request);
}

View File

@ -11,8 +11,6 @@ import { RouterModule } from '@angular/router';
import { ToasterModule } from 'angular2-toaster';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { AppComponent } from './app.component';
import { AvatarComponent } from './components/avatar.component';
import { PasswordRepromptComponent } from './components/password-reprompt.component';
import { PasswordStrengthComponent } from './components/password-strength.component';
@ -144,6 +142,7 @@ import { UpdateKeyComponent } from './settings/update-key.component';
import { UpdateLicenseComponent } from './settings/update-license.component';
import { UserBillingComponent } from './settings/user-billing.component';
import { UserSubscriptionComponent } from './settings/user-subscription.component';
import { VaultTimeoutInputComponent } from './settings/vault-timeout-input.component';
import { VerifyEmailComponent } from './settings/verify-email.component';
import { BreachReportComponent } from './tools/breach-report.component';
@ -307,7 +306,6 @@ registerLocaleData(localeZhTw, 'zh-TW');
AdjustStorageComponent,
ApiActionDirective,
ApiKeyComponent,
AppComponent,
AttachmentsComponent,
AutofocusDirective,
AvatarComponent,
@ -457,6 +455,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
DisableSendPolicyComponent,
SendOptionsPolicyComponent,
ResetPasswordPolicyComponent,
VaultTimeoutInputComponent,
],
exports: [
A11yTitleDirective,

View File

@ -124,7 +124,7 @@ const sendService = new SendService(cryptoService, userService, apiService, file
i18nService, cryptoFunctionService);
const vaultTimeoutService = new VaultTimeoutService(cipherService, folderService, collectionService,
cryptoService, platformUtilsService, storageService, messagingService, searchService, userService, tokenService,
null, async () => messagingService.send('logout', { expired: false }));
policyService, null, async () => messagingService.send('logout', { expired: false }));
const syncService = new SyncService(userService, apiService, settingsService,
folderService, cipherService, cryptoService, collectionService, storageService, messagingService, policyService,
sendService, async (expired: boolean) => messagingService.send('logout', { expired: expired }));

View File

@ -5,13 +5,8 @@
<form (ngSubmit)="submit()" ngNativeValidate>
<div class="row">
<div class="col-6">
<div class="form-group">
<label for="vaultTimeout">{{'vaultTimeout' | i18n}}</label>
<select id="vaultTimeout" name="VaultTimeout" [(ngModel)]="vaultTimeout" class="form-control">
<option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{o.name}}</option>
</select>
<small class="form-text text-muted">{{'vaultTimeoutDesc' | i18n}}</small>
</div>
<app-vault-timeout-input [vaultTimeouts]="vaultTimeouts" [formControl]="vaultTimeout" ngDefaultControl>
</app-vault-timeout-input>
</div>
</div>
<div class="form-group">

View File

@ -2,6 +2,7 @@ import {
Component,
OnInit,
} from '@angular/core';
import { FormControl } from '@angular/forms';
import { ToasterService } from 'angular2-toaster';
@ -21,15 +22,16 @@ import { Utils } from 'jslib-common/misc/utils';
templateUrl: 'options.component.html',
})
export class OptionsComponent implements OnInit {
vaultTimeout: number = null;
vaultTimeoutAction: string = 'lock';
disableIcons: boolean;
enableGravatars: boolean;
enableFullWidth: boolean;
locale: string;
vaultTimeouts: any[];
vaultTimeouts: { name: string; value: number; }[];
localeOptions: any[];
vaultTimeout: FormControl = new FormControl(null);
private startingLocale: string;
constructor(private storageService: StorageService, private stateService: StateService,
@ -63,7 +65,7 @@ export class OptionsComponent implements OnInit {
}
async ngOnInit() {
this.vaultTimeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey);
this.vaultTimeout.setValue(await this.vaultTimeoutService.getVaultTimeout());
this.vaultTimeoutAction = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey);
this.disableIcons = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey);
this.enableGravatars = await this.storageService.get<boolean>('enableGravatars');
@ -72,8 +74,12 @@ export class OptionsComponent implements OnInit {
}
async submit() {
await this.vaultTimeoutService.setVaultTimeoutOptions(this.vaultTimeout != null ? this.vaultTimeout : null,
this.vaultTimeoutAction);
if (!this.vaultTimeout.valid) {
this.toasterService.popAsync('error', null, this.i18nService.t('vaultTimeoutToLarge'));
return;
}
await this.vaultTimeoutService.setVaultTimeoutOptions(this.vaultTimeout.value, this.vaultTimeoutAction);
await this.storageService.save(ConstantsService.disableFaviconKey, this.disableIcons);
await this.stateService.save(ConstantsService.disableFaviconKey, this.disableIcons);
await this.storageService.save('enableGravatars', this.enableGravatars);

View File

@ -0,0 +1,28 @@
<app-callout type="info" *ngIf="vaultTimeoutPolicy">
{{'vaultTimeoutPolicyInEffect' | i18n : vaultTimeoutPolicyHours : vaultTimeoutPolicyMinutes}}
</app-callout>
<div [formGroup]="form">
<div class="form-group">
<label for="vaultTimeout">{{'vaultTimeout' | i18n}}</label>
<select id="vaultTimeout" name="VaultTimeout" formControlName="vaultTimeout" class="form-control">
<option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{o.name}}</option>
</select>
<small class="form-text text-muted">{{'vaultTimeoutDesc' | i18n}}</small>
</div>
<div class="form-group" *ngIf="showCustom" formGroupName="custom">
<label for="customVaultTimeout">{{'customVaultTimeout' | i18n}}</label>
<div class="row">
<div class="col-6">
<input id="hours" class="form-control" type="number" min="0" name="hours"
formControlName="hours">
<small>{{'hours' | i18n }}</small>
</div>
<div class="col-6">
<input id="minutes" class="form-control" type="number" min="0" max="59" name="minutes"
formControlName="minutes">
<small>{{'minutes' | i18n }}</small>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,28 @@
import { Component } from '@angular/core';
import {
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
} from '@angular/forms';
import {
VaultTimeoutInputComponent as VaultTimeoutInputComponentBase
} from 'jslib-angular/components/settings/vault-timeout-input.component';
@Component({
selector: 'app-vault-timeout-input',
templateUrl: 'vault-timeout-input.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: VaultTimeoutInputComponent,
},
{
provide: NG_VALIDATORS,
multi: true,
useExisting: VaultTimeoutInputComponent,
},
],
})
export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {
}

View File

@ -4198,5 +4198,39 @@
},
"updateMasterPasswordWarning": {
"message": "Your Master Password was recently changed by an administrator in your organization. In order to access the vault, you must update your Master Password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour."
},
"maximumVaultTimeout": {
"message": "Vault Timeout"
},
"maximumVaultTimeoutDesc": {
"message": "Configure a maximum vault timeout for all users."
},
"maximumVaultTimeoutLabel": {
"message": "Maximum Vault Timeout"
},
"hours": {
"message": "Hours"
},
"minutes": {
"message": "Minutes"
},
"vaultTimeoutPolicyInEffect": {
"message": "Your organization policies are affecting your vault timeout. Maximum allowed Vault Timeout is $HOURS$ hour(s) and $MINUTES$ minute(s)",
"placeholders": {
"hours": {
"content": "$1",
"example": "5"
},
"minutes": {
"content": "$2",
"example": "5"
}
}
},
"customVaultTimeout": {
"message": "Custom Vault Timeout"
},
"vaultTimeoutToLarge": {
"message": "Your vault timeout exceeds the restriction set by your organization."
}
}

View File

@ -82,10 +82,6 @@ export class WebPlatformUtilsService implements PlatformUtilsService {
return Promise.resolve(false);
}
lockTimeout(): number {
return null;
}
launchUri(uri: string, options?: any): void {
const a = document.createElement('a');
a.href = uri;