diff --git a/src/Definition/PasswordDefinition.php b/src/Definition/PasswordDefinition.php index 9b32795..e4e1e6b 100644 --- a/src/Definition/PasswordDefinition.php +++ b/src/Definition/PasswordDefinition.php @@ -55,49 +55,62 @@ public function getRule($rule): string|bool|int return $this->rules[$rule]; } - public function matchPassword(string $password): bool + const SUCCESS = 0; + const FAIL_MINIMUM_CHARS = 1; + const FAIL_UPPERCASE = 2; + const FAIL_LOWERCASE = 4; + const FAIL_SYMBOLS = 8; + const FAIL_NUMBERS = 16; + const FAIL_WHITESPACE = 32; + const FAIL_SEQUENTIAL = 64; + const FAIL_REPEATED = 128; + + public function matchPassword(string $password): int { + $result = 0; + // match password against the rules if (strlen($password) < $this->rules[self::MINIMUM_CHARS]) { - return false; + $result |= PasswordDefinition::FAIL_MINIMUM_CHARS; } if ($this->rules[self::REQUIRE_UPPERCASE] > 0) { if (preg_match_all('/[A-Z]/', $password, $matches) < $this->rules[self::REQUIRE_UPPERCASE]) { - return false; + $result |= PasswordDefinition::FAIL_UPPERCASE; } } if ($this->rules[self::REQUIRE_LOWERCASE] > 0) { if (preg_match_all('/[a-z]/', $password, $matches) < $this->rules[self::REQUIRE_LOWERCASE]) { - return false; + $result |= PasswordDefinition::FAIL_LOWERCASE; } } if ($this->rules[self::REQUIRE_SYMBOLS] > 0) { if (preg_match_all('/[!@#$%^&*()\-_=+{};:,<.>]/', $password, $matches) < $this->rules[self::REQUIRE_SYMBOLS]) { - return false; + $result |= PasswordDefinition::FAIL_SYMBOLS; } } if ($this->rules[self::REQUIRE_NUMBERS] > 0) { if (preg_match_all('/[0-9]/', $password, $matches) < $this->rules[self::REQUIRE_NUMBERS]) { - return false; + $result |= PasswordDefinition::FAIL_NUMBERS; } } if ($this->rules[self::ALLOW_WHITESPACE] == 0) { if (preg_match_all('/\s/', $password, $matches) > 0) { - return false; + $result |= PasswordDefinition::FAIL_WHITESPACE; } } if ($this->rules[self::ALLOW_SEQUENTIAL] == 0) { if (preg_match_all('/([aA][bB][cC]|[bB][cC][dD]|[cC][dD][eE]|[dD][eE][fF]|[eE][fF][gG]|[fF][gG][hH]|[gG][hH][iI]|[hH][iI][jJ]|[iI][jJ][kK]|[jJ][kK][lL]|[kK][lL][mM]|[lL][mM][nN]|[mM][nN][oO]|[nN][oO][pP]|[oO][pP][qQ]|[pP][qQ][rR]|[qQ][rR][sS]|[rR][sS][tT]|[sS][tT][uU]|[tT][uU][vV]|[uU][vV][wW]|[vV][wW][xX]|[wW][xX][yY]|[xX][yY][zZ]|012|123|234|345|456|567|678|789|890|987|876|765|654|543|432|321)/', $password, $matches) > 0) { - return false; + $result |= PasswordDefinition::FAIL_SEQUENTIAL; } } if ($this->rules[self::ALLOW_REPEATED] == 0) { if (preg_match_all('/(..?)\1{2,}/', $password, $matches) > 0) { - return false; + $result |= PasswordDefinition::FAIL_REPEATED; } } - return true; + + return $result; } diff --git a/src/Model/UserModel.php b/src/Model/UserModel.php index 4666681..3dd4ee3 100644 --- a/src/Model/UserModel.php +++ b/src/Model/UserModel.php @@ -117,9 +117,13 @@ public function getPassword(): ?string public function setPassword(?string $password): void { // Password len equals to 40 means that the password is already encrypted with sha1 - if (!empty($password) && strlen($password) != 40 && !empty($this->passwordDefinition) && !$this->passwordDefinition->matchPassword($password)) { - throw new InvalidArgumentException("Password does not match the password definition"); + if (!empty($this->passwordDefinition) && !empty($password) && strlen($password) != 40) { + $match = $this->passwordDefinition->matchPassword($password); + if ($match != PasswordDefinition::SUCCESS) { + throw new InvalidArgumentException("Password does not match the password definition [{$match}]"); + } } + $this->password = $password; } diff --git a/src/UsersDBDataset.php b/src/UsersDBDataset.php index fb47d62..8c29126 100644 --- a/src/UsersDBDataset.php +++ b/src/UsersDBDataset.php @@ -411,10 +411,11 @@ public function getProperty(string|HexUuidLiteral|int $userId, string $propertyN * Return all property's fields from this user * * @param UserModel $userRow + * @throws RepositoryReadOnlyException */ protected function setPropertiesInUser(UserModel $userRow): void { - $value = $this->propertiesRepository->getMapper()->getFieldMap(UserDefinition::FIELD_USERID)->getUpdateFunctionValue($userRow->getUserid(), $userRow); + $value = $this->propertiesRepository->getMapper()->getFieldMap(UserDefinition::FIELD_USERID)->getUpdateFunctionValue($userRow->getUserid(), $userRow, $this->propertiesRepository->getDbDriverWrite()->getDbHelper()); $query = Query::getInstance() ->table($this->getUserPropertiesDefinition()->table()) ->where("{$this->getUserPropertiesDefinition()->getUserid()} = :id", ['id' => $value]); diff --git a/tests/PasswordDefinitionTest.php b/tests/PasswordDefinitionTest.php index c12ef3b..3454239 100644 --- a/tests/PasswordDefinitionTest.php +++ b/tests/PasswordDefinitionTest.php @@ -64,8 +64,8 @@ public function testMatchPasswordMinimumChars() PasswordDefinition::ALLOW_SEQUENTIAL => 1, // Allow sequential characters PasswordDefinition::ALLOW_REPEATED => 1 // Allow repeated characters ]); - $this->assertFalse($passwordDefinition->matchPassword('1234567')); - $this->assertTrue($passwordDefinition->matchPassword('12345678')); + $this->assertEquals(PasswordDefinition::FAIL_MINIMUM_CHARS, $passwordDefinition->matchPassword('1234567')); + $this->assertEquals(PasswordDefinition::SUCCESS, $passwordDefinition->matchPassword('12345678')); } public function testMatchPasswordUppercase() @@ -80,9 +80,9 @@ public function testMatchPasswordUppercase() PasswordDefinition::ALLOW_SEQUENTIAL => 1, // Allow sequential characters PasswordDefinition::ALLOW_REPEATED => 1 // Allow repeated characters ]); - $this->assertFalse($passwordDefinition->matchPassword('12345678')); - $this->assertFalse($passwordDefinition->matchPassword('12345678A')); - $this->assertTrue($passwordDefinition->matchPassword('1234567BA')); + $this->assertEquals(PasswordDefinition::FAIL_UPPERCASE, $passwordDefinition->matchPassword('12345678')); + $this->assertEquals(PasswordDefinition::FAIL_UPPERCASE, $passwordDefinition->matchPassword('12345678A')); + $this->assertEquals(PasswordDefinition::SUCCESS, $passwordDefinition->matchPassword('1234567BA')); } public function testMatchPasswordLowercase() @@ -97,9 +97,9 @@ public function testMatchPasswordLowercase() PasswordDefinition::ALLOW_SEQUENTIAL => 1, // Allow sequential characters PasswordDefinition::ALLOW_REPEATED => 1 // Allow repeated characters ]); - $this->assertFalse($passwordDefinition->matchPassword('12345678')); - $this->assertFalse($passwordDefinition->matchPassword('12345678a')); - $this->assertTrue($passwordDefinition->matchPassword('1234567ba')); + $this->assertEquals(PasswordDefinition::FAIL_LOWERCASE, $passwordDefinition->matchPassword('12345678')); + $this->assertEquals(PasswordDefinition::FAIL_LOWERCASE, $passwordDefinition->matchPassword('12345678a')); + $this->assertEquals(PasswordDefinition::SUCCESS, $passwordDefinition->matchPassword('1234567ba')); } public function testMatchPasswordSymbols() @@ -114,9 +114,9 @@ public function testMatchPasswordSymbols() PasswordDefinition::ALLOW_SEQUENTIAL => 1, // Allow sequential characters PasswordDefinition::ALLOW_REPEATED => 1 // Allow repeated characters ]); - $this->assertFalse($passwordDefinition->matchPassword('12345678')); - $this->assertFalse($passwordDefinition->matchPassword('12345678!')); - $this->assertTrue($passwordDefinition->matchPassword('1234567!!')); + $this->assertEquals(PasswordDefinition::FAIL_SYMBOLS, $passwordDefinition->matchPassword('12345678')); + $this->assertEquals(PasswordDefinition::FAIL_SYMBOLS, $passwordDefinition->matchPassword('12345678!')); + $this->assertEquals(PasswordDefinition::SUCCESS, $passwordDefinition->matchPassword('1234567!!')); } public function testMatchPasswordNumbers() @@ -131,9 +131,9 @@ public function testMatchPasswordNumbers() PasswordDefinition::ALLOW_SEQUENTIAL => 1, // Allow sequential characters PasswordDefinition::ALLOW_REPEATED => 1 // Allow repeated characters ]); - $this->assertFalse($passwordDefinition->matchPassword('abcdefgh')); - $this->assertFalse($passwordDefinition->matchPassword('abcdefg1')); - $this->assertTrue($passwordDefinition->matchPassword('abcdef11')); + $this->assertEquals(PasswordDefinition::FAIL_NUMBERS, $passwordDefinition->matchPassword('abcdefgh')); + $this->assertEquals(PasswordDefinition::FAIL_NUMBERS, $passwordDefinition->matchPassword('abcdefg1')); + $this->assertEquals(PasswordDefinition::SUCCESS, $passwordDefinition->matchPassword('abcdef11')); } public function testMatchPasswordWhitespace() @@ -148,7 +148,7 @@ public function testMatchPasswordWhitespace() PasswordDefinition::ALLOW_SEQUENTIAL => 1, // Allow sequential characters PasswordDefinition::ALLOW_REPEATED => 1 // Allow repeated characters ]); - $this->assertFalse($passwordDefinition->matchPassword('1234 678')); + $this->assertEquals(PasswordDefinition::FAIL_WHITESPACE, $passwordDefinition->matchPassword('1234 678')); } public function testMatchPasswordSequential() @@ -163,11 +163,11 @@ public function testMatchPasswordSequential() PasswordDefinition::ALLOW_SEQUENTIAL => 0, // Allow sequential characters PasswordDefinition::ALLOW_REPEATED => 1 // Allow repeated characters ]); - $this->assertFalse($passwordDefinition->matchPassword('123asdkls')); // 123 is sequential - $this->assertFalse($passwordDefinition->matchPassword('sds456sks')); // 456 is sequential - $this->assertFalse($passwordDefinition->matchPassword('aju654sks')); // 654 is sequential - $this->assertFalse($passwordDefinition->matchPassword('791fghkalal')); // fgh is sequential - $this->assertTrue($passwordDefinition->matchPassword('diykdsn132')); + $this->assertEquals(PasswordDefinition::FAIL_SEQUENTIAL, $passwordDefinition->matchPassword('123asdkls')); // 123 is sequential + $this->assertEquals(PasswordDefinition::FAIL_SEQUENTIAL, $passwordDefinition->matchPassword('sds456sks')); // 456 is sequential + $this->assertEquals(PasswordDefinition::FAIL_SEQUENTIAL, $passwordDefinition->matchPassword('aju654sks')); // 654 is sequential + $this->assertEquals(PasswordDefinition::FAIL_SEQUENTIAL, $passwordDefinition->matchPassword('791fghkalal')); // fgh is sequential + $this->assertEquals(PasswordDefinition::SUCCESS, $passwordDefinition->matchPassword('diykdsn132')); } public function testMatchCharsRepeated() @@ -183,9 +183,9 @@ public function testMatchCharsRepeated() PasswordDefinition::ALLOW_REPEATED => 0 // Allow repeated characters ]); - $this->assertFalse($passwordDefinition->matchPassword('hay111oihsc')); // 111 is repeated - $this->assertFalse($passwordDefinition->matchPassword('haycccoihsc')); // ccc is repeated - $this->assertFalse($passwordDefinition->matchPassword('oilalalapo')); // lalala is repeated - $this->assertTrue($passwordDefinition->matchPassword('hay1d11oihsc')); + $this->assertEquals(PasswordDefinition::FAIL_REPEATED, $passwordDefinition->matchPassword('hay111oihsc')); // 111 is repeated + $this->assertEquals(PasswordDefinition::FAIL_REPEATED, $passwordDefinition->matchPassword('haycccoihsc')); // ccc is repeated + $this->assertEquals(PasswordDefinition::FAIL_REPEATED, $passwordDefinition->matchPassword('oilalalapo')); // lalala is repeated + $this->assertEquals(PasswordDefinition::SUCCESS, $passwordDefinition->matchPassword('hay1d11oihsc')); } } \ No newline at end of file