Completed
Push — master ( 4778d8...c5c7c0 )
by Дмитрий
01:42
created

src/OAuth2/AbstractProvider.php (1 issue)

Check for loose comparison of booleans.

Best Practice Bug Major

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * SocialConnect project
4
 * @author: Patsura Dmitry https://github.com/ovr <[email protected]>
5
 */
6
7
namespace SocialConnect\OAuth2;
8
9
use InvalidArgumentException;
10
use SocialConnect\OAuth2\Exception\InvalidState;
11
use SocialConnect\OAuth2\Exception\Unauthorized;
12
use SocialConnect\OAuth2\Exception\UnknownAuthorization;
13
use SocialConnect\OAuth2\Exception\UnknownState;
14
use SocialConnect\Provider\AbstractBaseProvider;
15
use SocialConnect\Provider\Exception\InvalidAccessToken;
16
use SocialConnect\Provider\Exception\InvalidResponse;
17
use SocialConnect\Common\Http\Client\Client;
18
19
abstract class AbstractProvider extends AbstractBaseProvider
20
{
21
    /**
22
     * HTTP method for access token request
23
     *
24
     * @var string
25
     */
26
    protected $requestHttpMethod = Client::POST;
27
28
    /**
29
     * @return string
30
     */
31
    abstract public function getAuthorizeUri();
32
33
    /**
34
     * @return string
35
     */
36
    abstract public function getRequestTokenUri();
37
38
    /**
39
     * Default parameters for auth url, can be redeclared inside implementation of the Provider
40
     *
41
     * @return array
42
     */
43 18
    public function getAuthUrlParameters()
44
    {
45
        return [
46 18
            'client_id' => $this->consumer->getKey(),
47 18
            'redirect_uri' => $this->getRedirectUrl(),
48 18
            'response_type' => 'code',
49 18
        ];
50
    }
51
52
    /**
53
     * 16 bytes / 128 bit / 16 symbols / 32 symbols in hex
54
     */
55
    const STATE_BYTES = 16;
56
57
    /**
58
     * @return string
59
     */
60 36
    protected function generateState()
61
    {
62 36
        if (function_exists('random_bytes')) {
63
            return bin2hex(random_bytes(self::STATE_BYTES));
64
        }
65
66 36
        if (function_exists('openssl_random_pseudo_bytes')) {
67 36
            $bytes = openssl_random_pseudo_bytes(self::STATE_BYTES, $strongSource);
68 36
            if (!$strongSource) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $strongSource of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
69
                trigger_error(
70
                    'openssl was unable to use a strong source of entropy. ' .
71
                    'Consider updating your system libraries, or ensuring ' .
72
                    'you have more available entropy.',
73
                    E_USER_WARNING
74
                );
75
            }
76
77 36
            return bin2hex($bytes);
78
        }
79
80
        trigger_error(
81
            'You do not have a safe source of random data available. ' .
82
            'Install either the openssl extension, or paragonie/random_compat. ' .
83
            'Falling back to an insecure random source.',
84
            E_USER_WARNING
85
        );
86
87
        return md5(
88
            time() . mt_rand()
89
        );
90
    }
91
92
    /**
93
     * @return string
94
     */
95 18
    public function makeAuthUrl()
96
    {
97 18
        $urlParameters = $this->getAuthUrlParameters();
98
99 18
        $this->session->set(
100 18
            'oauth2_state',
101 18
            $urlParameters['state'] = $this->generateState()
102 18
        );
103
104 18
        if (count($this->scope) > 0) {
105
            $urlParameters['scope'] = $this->getScopeInline();
106
        }
107
108 18
        if (count($this->fields) > 0) {
109
            $urlParameters['fields'] = $this->getFieldsInline();
110
        }
111
112 18
        return $this->getAuthorizeUri() . '?' . http_build_query($urlParameters);
113
    }
114
115
    /**
116
     * Parse access token from response's $body
117
     *
118
     * @param string|bool $body
119
     * @return AccessToken
120
     * @throws InvalidAccessToken
121
     */
122 3
    public function parseToken($body)
123
    {
124 3
        if (empty($body)) {
125
            throw new InvalidAccessToken('Provider response with empty body');
126
        }
127
128 3
        parse_str($body, $token);
129
130 3
        if (!is_array($token) || !isset($token['access_token'])) {
131 2
            throw new InvalidAccessToken('Provider API returned an unexpected response');
132
        }
133
134 1
        return new AccessToken($token);
135
    }
136
137
    /**
138
     * @param string $code
139
     * @return \SocialConnect\Common\Http\Request
140
     */
141 19
    protected function makeAccessTokenRequest($code)
142
    {
143
        $parameters = [
144 19
            'client_id' => $this->consumer->getKey(),
145 19
            'client_secret' => $this->consumer->getSecret(),
146 19
            'code' => $code,
147 19
            'grant_type' => 'authorization_code',
148 19
            'redirect_uri' => $this->getRedirectUrl()
149 19
        ];
150
151 19
        return new \SocialConnect\Common\Http\Request(
152 19
            $this->getRequestTokenUri(),
153 19
            $parameters,
154 19
            $this->requestHttpMethod,
155
            [
156
                'Content-Type' => 'application/x-www-form-urlencoded'
157 19
            ]
158 19
        );
159
    }
160
161
    /**
162
     * @param string $code
163
     * @return AccessToken
164
     * @throws InvalidResponse
165
     */
166 36
    public function getAccessToken($code)
167
    {
168 36
        if (!is_string($code)) {
169 18
            throw new InvalidArgumentException('Parameter $code must be a string');
170
        }
171
172 18
        $response = $this->httpClient->fromRequest(
173 18
            $this->makeAccessTokenRequest($code)
174 18
        );
175
176 18
        if (!$response->isSuccess()) {
177 18
            throw new InvalidResponse(
178 18
                'API response with error code',
179
                $response
180 18
            );
181
        }
182
183
        $body = $response->getBody();
184
        return $this->parseToken($body);
185
    }
186
187
    /**
188
     * @param array $parameters
189
     * @return AccessToken
190
     * @throws \SocialConnect\OAuth2\Exception\InvalidState
191
     * @throws \SocialConnect\OAuth2\Exception\UnknownState
192
     * @throws \SocialConnect\OAuth2\Exception\UnknownAuthorization
193
     */
194 18
    public function getAccessTokenByRequestParameters(array $parameters)
195
    {
196 18
        $state = $this->session->get('oauth2_state');
197 18
        if (!$state) {
198
            throw new UnknownAuthorization();
199
        }
200
201 18
        if (isset($parameters['error']) && $parameters['error'] === 'access_denied') {
202 18
            throw new Unauthorized();
203
        }
204
205
        if (!isset($parameters['state'])) {
206
            throw new UnknownState();
207
        }
208
209
        if ($state !== $parameters['state']) {
210
            throw new InvalidState();
211
        }
212
213
        if (!isset($parameters['code'])) {
214
            throw new Unauthorized('Unknown code');
215
        }
216
217
        return $this->getAccessToken($parameters['code']);
218
    }
219
}
220