Completed
Branch master (bda8d5)
by Дмитрий
02:19
created

AbstractProvider::getAccessTokenByRequestParameters()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 39.042

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 13
dl 0
loc 26
rs 8.4444
c 2
b 0
f 0
ccs 3
cts 14
cp 0.2143
cc 8
nc 7
nop 1
crap 39.042
1
<?php
2
/**
3
 * SocialConnect project
4
 * @author: Patsura Dmitry https://github.com/ovr <[email protected]>
5
 */
6
declare(strict_types=1);
7
8
namespace SocialConnect\OAuth2;
9
10
use InvalidArgumentException;
11
use SocialConnect\OAuth2\Exception\InvalidState;
12
use SocialConnect\OAuth2\Exception\Unauthorized;
13
use SocialConnect\OAuth2\Exception\UnknownAuthorization;
14
use SocialConnect\OAuth2\Exception\UnknownState;
15
use SocialConnect\Provider\AbstractBaseProvider;
16
use SocialConnect\Provider\Exception\InvalidAccessToken;
17
use SocialConnect\Provider\Exception\InvalidResponse;
18
use SocialConnect\Common\Http\Client\Client;
19
20
abstract class AbstractProvider extends AbstractBaseProvider
21
{
22
    /**
23
     * HTTP method for access token request
24
     *
25
     * @var string
26
     */
27
    protected $requestHttpMethod = Client::POST;
28
29
    /**
30
     * @return string
31
     */
32
    abstract public function getAuthorizeUri();
33
34
    /**
35
     * @return string
36
     */
37
    abstract public function getRequestTokenUri();
38
39
    /**
40
     * {@inheritdoc}
41
     */
42 23
    public function getAuthUrlParameters(): array
43
    {
44 23
        $parameters = parent::getAuthUrlParameters();
45
46
        // special parameters only required for OAuth2
47 23
        $parameters['client_id'] = $this->consumer->getKey();
48 23
        $parameters['redirect_uri'] = $this->getRedirectUrl();
49 23
        $parameters['response_type'] = 'code';
50
51 23
        return $parameters;
52
    }
53
54
    /**
55
     * 16 bytes / 128 bit / 16 symbols / 32 symbols in hex
56
     */
57
    const STATE_BYTES = 16;
58
59
    /**
60
     * @return string
61
     */
62 46
    protected function generateState()
63
    {
64 46
        return bin2hex(random_bytes(self::STATE_BYTES));
65
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70 23
    public function makeAuthUrl(): string
71
    {
72 23
        $urlParameters = $this->getAuthUrlParameters();
73
74 23
        if (!$this->getBoolOption('stateless', false)) {
75 23
            $this->session->set(
76 23
                'oauth2_state',
77 23
                $urlParameters['state'] = $this->generateState()
78
            );
79
        }
80
81 23
        if (count($this->scope) > 0) {
82
            $urlParameters['scope'] = $this->getScopeInline();
83
        }
84
85 23
        return $this->getAuthorizeUri() . '?' . http_build_query($urlParameters);
86
    }
87
88
    /**
89
     * Parse access token from response's $body
90
     *
91
     * @param string|bool $body
92
     * @return AccessToken
93
     * @throws InvalidAccessToken
94
     */
95 3
    public function parseToken($body)
96
    {
97 3
        if (empty($body)) {
98
            throw new InvalidAccessToken('Provider response with empty body');
99
        }
100
101 3
        parse_str($body, $token);
102
103 3
        if (!is_array($token) || !isset($token['access_token'])) {
104 2
            throw new InvalidAccessToken('Provider API returned an unexpected response');
105
        }
106
107 1
        return new AccessToken($token);
108
    }
109
110
    /**
111
     * @param string $code
112
     * @return \SocialConnect\Common\Http\Request
113
     */
114 23
    protected function makeAccessTokenRequest($code)
115
    {
116
        $parameters = [
117 23
            'client_id' => $this->consumer->getKey(),
118 23
            'client_secret' => $this->consumer->getSecret(),
119 23
            'code' => $code,
120 23
            'grant_type' => 'authorization_code',
121 23
            'redirect_uri' => $this->getRedirectUrl()
122
        ];
123
124 23
        return new \SocialConnect\Common\Http\Request(
125 23
            $this->getRequestTokenUri(),
126 23
            $parameters,
127 23
            $this->requestHttpMethod,
128
            [
129 23
                'Content-Type' => 'application/x-www-form-urlencoded'
130
            ]
131
        );
132
    }
133
134
    /**
135
     * @param string $code
136
     * @return AccessToken
137
     * @throws InvalidResponse
138
     */
139 46
    public function getAccessToken($code)
140
    {
141 46
        if (!is_string($code)) {
142 23
            throw new InvalidArgumentException('Parameter $code must be a string');
143
        }
144
145 23
        $response = $this->httpClient->fromRequest(
146 23
            $this->makeAccessTokenRequest($code)
147
        );
148
149 23
        if (!$response->isSuccess()) {
150 23
            throw new InvalidResponse(
151 23
                'API response with error code',
152 23
                $response
153
            );
154
        }
155
156
        $body = $response->getBody();
157
        return $this->parseToken($body);
158
    }
159
160
    /**
161
     * @param array $parameters
162
     * @return AccessToken
163
     * @throws InvalidResponse
164
     * @throws InvalidState
165
     * @throws Unauthorized
166
     * @throws UnknownAuthorization
167
     * @throws UnknownState
168
     */
169 23
    public function getAccessTokenByRequestParameters(array $parameters)
170
    {
171 23
        if (isset($parameters['error']) && $parameters['error'] === 'access_denied') {
172 23
            throw new Unauthorized();
173
        }
174
175
        if (!isset($parameters['code'])) {
176
            throw new Unauthorized('Unknown code');
177
        }
178
179
        if (!$this->getBoolOption('stateless', false)) {
180
            $state = $this->session->get('oauth2_state');
181
            if (!$state) {
182
                throw new UnknownAuthorization();
183
            }
184
185
            if (!isset($parameters['state'])) {
186
                throw new UnknownState();
187
            }
188
189
            if ($state !== $parameters['state']) {
190
                throw new InvalidState();
191
            }
192
        }
193
194
        return $this->getAccessToken($parameters['code']);
195
    }
196
}
197