Passed
Push — master ( 13425d...f357db )
by Alexander
02:26
created

AuthClient::getNormalizeUserAttributeMap()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 7
ccs 0
cts 4
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\AuthClient;
6
7
use Psr\Http\Client\ClientInterface as PsrClientInterface;
8
use Psr\Http\Message\RequestFactoryInterface;
9
use Psr\Http\Message\RequestInterface;
10
use Psr\Http\Message\ResponseInterface;
11
use Psr\Http\Message\ServerRequestInterface;
12
use Yiisoft\Yii\AuthClient\Exception\InvalidConfigException;
13
use Yiisoft\Yii\AuthClient\StateStorage\StateStorageInterface;
14
15
use function is_array;
16
use function is_callable;
17
18
/**
19
 * AuthClient is a base Auth Client class.
20
 *
21
 * @see AuthClientInterface
22
 */
23
abstract class AuthClient implements AuthClientInterface
24
{
25
    /**
26
     * @var array authenticated user attributes.
27
     */
28
    protected array $userAttributes = [];
29
    /**
30
     * @var array map used to normalize user attributes fetched from external auth service
31
     * in format: normalizedAttributeName => sourceSpecification
32
     * 'sourceSpecification' can be:
33
     * - string, raw attribute name
34
     * - array, pass to raw attribute value
35
     * - callable, PHP callback, which should accept array of raw attributes and return normalized value.
36
     *
37
     * For example:
38
     *
39
     * ```php
40
     * 'normalizeUserAttributeMap' => [
41
     *      'about' => 'bio',
42
     *      'language' => ['languages', 0, 'name'],
43
     *      'fullName' => function ($attributes) {
44
     *          return $attributes['firstName'] . ' ' . $attributes['lastName'];
45
     *      },
46
     *  ],
47
     * ```
48
     */
49
    protected array $normalizeUserAttributeMap = [];
50
    /**
51
     * @var array view options in format: optionName => optionValue
52
     */
53
    protected array $viewOptions;
54
55
    protected PsrClientInterface $httpClient;
56
57
    protected RequestFactoryInterface $requestFactory;
58
59
    /**
60
     * @var StateStorageInterface state storage to be used.
61
     */
62
    private StateStorageInterface $stateStorage;
63
64 9
    public function __construct(
65
        PsrClientInterface $httpClient,
66
        RequestFactoryInterface $requestFactory,
67
        StateStorageInterface $stateStorage
68
    ) {
69 9
        $this->httpClient = $httpClient;
70 9
        $this->requestFactory = $requestFactory;
71 9
        $this->stateStorage = $stateStorage;
72 9
    }
73
74
    /**
75
     * @throws InvalidConfigException
76
     *
77
     * @return array list of user attributes
78
     */
79
    public function getUserAttributes(): array
80
    {
81
        if ($this->userAttributes === null) {
82
            $this->userAttributes = $this->normalizeUserAttributes($this->initUserAttributes());
83
        }
84
85
        return $this->userAttributes;
86
    }
87
88
    /**
89
     * @param array $userAttributes list of user attributes
90
     *
91
     * @throws InvalidConfigException
92
     */
93
    public function setUserAttributes(array $userAttributes): void
94
    {
95
        $this->userAttributes = $this->normalizeUserAttributes($userAttributes);
96
    }
97
98
    /**
99
     * Normalize given user attributes according to {@see normalizeUserAttributeMap}.
100
     *
101
     * @param array $attributes raw attributes.
102
     *
103
     * @throws InvalidConfigException on incorrect normalize attribute map.
104
     *
105
     * @return array normalized attributes.
106
     */
107
    protected function normalizeUserAttributes(array $attributes): array
108
    {
109
        foreach ($this->getNormalizeUserAttributeMap() as $normalizedName => $actualName) {
110
            if (is_scalar($actualName)) {
111
                if (array_key_exists($actualName, $attributes)) {
112
                    $attributes[$normalizedName] = $attributes[$actualName];
113
                }
114
            } elseif (is_callable($actualName)) {
115
                $attributes[$normalizedName] = $actualName($attributes);
116
            } elseif (is_array($actualName)) {
117
                $haystack = $attributes;
118
                $searchKeys = $actualName;
119
                $isFound = true;
120
                while (($key = array_shift($searchKeys)) !== null) {
121
                    if (is_array($haystack) && array_key_exists($key, $haystack)) {
122
                        $haystack = $haystack[$key];
123
                    } else {
124
                        $isFound = false;
125
                        break;
126
                    }
127
                }
128
                if ($isFound) {
129
                    $attributes[$normalizedName] = $haystack;
130
                }
131
            } else {
132
                throw new InvalidConfigException(
133
                    'Invalid actual name "' . gettype($actualName) . '" specified at "' . static::class
134
135
                    . '::normalizeUserAttributeMap"'
136
                );
137
            }
138
        }
139
140
        return $attributes;
141
    }
142
143
    /**
144
     * @return array normalize user attribute map.
145
     */
146
    public function getNormalizeUserAttributeMap(): array
147
    {
148
        if ($this->normalizeUserAttributeMap === null) {
149
            $this->normalizeUserAttributeMap = $this->defaultNormalizeUserAttributeMap();
150
        }
151
152
        return $this->normalizeUserAttributeMap;
153
    }
154
155
    /**
156
     * @param array $normalizeUserAttributeMap normalize user attribute map.
157
     */
158
    public function setNormalizeUserAttributeMap(array $normalizeUserAttributeMap): void
159
    {
160
        $this->normalizeUserAttributeMap = $normalizeUserAttributeMap;
161
    }
162
163
    /**
164
     * Returns the default {@see normalizeUserAttributeMap} value.
165
     * Particular client may override this method in order to provide specific default map.
166
     *
167
     * @return array normalize attribute map.
168
     */
169
    protected function defaultNormalizeUserAttributeMap(): array
170
    {
171
        return [];
172
    }
173
174
    /**
175
     * Initializes authenticated user attributes.
176
     *
177
     * @return array auth user attributes.
178
     */
179
    abstract protected function initUserAttributes(): array;
180
181
    /**
182
     * @return array view options in format: optionName => optionValue
183
     */
184
    public function getViewOptions(): array
185
    {
186
        if ($this->viewOptions === null) {
187
            $this->viewOptions = $this->defaultViewOptions();
188
        }
189
190
        return $this->viewOptions;
191
    }
192
193
    /**
194
     * @param array $viewOptions view options in format: optionName => optionValue
195
     */
196
    public function setViewOptions(array $viewOptions): void
197
    {
198
        $this->viewOptions = $viewOptions;
199
    }
200
201
    /**
202
     * Returns the default {@see viewOptions} value.
203
     * Particular client may override this method in order to provide specific default view options.
204
     *
205
     * @return array list of default {@see viewOptions}
206
     */
207
    protected function defaultViewOptions(): array
208
    {
209
        return [];
210
    }
211
212
    abstract public function buildAuthUrl(ServerRequestInterface $incomingRequest, array $params): string;
213
214 2
    public function createRequest(string $method, string $uri): RequestInterface
215
    {
216 2
        return $this->requestFactory->createRequest($method, $uri);
217
    }
218
219
    /**
220
     * Sets persistent state.
221
     *
222
     * @param string $key state key.
223
     * @param mixed $value state value
224
     *
225
     * @return $this the object itself
226
     */
227 1
    protected function setState(string $key, $value): self
228
    {
229 1
        $this->stateStorage->set($this->getStateKeyPrefix() . $key, $value);
230 1
        return $this;
231
    }
232
233
    /**
234
     * Returns session key prefix, which is used to store internal states.
235
     *
236
     * @return string session key prefix.
237
     */
238 3
    protected function getStateKeyPrefix(): string
239
    {
240 3
        return static::class . '_' . $this->getName() . '_';
241
    }
242
243
    /**
244
     * Returns persistent state value.
245
     *
246
     * @param string $key state key.
247
     *
248
     * @return mixed state value.
249
     */
250 2
    protected function getState(string $key)
251
    {
252 2
        return $this->stateStorage->get($this->getStateKeyPrefix() . $key);
253
    }
254
255
    /**
256
     * Removes persistent state value.
257
     *
258
     * @param string $key state key.
259
     *
260
     * @return bool success.
261
     */
262
    protected function removeState(string $key): void
263
    {
264
        $this->stateStorage->remove($this->getStateKeyPrefix() . $key);
265
    }
266
267
    protected function sendRequest(RequestInterface $request): ResponseInterface
268
    {
269
        return $this->httpClient->sendRequest($request);
270
    }
271
}
272