Completed
Push — master ( c5c7c0...e9ef24 )
by Дмитрий
01:35
created

AbstractProvider::generateState()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
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
        return bin2hex(random_bytes(self::STATE_BYTES));
63
    }
64
65
    /**
66
     * @return string
67
     */
68 18
    public function makeAuthUrl()
69
    {
70 18
        $urlParameters = $this->getAuthUrlParameters();
71
72 18
        $this->session->set(
73 18
            'oauth2_state',
74 18
            $urlParameters['state'] = $this->generateState()
75 18
        );
76
77 18
        if (count($this->scope) > 0) {
78
            $urlParameters['scope'] = $this->getScopeInline();
79
        }
80
81 18
        if (count($this->fields) > 0) {
82
            $urlParameters['fields'] = $this->getFieldsInline();
83
        }
84
85 18
        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 19
    protected function makeAccessTokenRequest($code)
115
    {
116
        $parameters = [
117 19
            'client_id' => $this->consumer->getKey(),
118 19
            'client_secret' => $this->consumer->getSecret(),
119 19
            'code' => $code,
120 19
            'grant_type' => 'authorization_code',
121 19
            'redirect_uri' => $this->getRedirectUrl()
122 19
        ];
123
124 19
        return new \SocialConnect\Common\Http\Request(
125 19
            $this->getRequestTokenUri(),
126 19
            $parameters,
127 19
            $this->requestHttpMethod,
128
            [
129
                'Content-Type' => 'application/x-www-form-urlencoded'
130 19
            ]
131 19
        );
132
    }
133
134
    /**
135
     * @param string $code
136
     * @return AccessToken
137
     * @throws InvalidResponse
138
     */
139 36
    public function getAccessToken($code)
140
    {
141 36
        if (!is_string($code)) {
142 18
            throw new InvalidArgumentException('Parameter $code must be a string');
143
        }
144
145 18
        $response = $this->httpClient->fromRequest(
146 18
            $this->makeAccessTokenRequest($code)
147 18
        );
148
149 18
        if (!$response->isSuccess()) {
150 18
            throw new InvalidResponse(
151 18
                'API response with error code',
152
                $response
153 18
            );
154
        }
155
156
        $body = $response->getBody();
157
        return $this->parseToken($body);
158
    }
159
160
    /**
161
     * @param array $parameters
162
     * @return AccessToken
163
     * @throws \SocialConnect\OAuth2\Exception\InvalidState
164
     * @throws \SocialConnect\OAuth2\Exception\UnknownState
165
     * @throws \SocialConnect\OAuth2\Exception\UnknownAuthorization
166
     */
167 18
    public function getAccessTokenByRequestParameters(array $parameters)
168
    {
169 18
        $state = $this->session->get('oauth2_state');
170 18
        if (!$state) {
171
            throw new UnknownAuthorization();
172
        }
173
174 18
        if (isset($parameters['error']) && $parameters['error'] === 'access_denied') {
175 18
            throw new Unauthorized();
176
        }
177
178
        if (!isset($parameters['state'])) {
179
            throw new UnknownState();
180
        }
181
182
        if ($state !== $parameters['state']) {
183
            throw new InvalidState();
184
        }
185
186
        if (!isset($parameters['code'])) {
187
            throw new Unauthorized('Unknown code');
188
        }
189
190
        return $this->getAccessToken($parameters['code']);
191
    }
192
}
193