From ee8ca0beed5e04676281e0d1649e10cd3084e74a Mon Sep 17 00:00:00 2001 From: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Date: Tue, 10 Mar 2020 12:50:54 -0500 Subject: [PATCH] Password Generator Passphrase Policy (#85) * Initial commit for passphrase enforcement * Updated type implementation * Updated default type conditional * Added helper method to enforced options object Co-authored-by: Vincent Salucci --- .../password-generator.component.ts | 13 +++---- .../domain/passwordGeneratorPolicyOptions.ts | 18 ++++++++++ src/services/passwordGeneration.service.ts | 36 +++++++++++++++++++ 3 files changed, 58 insertions(+), 9 deletions(-) diff --git a/src/angular/components/password-generator.component.ts b/src/angular/components/password-generator.component.ts index 9b703a31cb..51f70bc8bf 100644 --- a/src/angular/components/password-generator.component.ts +++ b/src/angular/components/password-generator.component.ts @@ -19,7 +19,6 @@ export class PasswordGeneratorComponent implements OnInit { password: string = '-'; showOptions = false; avoidAmbiguous = false; - policyInEffect = false; enforcedPolicyOptions: PasswordGeneratorPolicyOptions; constructor(protected passwordGenerationService: PasswordGenerationService, @@ -30,14 +29,6 @@ export class PasswordGeneratorComponent implements OnInit { const optionsResponse = await this.passwordGenerationService.getOptions(); this.options = optionsResponse[0]; this.enforcedPolicyOptions = optionsResponse[1]; - this.policyInEffect = this.enforcedPolicyOptions != null && ( - this.enforcedPolicyOptions.minLength > 0 || - this.enforcedPolicyOptions.numberCount > 0 || - this.enforcedPolicyOptions.specialCount > 0 || - this.enforcedPolicyOptions.useUppercase || - this.enforcedPolicyOptions.useLowercase || - this.enforcedPolicyOptions.useNumbers || - this.enforcedPolicyOptions.useSpecial); this.avoidAmbiguous = !this.options.ambiguous; this.options.type = this.options.type === 'passphrase' ? 'passphrase' : 'password'; this.password = await this.passwordGenerationService.generatePassword(this.options); @@ -147,6 +138,10 @@ export class PasswordGeneratorComponent implements OnInit { this.options.numWords = 20; } + if (this.options.numWords < this.enforcedPolicyOptions.minNumberWords) { + this.options.numWords = this.enforcedPolicyOptions.minNumberWords; + } + if (this.options.wordSeparator != null && this.options.wordSeparator.length > 1) { this.options.wordSeparator = this.options.wordSeparator[0]; } diff --git a/src/models/domain/passwordGeneratorPolicyOptions.ts b/src/models/domain/passwordGeneratorPolicyOptions.ts index fb68016b95..d84b575a4d 100644 --- a/src/models/domain/passwordGeneratorPolicyOptions.ts +++ b/src/models/domain/passwordGeneratorPolicyOptions.ts @@ -1,6 +1,7 @@ import Domain from './domainBase'; export class PasswordGeneratorPolicyOptions extends Domain { + defaultType: string = ''; minLength: number = 0; useUppercase: boolean = false; useLowercase: boolean = false; @@ -8,4 +9,21 @@ export class PasswordGeneratorPolicyOptions extends Domain { numberCount: number = 0; useSpecial: boolean = false; specialCount: number = 0; + minNumberWords: number = 0; + capitalize: boolean = false; + includeNumber: boolean = false; + + inEffect() { + return this.defaultType !== '' || + this.minLength > 0 || + this.numberCount > 0 || + this.specialCount > 0 || + this.useUppercase || + this.useLowercase || + this.useNumbers || + this.useSpecial || + this.minNumberWords > 0 || + this.capitalize || + this.includeNumber; + } } diff --git a/src/services/passwordGeneration.service.ts b/src/services/passwordGeneration.service.ts index 6293555df4..2af4c40ce5 100644 --- a/src/services/passwordGeneration.service.ts +++ b/src/services/passwordGeneration.service.ts @@ -262,6 +262,24 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr if (options.minSpecial + options.minNumber > options.length) { options.minSpecial = options.length - options.minNumber; } + + if (options.numWords < enforcedPolicyOptions.minNumberWords) { + options.numWords = enforcedPolicyOptions.minNumberWords; + } + + if (enforcedPolicyOptions.capitalize) { + options.capitalize = true; + } + + if (enforcedPolicyOptions.includeNumber) { + options.includeNumber = true; + } + + // Force default type if password/passphrase selected via policy + if (enforcedPolicyOptions.defaultType === 'password' || + enforcedPolicyOptions.defaultType === 'passphrase') { + options.type = enforcedPolicyOptions.defaultType; + } } else { // UI layer expects an instantiated object to prevent more explicit null checks enforcedPolicyOptions = new PasswordGeneratorPolicyOptions(); } @@ -285,6 +303,11 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr enforcedOptions = new PasswordGeneratorPolicyOptions(); } + // Password wins in multi-org collisions + if (currentPolicy.data.defaultType != null && enforcedOptions.defaultType !== 'password') { + enforcedOptions.defaultType = currentPolicy.data.defaultType; + } + if (currentPolicy.data.minLength != null && currentPolicy.data.minLength > enforcedOptions.minLength) { enforcedOptions.minLength = currentPolicy.data.minLength; @@ -315,6 +338,19 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr && currentPolicy.data.minSpecial > enforcedOptions.specialCount) { enforcedOptions.specialCount = currentPolicy.data.minSpecial; } + + if (currentPolicy.data.minNumberWords != null + && currentPolicy.data.minNumberWords > enforcedOptions.minNumberWords) { + enforcedOptions.minNumberWords = currentPolicy.data.minNumberWords; + } + + if (currentPolicy.data.capitalize) { + enforcedOptions.capitalize = true; + } + + if (currentPolicy.data.includeNumber) { + enforcedOptions.includeNumber = true; + } }); return enforcedOptions;