Skip to content

Commit

Permalink
Password policy followup (#9682)
Browse files Browse the repository at this point in the history
* Add unit tests

* add more tests

* change capability prop names

* Add changelog item
  • Loading branch information
Jan authored Sep 11, 2023
1 parent 406390e commit e3edf1a
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Enhancement: Add password policy compatibility

We consume password policy rules from the server and test public link passwords against those.
Additionally we added a show/hide toggle button to password input field

https://github.com/owncloud/web/pull/9682
https://github.com/owncloud/web/pull/9634
https://github.com/owncloud/web/issues/9638
https://github.com/owncloud/web/issues/9657
5 changes: 2 additions & 3 deletions packages/web-client/src/ocs/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@ export interface AppProviderCapability {
export interface PasswordPolicyCapability {
min_characters?: number
max_characters?: number
min_lower_case_characters?: number
min_upper_case_characters?: number
min_lowercase_characters?: number
min_uppercase_characters?: number
min_digits?: number
min_special_characters?: number
special_characters?: string
}

export interface Capabilities {
Expand Down
21 changes: 14 additions & 7 deletions packages/web-pkg/src/services/passwordPolicy/passwordPolicy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,28 @@ export class PasswordPolicyService {
}
const rules = {} as any

// add default rule
if (
Object.keys(this.capability).length === 0 ||
(Object.keys(this.capability).length === 1 &&
Object.keys(this.capability)[0] === 'max_characters')
) {
rules.mustNotBeEmpty = {}
}

if (this.capability.min_characters) {
rules.atLeastCharacters = { minLength: this.capability.min_characters }
} else {
rules.mustNotBeEmpty = {}
}

if (this.capability.min_upper_case_characters) {
if (this.capability.min_uppercase_characters) {
rules.atLeastUppercaseCharacters = {
minLength: this.capability.min_upper_case_characters
minLength: this.capability.min_uppercase_characters
}
}

if (this.capability.min_lower_case_characters) {
if (this.capability.min_lowercase_characters) {
rules.atLeastLowercaseCharacters = {
minLength: this.capability.min_lower_case_characters
minLength: this.capability.min_lowercase_characters
}
}

Expand All @@ -61,7 +68,7 @@ export class PasswordPolicyService {
if (this.capability.min_special_characters) {
rules.mustContain = {
minLength: this.capability.min_special_characters,
characters: this.capability.special_characters
characters: ' "!#\\$%&\'()*+,-./:;<=>?@[\\]^_`{|}~"'
}
}

Expand Down
127 changes: 127 additions & 0 deletions packages/web-pkg/tests/unit/services/passwordPolicy.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { PasswordPolicyService } from '../../../src/services'
import { createStore, defaultStoreMockOptions } from 'web-test-helpers'
import { Language } from 'vue3-gettext'
import { PasswordPolicyCapability } from 'web-client/src/ocs/capabilities'

describe('PasswordPolicyService', () => {
describe('policy', () => {
describe('contains the rules according to the capability', () => {
it.each([
[{} as PasswordPolicyCapability, ['mustNotBeEmpty']],
[{ min_characters: 2 } as PasswordPolicyCapability, ['atLeastCharacters']],
[
{ min_lowercase_characters: 2 } as PasswordPolicyCapability,
['atLeastLowercaseCharacters']
],
[
{ min_uppercase_characters: 2 } as PasswordPolicyCapability,
['atLeastUppercaseCharacters']
],
[{ min_digits: 2 } as PasswordPolicyCapability, ['atLeastDigits']],
[{ min_digits: 2 } as PasswordPolicyCapability, ['atLeastDigits']],
[{ min_special_characters: 2 } as PasswordPolicyCapability, ['mustContain']],
[
{ max_characters: 72 } as PasswordPolicyCapability,
['mustNotBeEmpty', 'atMostCharacters']
],
[
{
min_characters: 2,
min_lowercase_characters: 2,
min_uppercase_characters: 2,
min_digits: 2,
min_special_characters: 2,
max_characters: 72
} as PasswordPolicyCapability,
[
'atLeastCharacters',
'atLeastUppercaseCharacters',
'atLeastLowercaseCharacters',
'atLeastDigits',
'mustContain',
'atMostCharacters'
]
]
])('capability "%s"', (capability: PasswordPolicyCapability, expected: Array<string>) => {
const { passwordPolicyService } = getWrapper(capability)
expect(Object.keys((passwordPolicyService.getPolicy() as any).rules)).toEqual(expected)
})
})
describe('method "check"', () => {
describe('test the password correctly against te defined rules', () => {
it.each([
[{} as PasswordPolicyCapability, ['', 'o'], [false, true]],
[
{ min_characters: 2 } as PasswordPolicyCapability,
['', 'o', 'ow', 'ownCloud'],
[false, false, true, true]
],
[
{ min_lowercase_characters: 2 } as PasswordPolicyCapability,
['', 'o', 'oWNCLOUD', 'ownCloud'],
[false, false, false, true]
],
[
{ min_uppercase_characters: 2 } as PasswordPolicyCapability,
['', 'o', 'ownCloud', 'ownCLoud'],
[false, false, false, true]
],
[
{ min_digits: 2 } as PasswordPolicyCapability,
['', '1', 'ownCloud1', 'ownCloud12'],
[false, false, false, true]
],
[
{ min_special_characters: 2 } as PasswordPolicyCapability,
['', '!', 'ownCloud!', 'ownCloud!#'],
[false, false, false, true]
],
[
{ max_characters: 2 } as PasswordPolicyCapability,
['ownCloud', 'ownC', 'ow', 'o'],
[false, false, true, true]
],
[
{
min_characters: 8,
min_lowercase_characters: 2,
min_uppercase_characters: 2,
min_digits: 2,
min_special_characters: 2,
max_characters: 72
} as PasswordPolicyCapability,
['öwnCloud', 'öwnCloudää', 'öwnCloudää12', 'öwnCloudäÄ12#!'],
[false, false, false, true]
]
])(
'capability "%s, passwords "%s"',
(
capability: PasswordPolicyCapability,
passwords: Array<string>,
expected: Array<boolean>
) => {
const { passwordPolicyService } = getWrapper(capability)
const policy = passwordPolicyService.getPolicy()
for (let i = 0; i < passwords.length; i++) {
expect((policy as any).check(passwords[i])).toEqual(expected[i])
}
}
)
})
})
})
})

const getWrapper = (capability: PasswordPolicyCapability) => {
const storeOptions = defaultStoreMockOptions
storeOptions.getters.capabilities.mockReturnValue({
password_policy: capability
})
const store = createStore(storeOptions)
return {
passwordPolicyService: new PasswordPolicyService({
store,
language: { current: 'en' } as Language
})
}
}

0 comments on commit e3edf1a

Please sign in to comment.