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

Provider::authorize()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
c 0
b 0
f 0
rs 9.4285
cc 2
eloc 7
nc 2
nop 2
1
<?php namespace nyx\auth\id\protocols\oauth1;
2
3
// External dependencies
4
use Psr\Http\Message\ResponseInterface as Response;
5
use GuzzleHttp\Promise\PromiseInterface as Promise;
6
7
// Internal dependencies
8
use nyx\auth\id;
9
use nyx\auth;
10
11
/**
12
 * Base OAuth 1.0a 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 id\Provider implements interfaces\Provider
21
{
22
    /**
23
     * @var interfaces\Signer   The Signer used for generating OAuth 1.0a compliant signatures of Requests
24
     *                          sent to this Identity Provider service.
25
     */
26
    protected $signer;
27
28
    /**
29
     * {@inheritDoc}
30
     *
31
     * @param   interfaces\Signer   $signer     The Signer to use for generating OAuth 1.0a compliant signatures
32
     *                                          of Requests sent to this Identity Provider service.
33
     */
34
    public function __construct(id\credentials\Client $consumer, interfaces\Signer $signer = null)
35
    {
36
        parent::__construct($consumer);
37
38
        $this->signer = $signer;
39
    }
40
41
    /**
42
     * {@inheritDoc}
43
     */
44
    public function getTemporaryCredentialsUrl() : string
45
    {
46
        return static::URL_REQUEST;
47
    }
48
49
    /**
50
     * {@inheritDoc}
51
     */
52
    public function authorize(callable $redirect, array $parameters = [])
53
    {
54
        if (!isset($parameters['oauth_token'])) {
55
            $temporaryCredentials = $this->handshake()->wait();
56
57
            // We will pass along the token to the authorize redirect, but the secret for the
58
            // upcoming exchange needs to be persisted until the exchange happens, so we'll hand it back
59
            // to the provided callable to deal with that.
60
            $parameters['oauth_token'] = $temporaryCredentials->getId();
61
        } else {
62
            $temporaryCredentials = null;
63
        }
64
65
        return $redirect($this->getAuthorizeUrl($parameters), $temporaryCredentials, $parameters);
66
    }
67
68
    /**
69
     * {@inheritDoc}
70
     */
71
    public function handshake() : Promise
72
    {
73
        return $this->requestCredentials($this->getTemporaryCredentialsUrl(), [
74
            'oauth1' => [
75
                'callback' => true
76
            ]
77
        ]);
78
    }
79
80
    /**
81
     * {@inheritDoc}
82
     */
83
    public function exchange(auth\Credentials $temporary, string $verifier) : Promise
84
    {
85
        return $this->requestCredentials($this->getExchangeUrl(), [
86
            'form_params' => [
87
                'oauth_verifier' => $verifier
88
            ]
89
        ], $temporary);
90
    }
91
92
    /**
93
     * {@inheritDoc}
94
     */
95
    public function identify(auth\Credentials $credentials) : Promise
96
    {
97
        return $this->request('GET', $this->getIdentifyUrl(), $credentials)->then(function (array $data) use ($credentials) {
98
            return $this->createIdentity($credentials, $data);
99
        });
100
    }
101
102
    /**
103
     * {@inheritDoc}
104
     */
105
    protected function getDefaultRequestOptions(auth\interfaces\Token $token = null) : array
106
    {
107
        // We are doing the union even if $token is actually not set - we need the "oauth1" key in the options
108
        // to be not null so that our authorization middleware can pick it up and do its magic.
109
        return [
110
            'oauth1' => [
111
                'token' => $token
112
            ]
113
        ] + parent::getDefaultRequestOptions($token);
114
    }
115
116
    /**
117
     * {@inheritDoc}
118
     *
119
     * Overridden as we need to instantiate a custom middleware stack that includes our authorization
120
     * middleware for Guzzle.
121
     */
122
    protected function getHttpClient() : \GuzzleHttp\ClientInterface
123
    {
124
        if (!isset($this->httpClient)) {
125
126
            $stack = \GuzzleHttp\HandlerStack::create();
127
            $stack->push(new middlewares\Authorization($this->consumer, $this->getSigner()), 'authorization');
128
129
            $this->httpClient = new \GuzzleHttp\Client(['handler' => $stack]);
130
        }
131
132
        return $this->httpClient;
133
    }
134
135
    /**
136
     * Returns the Signer used for generating OAuth 1.0a compliant signatures of Requests sent to this
137
     * Identity Provider service. Lazily instantiates a default HMAC-SHA1 Signer if no Signer is set yet.
138
     *
139
     * @return  interfaces\Signer
140
     */
141
    protected function getSigner() : interfaces\Signer
142
    {
143
        return $this->signer ?: $this->signer = new signers\HmacSha1();
144
    }
145
146
    /**
147
     * Performs a POST request to the specified URL assuming the Response will contain form-encoded
148
     * OAuth 1.0a credentials, either temporary or proper, which will then be used to create a auth\Credentials
149
     * instance out of.
150
     *
151
     * @param   string              $url        The URL to query.
152
     * @param   array               $options    Additional request options (will be merged with the defaults).
153
     * @param   auth\Credentials    $temporary  A set of temporary credentials. Must be set when requesting
154
     *                                          proper credentials as it affects how the middleware signs the request.
155
     *                                          Must *not* be set when requesting those temporary credentials as this
156
     *                                          in turn affects how the response is verified.
157
     * @return  Promise                         A Promise for a set of Credentials (an auth\Credentials instance).
158
     */
159
    protected function requestCredentials(string $url, array $options, auth\Credentials $temporary = null) : Promise
160
    {
161
        return $this->getHttpClient()
162
            ->requestAsync('POST', $url, array_merge_recursive($this->getDefaultRequestOptions($temporary), $options))
163
            ->then(function (Response $response) use($temporary) {
164
165
                // We are assuming a form-encoded body here - for any Provider that does not return those in this format
166
                // for handshakes and exchanges this will require refactoring.
167
                parse_str((string) $response->getBody(), $data);
168
169
                if (!is_array($data) || !isset($data['oauth_token']) || !isset($data['oauth_token_secret'])) {
170
                    throw new \Exception('Failed to parse the Response after requesting OAuth 1.0a credentials.');
171
                }
172
173
                if (isset($data['error'])) {
174
                    throw new \Exception("Failed to retrieve credentials: {$data['error']}.");
175
                }
176
177
                // Only check this if we've got a Response to a request for temporary credentials (eg. the temporary
178
                // credentials were not given).
179
                if (!isset($temporary) && (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] != 'true')) {
180
                    throw new \Exception('Failed to retrieve temporary credentials.');
181
                }
182
183
                return new auth\Credentials($data['oauth_token'], $data['oauth_token_secret']);
184
            });
185
    }
186
}
187