Skip to content
This repository was archived by the owner on Jan 29, 2020. It is now read-only.

Commit 8a0edbd

Browse files
committed
Merge branch 'feature/47' into develop
Close #47
2 parents 9b3620e + 2268e32 commit 8a0edbd

File tree

4 files changed

+230
-12
lines changed

4 files changed

+230
-12
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ All notable changes to this project will be documented in this file, in reverse
77
### Added
88

99
- [#44](https://github.com/zendframework/zend-authentication/pull/44) adds support for PHP 7.3.
10+
- [#47](https://github.com/zendframework/zend-authentication/pull/47) adds
11+
configuration option to `Zend\Authentication\Validator\Authentication` for
12+
mapping custom authentication result codes to existing and new validation
13+
message types.
1014

1115
### Changed
1216

docs/book/validator.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ The available configuration options include:
1313
- `identity`: the identity or name of the identity field in the provided context.
1414
- `credential`: credential or the name of the credential field in the provided context.
1515
- `service`: an instance of `Zend\Authentication\AuthenticationService`.
16+
- `code_map`: map of `Zend\Authentication\Result` codes to validator message identifiers.
1617

1718
## Usage
1819

@@ -33,3 +34,65 @@ $validator->isValid('myIdentity', [
3334
'myCredentialContext' => 'myCredential',
3435
]);
3536
```
37+
38+
## Validation messages
39+
40+
The authentication validator defines five failure message types; identifiers
41+
for them are available as constants for convenience.
42+
Common authentication failure codes, defined as constants in
43+
`Zend\Authentication\Result`, are mapped to validation messages
44+
using a map in `CODE_MAP` constant. Other authentication codes default to the
45+
`general` message type.
46+
47+
```php
48+
namespace Zend\Authentication\Validator;
49+
50+
use Zend\Authentication\Result;
51+
52+
class Authentication
53+
{
54+
const IDENTITY_NOT_FOUND = 'identityNotFound';
55+
const IDENTITY_AMBIGUOUS = 'identityAmbiguous';
56+
const CREDENTIAL_INVALID = 'credentialInvalid';
57+
const UNCATEGORIZED = 'uncategorized';
58+
const GENERAL = 'general';
59+
60+
const CODE_MAP = [
61+
Result::FAILURE_IDENTITY_NOT_FOUND => self::IDENTITY_NOT_FOUND,
62+
Result::FAILURE_CREDENTIAL_INVALID => self::CREDENTIAL_INVALID,
63+
Result::FAILURE_IDENTITY_AMBIGUOUS => self::IDENTITY_AMBIGUOUS,
64+
Result::FAILURE_UNCATEGORIZED => self::UNCATEGORIZED,
65+
];
66+
}
67+
```
68+
69+
The authentication validator extends `Zend\Validator\AbstractValidator`, providing
70+
a way common for all framework validators to access, change or translate message templates.
71+
More information is available in the
72+
[zend-validator documentation](https://docs.zendframework.com/zend-validator/messages/)
73+
74+
## Configure validation messages for custom authentication result codes
75+
76+
The constructor configuration option `code_map` allows mapping custom codes
77+
from `Zend\Authentication\Result` to validation message identifiers.
78+
`code_map` is an array of integer code => string message identifier pairs
79+
80+
A new custom message identifier can be specified in `code_map` which will then
81+
be registered as a new message type with the template value set to the `general` message.
82+
Once registered, the message template for the new identifier can be changed
83+
as described in the [zend-validator documentation](https://docs.zendframework.com/zend-validator/messages/).
84+
85+
```php
86+
use Zend\Authentication\Validator\Authentication as AuthenticationValidator;
87+
88+
$validator = new AuthenticationValidator([
89+
'code_map' => [
90+
// map custom result code to existing message
91+
-990 => AuthenticationValidator::IDENTITY_NOT_FOUND,
92+
// map custom result code to a new message type
93+
-991 => 'custom_failure_identifier',
94+
],
95+
]);
96+
97+
$validator->setMessage('Custom Error Happened', 'custom_failure_identifier');
98+
```

src/Validator/Authentication.php

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
33
* @see https://github.com/zendframework/zend-authentication for the canonical source repository
4-
* @copyright Copyright (c) 2013-2018 Zend Technologies USA Inc. (https://www.zend.com)
4+
* @copyright Copyright (c) 2013-2019 Zend Technologies USA Inc. (https://www.zend.com)
55
* @license https://github.com/zendframework/zend-authentication/blob/master/LICENSE.md New BSD License
66
*/
77

@@ -15,6 +15,9 @@
1515
use Zend\Stdlib\ArrayUtils;
1616
use Zend\Validator\AbstractValidator;
1717

18+
use function is_array;
19+
use function is_string;
20+
1821
/**
1922
* Authentication Validator
2023
*/
@@ -41,6 +44,12 @@ class Authentication extends AbstractValidator
4144
Result::FAILURE_UNCATEGORIZED => self::UNCATEGORIZED,
4245
];
4346

47+
/**
48+
* Authentication\Result codes mapping configurable overrides
49+
* @var string[]
50+
*/
51+
protected $codeMap = [];
52+
4453
/**
4554
* Error Messages
4655
* @var array
@@ -89,18 +98,31 @@ public function __construct($options = null)
8998
}
9099

91100
if (is_array($options)) {
92-
if (array_key_exists('adapter', $options)) {
101+
if (isset($options['adapter'])) {
93102
$this->setAdapter($options['adapter']);
94103
}
95-
if (array_key_exists('identity', $options)) {
104+
if (isset($options['identity'])) {
96105
$this->setIdentity($options['identity']);
97106
}
98-
if (array_key_exists('credential', $options)) {
107+
if (isset($options['credential'])) {
99108
$this->setCredential($options['credential']);
100109
}
101-
if (array_key_exists('service', $options)) {
110+
if (isset($options['service'])) {
102111
$this->setService($options['service']);
103112
}
113+
if (isset($options['code_map'])) {
114+
foreach ($options['code_map'] as $code => $template) {
115+
if (empty($template) || ! is_string($template)) {
116+
throw new Exception\InvalidArgumentException(
117+
'Message key in code_map option must be a non-empty string'
118+
);
119+
}
120+
if (! isset($this->messageTemplates[$template])) {
121+
$this->messageTemplates[$template] = $this->messageTemplates[static::GENERAL];
122+
}
123+
$this->codeMap[(int) $code] = $template;
124+
}
125+
}
104126
}
105127
parent::__construct($options);
106128
}
@@ -244,15 +266,27 @@ public function isValid($value = null, $context = null)
244266
return true;
245267
}
246268

247-
$code = self::GENERAL;
248-
if (array_key_exists($result->getCode(), self::CODE_MAP)) {
249-
$code = self::CODE_MAP[$result->getCode()];
250-
}
251-
$this->error($code);
269+
$messageKey = $this->mapResultCodeToMessageKey($result->getCode());
270+
$this->error($messageKey);
252271

253272
return false;
254273
}
255274

275+
/**
276+
* @param int $code Authentication result code
277+
* @return string Message key that should be used for the code
278+
*/
279+
protected function mapResultCodeToMessageKey($code)
280+
{
281+
if (isset($this->codeMap[$code])) {
282+
return $this->codeMap[$code];
283+
}
284+
if (array_key_exists($code, static::CODE_MAP)) {
285+
return static::CODE_MAP[$code];
286+
}
287+
return self::GENERAL;
288+
}
289+
256290
/**
257291
* @return ValidatableAdapterInterface
258292
* @throws Exception\RuntimeException if no adapter present in

test/Validator/AuthenticationTest.php

Lines changed: 119 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
<?php
22
/**
33
* @see https://github.com/zendframework/zend-authentication for the canonical source repository
4-
* @copyright Copyright (c) 2013-2018 Zend Technologies USA Inc. (https://www.zend.com)
4+
* @copyright Copyright (c) 2013-2019 Zend Technologies USA Inc. (https://www.zend.com)
55
* @license https://github.com/zendframework/zend-authentication/blob/master/LICENSE.md New BSD License
66
*/
77

88
namespace ZendTest\Authentication\Validator;
99

1010
use PHPUnit\Framework\TestCase;
11-
use RuntimeException;
1211
use Zend\Authentication\Adapter\ValidatableAdapterInterface;
1312
use Zend\Authentication\AuthenticationService;
1413
use Zend\Authentication\Exception;
@@ -54,6 +53,124 @@ public function testOptions()
5453
$this->assertSame($auth->getCredential(), 'password');
5554
}
5655

56+
public function testConstructorOptionCodeMapOverridesDefaultMap()
57+
{
58+
$authAdapter = new AuthTest\TestAsset\ValidatableAdapter(AuthenticationResult::FAILURE_UNCATEGORIZED);
59+
$auth = new AuthenticationValidator([
60+
'adapter' => $authAdapter,
61+
'service' => $this->authService,
62+
'identity' => 'username',
63+
'credential' => 'password',
64+
'code_map' => [
65+
AuthenticationResult::FAILURE_UNCATEGORIZED => AuthenticationValidator::IDENTITY_NOT_FOUND,
66+
]
67+
]);
68+
$this->assertFalse($auth->isValid());
69+
$this->assertArrayHasKey(
70+
AuthenticationValidator::IDENTITY_NOT_FOUND,
71+
$auth->getMessages(),
72+
print_r($auth->getMessages(), true)
73+
);
74+
}
75+
76+
public function testConstructorOptionCodeMapUsesDefaultMapForOmittedCodes()
77+
{
78+
$authAdapter = new AuthTest\TestAsset\ValidatableAdapter(AuthenticationResult::FAILURE_IDENTITY_AMBIGUOUS);
79+
$auth = new AuthenticationValidator([
80+
'adapter' => $authAdapter,
81+
'service' => $this->authService,
82+
'identity' => 'username',
83+
'credential' => 'password',
84+
'code_map' => [
85+
AuthenticationResult::FAILURE_UNCATEGORIZED => AuthenticationValidator::IDENTITY_NOT_FOUND,
86+
]
87+
]);
88+
$this->assertFalse($auth->isValid());
89+
$this->assertArrayHasKey(
90+
AuthenticationValidator::IDENTITY_AMBIGUOUS,
91+
$auth->getMessages(),
92+
print_r($auth->getMessages(), true)
93+
);
94+
}
95+
96+
public function testCodeMapAllowsToSpecifyCustomCodes()
97+
{
98+
$authAdapter = new AuthTest\TestAsset\ValidatableAdapter(-999);
99+
$auth = new AuthenticationValidator([
100+
'adapter' => $authAdapter,
101+
'service' => $this->authService,
102+
'identity' => 'username',
103+
'credential' => 'password',
104+
'code_map' => [
105+
-999 => AuthenticationValidator::IDENTITY_NOT_FOUND,
106+
]
107+
]);
108+
$this->assertFalse($auth->isValid());
109+
$this->assertArrayHasKey(
110+
AuthenticationValidator::IDENTITY_NOT_FOUND,
111+
$auth->getMessages(),
112+
print_r($auth->getMessages(), true)
113+
);
114+
}
115+
116+
public function testCodeMapAllowsToAddCustomMessageTemplates()
117+
{
118+
$auth = new AuthenticationValidator([
119+
'code_map' => [
120+
-999 => 'custom_error',
121+
]
122+
]);
123+
$templates = $auth->getMessageTemplates();
124+
$this->assertArrayHasKey(
125+
'custom_error',
126+
$templates,
127+
print_r($templates, true)
128+
);
129+
}
130+
131+
/**
132+
* @depends testCodeMapAllowsToAddCustomMessageTemplates
133+
*/
134+
public function testCodeMapCustomMessageTemplateValueDefaultsToGeneralMessageTemplate()
135+
{
136+
$auth = new AuthenticationValidator([
137+
'code_map' => [
138+
-999 => 'custom_error',
139+
]
140+
]);
141+
$templates = $auth->getMessageTemplates();
142+
$this->assertEquals($templates['general'], $templates['custom_error']);
143+
}
144+
145+
/**
146+
* @depends testCodeMapAllowsToAddCustomMessageTemplates
147+
*/
148+
public function testCustomMessageTemplateValueCanBeProvidedAsOption()
149+
{
150+
$auth = new AuthenticationValidator([
151+
'code_map' => [
152+
-999 => 'custom_error',
153+
],
154+
'messages' => [
155+
'custom_error' => 'Custom Error'
156+
]
157+
158+
]);
159+
$templates = $auth->getMessageTemplates();
160+
$this->assertEquals('Custom Error', $templates['custom_error']);
161+
}
162+
163+
public function testCodeMapOptionRequiresMessageKeyToBeString()
164+
{
165+
$this->expectException(Exception\InvalidArgumentException::class);
166+
$this->expectExceptionMessage('Message key in code_map option must be a non-empty string');
167+
$auth = new AuthenticationValidator([
168+
'code_map' => [
169+
-999 => [],
170+
]
171+
]);
172+
}
173+
57174
public function testSetters()
58175
{
59176
$this->validator->setAdapter($this->authAdapter);

0 commit comments

Comments
 (0)