Completed
Push — master ( 017947...8f5b55 )
by frey
13s
created

AbstractAuthentication::arrayItem()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 20
Code Lines 10

Duplication

Lines 20
Ratio 100 %

Importance

Changes 0
Metric Value
cc 6
eloc 10
nc 5
nop 3
dl 20
loc 20
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
namespace EntWeChat\Auth;
4
5
use EntWeChat\Core\AbstractAPI;
6
use EntWeChat\Core\AccessToken;
7
use Symfony\Component\HttpFoundation\RedirectResponse;
8
use Symfony\Component\HttpFoundation\Request;
9
10
/**
11
 * Class AbstractAuthentication.
12
 */
13
abstract class AbstractAuthentication extends AbstractAPI
14
{
15
    /**
16
     * The HTTP request instance.
17
     *
18
     * @var \Symfony\Component\HttpFoundation\Request
19
     */
20
    protected $request;
21
22
    /**
23
     * The client ID.
24
     *
25
     * @var string
26
     */
27
    protected $clientId;
28
29
    /**
30
     * The client secret.
31
     *
32
     * @var string
33
     */
34
    protected $clientSecret;
35
36
    /**
37
     * The redirect URL.
38
     *
39
     * @var string
40
     */
41
    protected $redirectUrl;
42
43
    /**
44
     * The custom parameters to be sent with the request.
45
     *
46
     * @var array
47
     */
48
    protected $parameters = [];
49
50
    /**
51
     * The scopes being requested.
52
     *
53
     * @var array
54
     */
55
    protected $scopes = [];
56
57
    /**
58
     * The separating character for the requested scopes.
59
     *
60
     * @var string
61
     */
62
    protected $scopeSeparator = ',';
63
64
    /**
65
     * The type of the encoding in the query.
66
     *
67
     * @var int Can be either PHP_QUERY_RFC3986 or PHP_QUERY_RFC1738
68
     */
69
    protected $encodingType = PHP_QUERY_RFC1738;
70
71
    /**
72
     * Indicates if the session state should be utilized.
73
     *
74
     * @var bool
75
     */
76
    protected $stateless = false;
77
78
    /**
79
     * AbstractService constructor.
80
     *
81
     * @param AccessToken $accessToken
82
     */
83
    public function __construct(AccessToken $accessToken)
84
    {
85
        parent::__construct($accessToken);
86
87
        $this->clientId = $this->getAccessToken()->getCorpId();
88
        $this->clientSecret = $this->getAccessToken()->getSecret();
89
        $this->request = Request::createFromGlobals();
90
    }
91
92
    /**
93
     * Get the authentication URL.
94
     *
95
     * @param string $state
96
     *
97
     * @return string
98
     */
99
    abstract protected function getAuthUrl($state);
100
101
    /**
102
     * Redirect the user of the application to the provider's authentication screen.
103
     *
104
     * @param string $redirectUrl
105
     *
106
     * @return \Symfony\Component\HttpFoundation\RedirectResponse
107
     */
108
    public function redirect($redirectUrl = null)
109
    {
110
        $state = null;
111
112
        if (!is_null($redirectUrl)) {
113
            $this->redirectUrl = $redirectUrl;
114
        }
115
116
        if ($this->usesState()) {
117
            $state = $this->makeState();
118
        }
119
120
        return new RedirectResponse($this->getAuthUrl($state));
0 ignored issues
show
Bug introduced by
It seems like $state can also be of type false or null; however, EntWeChat\Auth\AbstractA...ntication::getAuthUrl() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
121
    }
122
123
    /**
124
     * Set redirect url.
125
     *
126
     * @param string $redirectUrl
127
     *
128
     * @return $this
129
     */
130
    public function setRedirectUrl($redirectUrl)
131
    {
132
        $this->redirectUrl = $redirectUrl;
133
134
        return $this;
135
    }
136
137
    /**
138
     * Set redirect url.
139
     *
140
     * @param string $redirectUrl
141
     *
142
     * @return $this
143
     */
144
    public function withRedirectUrl($redirectUrl)
145
    {
146
        $this->redirectUrl = $redirectUrl;
147
148
        return $this;
149
    }
150
151
    /**
152
     * Return the redirect url.
153
     *
154
     * @return string
155
     */
156
    public function getRedirectUrl()
157
    {
158
        return $this->redirectUrl;
159
    }
160
161
    /**
162
     * Set the scopes of the requested access.
163
     *
164
     * @param array $scopes
165
     *
166
     * @return $this
167
     */
168
    public function scopes(array $scopes)
169
    {
170
        $this->scopes = $scopes;
171
172
        return $this;
173
    }
174
175
    /**
176
     * Set the request instance.
177
     *
178
     * @param Request $request
179
     *
180
     * @return $this
181
     */
182
    public function setRequest(Request $request)
0 ignored issues
show
Bug introduced by
You have injected the Request via parameter $request. This is generally not recommended as there might be multiple instances during a request cycle (f.e. when using sub-requests). Instead, it is recommended to inject the RequestStack and retrieve the current request each time you need it via getCurrentRequest().
Loading history...
183
    {
184
        $this->request = $request;
185
186
        return $this;
187
    }
188
189
    /**
190
     * Get the request instance.
191
     *
192
     * @return Request
193
     */
194
    public function getRequest()
195
    {
196
        return $this->request;
197
    }
198
199
    /**
200
     * Indicates that the provider should operate as stateless.
201
     *
202
     * @return $this
203
     */
204
    public function stateless()
205
    {
206
        $this->stateless = true;
207
208
        return $this;
209
    }
210
211
    /**
212
     * Set the custom parameters of the request.
213
     *
214
     * @param array $parameters
215
     *
216
     * @return $this
217
     */
218
    public function with(array $parameters)
219
    {
220
        $this->parameters = $parameters;
221
222
        return $this;
223
    }
224
225
    /**
226
     * Get the authentication URL for the provider.
227
     *
228
     * @param string $url
229
     * @param string $state
230
     *
231
     * @return string
232
     */
233
    protected function buildAuthUrlFromBase($url, $state)
234
    {
235
        return $url.'?'.http_build_query($this->getCodeFields($state), '', '&', $this->encodingType);
236
    }
237
238
    /**
239
     * Get the GET parameters for the code request.
240
     *
241
     * @param string|null $state
242
     *
243
     * @return array
244
     */
245
    protected function getCodeFields($state = null)
246
    {
247
        $fields = array_merge([
248
            'client_id'     => $this->clientId,
249
            'redirect_uri'  => $this->redirectUrl,
250
            'scope'         => $this->formatScopes($this->scopes, $this->scopeSeparator),
251
            'response_type' => 'code',
252
        ], $this->parameters);
253
254
        if ($this->usesState()) {
255
            $fields['state'] = $state;
256
        }
257
258
        return $fields;
259
    }
260
261
    /**
262
     * Format the given scopes.
263
     *
264
     * @param array  $scopes
265
     * @param string $scopeSeparator
266
     *
267
     * @return string
268
     */
269
    protected function formatScopes(array $scopes, $scopeSeparator)
270
    {
271
        return implode($scopeSeparator, $scopes);
272
    }
273
274
    /**
275
     * Determine if the current request / session has a mismatching "state".
276
     *
277
     * @return bool
278
     */
279
    protected function hasInvalidState()
280
    {
281
        if ($this->isStateless()) {
282
            return false;
283
        }
284
285
        $state = $this->request->getSession()->get('state');
286
287
        return !(strlen($state) > 0 && $this->request->get('state') === $state);
288
    }
289
290
    /**
291
     * Get the POST fields for the token request.
292
     *
293
     * @param string $code
294
     *
295
     * @return array
296
     */
297
    protected function getTokenFields($code)
298
    {
299
        return [
300
            'client_id'     => $this->clientId,
301
            'client_secret' => $this->clientSecret,
302
            'code'          => $code,
303
            'redirect_uri'  => $this->redirectUrl,
304
        ];
305
    }
306
307
    /**
308
     * Get the code from the request.
309
     *
310
     * @return string
311
     */
312
    protected function getCode()
313
    {
314
        return $this->request->get('code');
315
    }
316
317
    /**
318
     * Determine if the provider is operating with state.
319
     *
320
     * @return bool
321
     */
322
    protected function usesState()
323
    {
324
        return !$this->stateless;
325
    }
326
327
    /**
328
     * Determine if the provider is operating as stateless.
329
     *
330
     * @return bool
331
     */
332
    protected function isStateless()
333
    {
334
        return $this->stateless;
335
    }
336
337
    /**
338
     * Return array item by key.
339
     *
340
     * @param array  $array
341
     * @param string $key
342
     * @param mixed  $default
343
     *
344
     * @return mixed
345
     */
346 View Code Duplication
    protected function arrayItem(array $array, $key, $default = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
347
    {
348
        if (is_null($key)) {
349
            return $array;
350
        }
351
352
        if (isset($array[$key])) {
353
            return $array[$key];
354
        }
355
356
        foreach (explode('.', $key) as $segment) {
357
            if (!is_array($array) || !array_key_exists($segment, $array)) {
358
                return $default;
359
            }
360
361
            $array = $array[$segment];
362
        }
363
364
        return $array;
365
    }
366
367
    /**
368
     * Put state to session storage and return it.
369
     *
370
     * @return string|bool
371
     */
372
    protected function makeState()
373
    {
374
        $state = sha1(uniqid(mt_rand(1, 1000000), true));
375
        $session = $this->request->getSession();
376
377
        if (is_callable([$session, 'put'])) {
378
            $session->put('state', $state);
0 ignored issues
show
Bug introduced by
The method put() does not seem to exist on object<Symfony\Component...ssion\SessionInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
379
        } elseif (is_callable([$session, 'set'])) {
380
            $session->set('state', $state);
381
        } else {
382
            return false;
383
        }
384
385
        return $state;
386
    }
387
}
388