Completed
Push — master ( 8d6a23...e237a8 )
by Michał
02:07
created

Provider::createIdentity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 2
1
<?php namespace nyx\auth\id\protocols\oauth2;
2
3
// External dependencies
4
use GuzzleHttp\Promise\PromiseInterface as Promise;
5
use nyx\utils;
6
7
// Internal dependencies
8
use nyx\auth\id\protocols\oauth2;
9
use nyx\auth;
10
11
/**
12
 * OAuth 2.0 Provider
13
 *
14
 * @package     Nyx\Auth
15
 * @version     0.1.0
16
 * @author      Michal Chojnacki <[email protected]>
17
 * @copyright   2012-2017 Nyx Dev Team
18
 * @link        https://github.com/unyx/nyx
19
 */
20
abstract class Provider extends auth\id\Provider implements interfaces\Provider
21
{
22
    /**
23
     * The character separating different scopes in the request.
24
     */
25
    const SCOPE_SEPARATOR = ',';
26
27
    /**
28
     * @var array   The default access scopes to be requested during the authorization step.
29
     */
30
    protected $defaultScopes = [];
31
32
    /**
33
     * {@inheritDoc}
34
     */
35
    public function authorize(callable $redirect, array $parameters = [])
36
    {
37
        $parameters += [
38
            'scope'         => implode(static::SCOPE_SEPARATOR, $this->defaultScopes),
39
            'response_type' => 'code'
40
        ];
41
42
        // The explicitly set Client Credentials will always overwrite the keys' values from the optional
43
        // $parameters if they are present. If you want to use other credentials, either use the getAuthorizeUrl()
44
        // method directly or instantiate a Provider with different consumer credentials.
45
        $parameters['client_id']    = $this->consumer->getId();
46
        $parameters['redirect_uri'] = $this->consumer->getRedirectUri();
47
48
        // Only doing an isset here - can't easily predict valid values nor force properly randomized values.
49
        // Invalid requests will be rejected by the endpoint, after all.
50
        $parameters['state'] = $parameters['state'] ?? utils\Random::string(16);
51
52
        // The state gets passed along explicitly as the second argument since it will *always* need to be persisted
53
        // in some way by the end-user until it can be discarded after a successful exchange.
54
        return $redirect($this->getAuthorizeUrl($parameters), $parameters['state'], $parameters);
55
    }
56
57
    /**
58
     * Exchanges the given authorization code grant for an Access Token.
59
     *
60
     * @param   string  $code       The authorization code to exchange.
61
     * @return  Promise             A Promise for an Access Token.
62
     */
63
    public function exchange(string $code) : Promise
64
    {
65
        return $this->request('POST', $this->getExchangeUrl(), null, [
66
            'form_params' => [
67
                'client_id'     => $this->consumer->getId(),
68
                'client_secret' => $this->consumer->getSecret(),
69
                'redirect_uri'  => $this->consumer->getRedirectUri(),
70
                'grant_type'    => 'authorization_code',
71
                'code'          => $code
72
            ]
73
        ])->then(function (array $data) {
74
            return $this->createToken($data);
75
        });
76
    }
77
78
    /**
79
     * {@inheritDoc}
80
     */
81
    public function identify(oauth2\Token $token) : Promise
82
    {
83
        return $this->request('GET', $this->getIdentifyUrl(), $token)->then(function (array $data) use ($token) {
84
            return $this->createIdentity($token, $data);
85
        });
86
    }
87
88
    /**
89
     * {@inheritDoc}
90
     */
91
    protected function getDefaultRequestOptions(auth\interfaces\Token $token = null) : array
92
    {
93
        $options = parent::getDefaultRequestOptions($token);
94
95
        // If the $token is explicitly given, it will override the respective authorization header.
96
        if (isset($token)) {
97
            $options['headers']['Authorization'] = 'Bearer '.$token;
98
        }
99
100
        return $options;
101
    }
102
103
    /**
104
     * Creates an OAuth 2.0 Access Token instance based on raw response data.
105
     *
106
     * @param   array           $data   The raw (response) data to base on.
107
     * @return  oauth2\Token            The resulting OAuth 2.0 Access Token instance.
108
     * @throws  \RuntimeException       When the data did not contain an access token in a recognized format.
109
     */
110
    protected function createToken(array $data) : oauth2\Token
111
    {
112
        // The HTTP Client will throw on an unsuccessful response, but we'll double check that we actually got
113
        // an access token in response.
114
        if (empty($data['access_token'])) {
115
            throw new \RuntimeException('The Provider did not return an access token or it was in an unrecognized format.');
116
        }
117
118
        $token = new oauth2\Token($data['access_token']);
119
120
        if (!empty($data['refresh_token'])) {
121
            $token->setRefreshToken($data['refresh_token']);
122
        }
123
124
        if (!empty($data['expires_in'])) {
125
            $token->setExpiry($data['expires_in']);
126
        }
127
128
        // Some providers, like Github or Slack, return the granted scopes along with the Tokens. Let's make
129
        // use of that in the base class since an isset isn't exactly expensive and if other providers happen
130
        // to return the scopes under a different key, child classes can just remap the value.
131
        if (isset($data['scope'])) {
132
            $token->setScopes(is_array($data['scope']) ? $data['scope'] : explode(static::SCOPE_SEPARATOR, $data['scope']));
133
        }
134
135
        return $token;
136
    }
137
}
138