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:
parent
29635bf9da
commit
862057dca6
@ -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>;
|
||||
|
@ -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;
|
||||
}
|
||||
|
11
src/models/domain/passwordGeneratorPolicyOptions.ts
Normal file
11
src/models/domain/passwordGeneratorPolicyOptions.ts
Normal 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;
|
||||
}
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user