1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-12-26 17:08:33 +01:00

Enforce Password Generator Policy (#75)

* Enforce Password Generator Policy

* Move policy enforcement to service layer

* Fixed typo (vscode didn't warn..) and adjust import spacing

* Made requested changes
This commit is contained in:
Vincent Salucci 2020-02-26 16:38:11 -06:00 committed by GitHub
parent 29635bf9da
commit 862057dca6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 132 additions and 5 deletions

View File

@ -1,9 +1,11 @@
import { GeneratedPasswordHistory } from '../models/domain/generatedPasswordHistory';
import { PasswordGeneratorPolicyOptions } from '../models/domain/passwordGeneratorPolicyOptions';
export abstract class PasswordGenerationService {
generatePassword: (options: any) => Promise<string>;
generatePassphrase: (options: any) => Promise<string>;
getOptions: () => any;
getOptions: () => Promise<[any, PasswordGeneratorPolicyOptions]>;
getPasswordGeneratorPolicyOptions: () => Promise<PasswordGeneratorPolicyOptions>;
saveOptions: (options: any) => Promise<any>;
getHistory: () => Promise<GeneratedPasswordHistory[]>;
addHistory: (password: string) => Promise<any>;

View File

@ -9,6 +9,8 @@ import { I18nService } from '../../abstractions/i18n.service';
import { PasswordGenerationService } from '../../abstractions/passwordGeneration.service';
import { PlatformUtilsService } from '../../abstractions/platformUtils.service';
import { PasswordGeneratorPolicyOptions } from '../../models/domain/passwordGeneratorPolicyOptions';
export class PasswordGeneratorComponent implements OnInit {
@Input() showSelect: boolean = false;
@Output() onSelected = new EventEmitter<string>();
@ -17,13 +19,16 @@ export class PasswordGeneratorComponent implements OnInit {
password: string = '-';
showOptions = false;
avoidAmbiguous = false;
enforcedPolicyOptions: PasswordGeneratorPolicyOptions;
constructor(protected passwordGenerationService: PasswordGenerationService,
protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService,
private win: Window) { }
async ngOnInit() {
this.options = await this.passwordGenerationService.getOptions();
const optionsResponse = await this.passwordGenerationService.getOptions();
this.options = optionsResponse[0];
this.enforcedPolicyOptions = optionsResponse[1];
this.avoidAmbiguous = !this.options.ambiguous;
this.options.type = this.options.type === 'passphrase' ? 'passphrase' : 'password';
this.password = await this.passwordGenerationService.generatePassword(this.options);
@ -95,6 +100,10 @@ export class PasswordGeneratorComponent implements OnInit {
this.options.length = 128;
}
if (this.options.length < this.enforcedPolicyOptions.minLength) {
this.options.length = this.enforcedPolicyOptions.minLength;
}
if (!this.options.minNumber) {
this.options.minNumber = 0;
} else if (this.options.minNumber > this.options.length) {
@ -103,6 +112,10 @@ export class PasswordGeneratorComponent implements OnInit {
this.options.minNumber = 9;
}
if (this.options.minNumber < this.enforcedPolicyOptions.numberCount) {
this.options.minNumber = this.enforcedPolicyOptions.numberCount;
}
if (!this.options.minSpecial) {
this.options.minSpecial = 0;
} else if (this.options.minSpecial > this.options.length) {
@ -111,6 +124,10 @@ export class PasswordGeneratorComponent implements OnInit {
this.options.minSpecial = 9;
}
if (this.options.minSpecial < this.enforcedPolicyOptions.specialCount) {
this.options.minSpecial = this.enforcedPolicyOptions.specialCount;
}
if (this.options.minSpecial + this.options.minNumber > this.options.length) {
this.options.minSpecial = this.options.length - this.options.minNumber;
}

View File

@ -0,0 +1,11 @@
import Domain from './domainBase';
export class PasswordGeneratorPolicyOptions extends Domain {
minLength: number = 0;
useUppercase: boolean = false;
useLowercase: boolean = false;
useNumbers: boolean = false;
numberCount: number = 0;
useSpecial: boolean = false;
specialCount: number = 0;
}

View File

@ -2,15 +2,20 @@ import * as zxcvbn from 'zxcvbn';
import { CipherString } from '../models/domain/cipherString';
import { GeneratedPasswordHistory } from '../models/domain/generatedPasswordHistory';
import { PasswordGeneratorPolicyOptions } from '../models/domain/passwordGeneratorPolicyOptions';
import { Policy } from '../models/domain/policy';
import { CryptoService } from '../abstractions/crypto.service';
import {
PasswordGenerationService as PasswordGenerationServiceAbstraction,
} from '../abstractions/passwordGeneration.service';
import { PolicyService } from '../abstractions/policy.service';
import { StorageService } from '../abstractions/storage.service';
import { EEFLongWordList } from '../misc/wordlist';
import { PolicyType } from '../enums/policyType';
const DefaultOptions = {
length: 14,
ambiguous: false,
@ -40,7 +45,8 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
private optionsCache: any;
private history: GeneratedPasswordHistory[];
constructor(private cryptoService: CryptoService, private storageService: StorageService) { }
constructor(private cryptoService: CryptoService, private storageService: StorageService,
private policyService: PolicyService) { }
async generatePassword(options: any): Promise<string> {
// overload defaults with given options
@ -207,7 +213,7 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
return wordList.join(o.wordSeparator);
}
async getOptions() {
async getOptions(): Promise<[any, PasswordGeneratorPolicyOptions]> {
if (this.optionsCache == null) {
const options = await this.storageService.get(Keys.options);
if (options == null) {
@ -217,7 +223,98 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
}
}
return this.optionsCache;
let enforcedPolicyOptions = await this.getPasswordGeneratorPolicyOptions();
if (enforcedPolicyOptions != null) {
if (this.optionsCache.length < enforcedPolicyOptions.minLength) {
this.optionsCache.length = enforcedPolicyOptions.minLength;
}
if (enforcedPolicyOptions.useUppercase) {
this.optionsCache.uppercase = true;
}
if (enforcedPolicyOptions.useLowercase) {
this.optionsCache.lowercase = true;
}
if (enforcedPolicyOptions.useNumbers) {
this.optionsCache.number = true;
}
if (this.optionsCache.minNumber < enforcedPolicyOptions.numberCount) {
this.optionsCache.minNumber = enforcedPolicyOptions.numberCount;
}
if (enforcedPolicyOptions.useSpecial) {
this.optionsCache.special = true;
}
if (this.optionsCache.minSpecial < enforcedPolicyOptions.specialCount) {
this.optionsCache.minSpecial = enforcedPolicyOptions.specialCount;
}
// Must normalize these fields because the receiving call expects all options to pass the current rules
if (this.optionsCache.minSpecial + this.optionsCache.minNumber > this.optionsCache.length) {
this.optionsCache.minSpecial = this.optionsCache.length - this.optionsCache.minNumber;
}
} else { // UI layer expects an instantiated object to prevent more explicit null checks
enforcedPolicyOptions = new PasswordGeneratorPolicyOptions();
}
return [this.optionsCache, enforcedPolicyOptions];
}
async getPasswordGeneratorPolicyOptions(): Promise<PasswordGeneratorPolicyOptions> {
const policies: Policy[] = await this.policyService.getAll(PolicyType.PasswordGenerator);
let enforcedOptions: PasswordGeneratorPolicyOptions = null;
if (policies == null || policies.length === 0) {
return enforcedOptions;
}
policies.forEach((currentPolicy) => {
if (!currentPolicy.enabled || currentPolicy.data == null) {
return;
}
if (enforcedOptions == null) {
enforcedOptions = new PasswordGeneratorPolicyOptions();
}
if (currentPolicy.data.minLength != null
&& currentPolicy.data.minLength > enforcedOptions.minLength) {
enforcedOptions.minLength = currentPolicy.data.minLength;
}
if (currentPolicy.data.useUpper) {
enforcedOptions.useUppercase = true;
}
if (currentPolicy.data.useLower) {
enforcedOptions.useLowercase = true;
}
if (currentPolicy.data.useNumbers) {
enforcedOptions.useNumbers = true;
}
if (currentPolicy.data.minNumbers != null
&& currentPolicy.data.minNumbers > enforcedOptions.numberCount) {
enforcedOptions.numberCount = currentPolicy.data.minNumbers;
}
if (currentPolicy.data.useSpecial) {
enforcedOptions.useSpecial = true;
}
if (currentPolicy.data.minSpecial != null
&& currentPolicy.data.minSpecial > enforcedOptions.specialCount) {
enforcedOptions.specialCount = currentPolicy.data.minSpecial;
}
});
return enforcedOptions;
}
async saveOptions(options: any) {