ThirdPartyGrantType   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 249
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 0%

Importance

Changes 5
Bugs 3 Features 0
Metric Value
c 5
b 3
f 0
dl 0
loc 249
wmc 32
lcom 1
cbo 14
ccs 0
cts 126
cp 0
rs 9.6

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 16 2
A getQuerystringIdentifier() 0 4 1
D validateRequest() 0 88 18
A getClientId() 0 4 1
A getUserId() 0 8 2
A getScope() 0 4 2
A createAccessToken() 0 4 1
A getProviders() 0 4 1
A setProviders() 0 8 2
A addProvider() 0 4 1
A connectUserToThirdParty() 0 12 1
1
<?php
2
/**
3
 * @author Stefano Torresi (http://stefanotorresi.it)
4
 * @license See the file LICENSE.txt for copying permission.
5
 * ************************************************
6
 */
7
8
namespace Thorr\OAuth2\GrantType;
9
10
use Exception;
11
use InvalidArgumentException;
12
use OAuth2\GrantType\GrantTypeInterface;
13
use OAuth2\RequestInterface;
14
use OAuth2\ResponseInterface;
15
use OAuth2\ResponseType\AccessTokenInterface as AccessTokenFactory;
16
use Thorr\OAuth2\DataMapper\ThirdPartyMapperInterface;
17
use Thorr\OAuth2\DataMapper\TokenMapperInterface;
18
use Thorr\OAuth2\Entity;
19
use Thorr\OAuth2\GrantType\ThirdParty\Provider\Exception\ClientException;
20
use Thorr\OAuth2\GrantType\ThirdParty\Provider\ProviderInterface;
21
use Thorr\OAuth2\Options\ModuleOptions;
22
use Thorr\Persistence\DataMapper\DataMapperInterface;
23
use Traversable;
24
use Zend\Stdlib\Guard\ArrayOrTraversableGuardTrait;
25
26
class ThirdPartyGrantType implements GrantTypeInterface
27
{
28
    use ArrayOrTraversableGuardTrait;
29
30
    /**
31
     * @var DataMapperInterface
32
     */
33
    protected $userMapper;
34
35
    /**
36
     * @var ThirdPartyMapperInterface
37
     */
38
    protected $thirdPartyMapper;
39
40
    /**
41
     * @var TokenMapperInterface
42
     */
43
    protected $accessTokenMapper;
44
45
    /**
46
     * @var ModuleOptions
47
     */
48
    protected $moduleOptions;
49
50
    /**
51
     * @var Entity\UserInterface
52
     */
53
    protected $user;
54
55
    /**
56
     * @var array|Traversable
57
     */
58
    protected $providers;
59
60
    /**
61
     * @param DataMapperInterface       $userMapper
62
     * @param ThirdPartyMapperInterface $thirdPartyMapper
63
     * @param TokenMapperInterface      $accessTokenMapper
64
     * @param ModuleOptions             $moduleOptions
65
     */
66
    public function __construct(
67
        DataMapperInterface $userMapper,
68
        ThirdPartyMapperInterface $thirdPartyMapper,
69
        TokenMapperInterface $accessTokenMapper,
70
        ModuleOptions $moduleOptions
71
    ) {
72
        $this->userMapper        = $userMapper;
73
        $this->thirdPartyMapper  = $thirdPartyMapper;
74
        $this->accessTokenMapper = $accessTokenMapper;
75
        $this->moduleOptions     = $moduleOptions;
76
77
        foreach ($moduleOptions->getThirdPartyProviders() as $providerConfig) {
78
            $provider = ThirdParty\Provider\ProviderFactory::createProvider($providerConfig);
79
            $this->addProvider($provider);
80
        }
81
    }
82
83
    /**
84
     * @return string
85
     */
86
    public function getQuerystringIdentifier()
87
    {
88
        return 'third_party';
89
    }
90
91
    /**
92
     * @param RequestInterface  $request
93
     * @param ResponseInterface $response
94
     *
95
     * @return bool
96
     */
97
    public function validateRequest(RequestInterface $request, ResponseInterface $response)
98
    {
99
        $providerName        = $request->request('provider');
100
        $providerUserId      = $request->request('provider_user_id');
101
        $providerAccessToken = $request->request('provider_access_token');
102
103
        if (! $providerName || ! $providerUserId || ! $providerAccessToken) {
104
            $response->setError(
105
                400,
106
                'invalid_request',
107
                'One or more missing parameter: "provider", "provider_user_id" and "provider_access_token" are required'
108
            );
109
110
            return false;
111
        }
112
113
        $provider = isset($this->providers[$providerName]) ? $this->providers[$providerName] : null;
114
115
        if (! $provider instanceof ProviderInterface) {
116
            $response->setError(400, 'invalid_request', 'Unknown provider selected');
117
118
            return false;
119
        }
120
121
        try {
122
            $errorMessage = '';
123
            if (! $provider->validate($providerUserId, $providerAccessToken, $errorMessage)) {
124
                $response->setError(401, 'invalid_grant', 'Invalid third party credentials: ' . $errorMessage);
125
126
                return false;
127
            }
128
        } catch (ClientException $e) {
129
            $response->setError($e->getCode(), 'provider_client_error', $e->getMessage());
130
131
            return false;
132
        } catch (Exception $e) {
133
            $response->setError(500, 'provider_error', $e->getMessage());
134
135
            return false;
136
        }
137
138
        $token       = $request->request('access_token');
139
        $accessToken = $token ? $this->accessTokenMapper->findByToken($token) : null;
140
141
        if ($accessToken instanceof Entity\AccessToken && $accessToken->isExpired()) {
142
            $response->setError(401, 'invalid_grant', 'Access token is expired');
143
144
            return false;
145
        }
146
147
        $thirdPartyUser = $this->thirdPartyMapper->findByProvider($provider);
148
149
        switch (true) {
150
            // a known user tries to connect with third party credentials owned by another user? issue an error
151
            case ($accessToken instanceof Entity\AccessToken
152
                    && $thirdPartyUser instanceof Entity\ThirdParty
153
                    && $thirdPartyUser->getUser() !== $accessToken->getUser()):
154
                $response->setError(400, 'invalid_request', 'Another user is already registered with same credentials');
155
156
                return false;
157
158
            // known third party credentials? update the data and grab the user form it
159
            case ($thirdPartyUser instanceof Entity\ThirdParty):
160
                $thirdPartyUser->setData($provider->getUserData());
161
                $user = $thirdPartyUser->getUser();
162
                break;
163
164
            // valid access token? grab the user form it
165
            case ($accessToken instanceof Entity\AccessToken):
166
                $user = $accessToken->getUser();
167
                break;
168
169
            // no third party credentials or access token? it's a new user
170
            default:
171
                $userClass = $this->moduleOptions->getUserEntityClassName();
172
                $user      = new $userClass();
173
        }
174
175
        // in case 3 and 4 we need to connect the user with new third party credentials
176
        if (! $thirdPartyUser instanceof Entity\ThirdParty) {
177
            $this->connectUserToThirdParty($user, $provider);
178
        }
179
180
        $this->userMapper->save($user);
181
        $this->user = $user;
182
183
        return true;
184
    }
185
186
    /**
187
     * not actually needed
188
     * client_id is retrieved via a ClientAssertionTypeInterface before querying this grant.
189
     */
190
    public function getClientId()
191
    {
192
        return;
193
    }
194
195
    /**
196
     * @return null|string
197
     */
198
    public function getUserId()
199
    {
200
        if (! $this->user) {
201
            return;
202
        }
203
204
        return $this->user->getUuid();
205
    }
206
207
    /**
208
     * @return null|string
209
     */
210
    public function getScope()
211
    {
212
        return $this->user instanceof Entity\ScopesProviderInterface ? $this->user->getScopesString() : null;
213
    }
214
215
    /**
216
     * @param AccessTokenFactory $accessTokenFactory
217
     * @param $client_id
218
     * @param $user_id
219
     * @param $scope
220
     *
221
     * @return mixed
222
     */
223
    public function createAccessToken(AccessTokenFactory $accessTokenFactory, $client_id, $user_id, $scope)
224
    {
225
        return $accessTokenFactory->createAccessToken($client_id, $user_id, $scope);
226
    }
227
228
    /**
229
     * @return array
230
     */
231
    public function getProviders()
232
    {
233
        return $this->providers;
234
    }
235
236
    /**
237
     * @param array|Traversable $providers
238
     *
239
     * @throws InvalidArgumentException
240
     */
241
    public function setProviders($providers)
242
    {
243
        $this->guardForArrayOrTraversable($providers);
244
245
        foreach ($providers as $provider) {
246
            $this->addProvider($provider);
247
        }
248
    }
249
250
    /**
251
     * @param ProviderInterface $provider
252
     */
253
    public function addProvider(ProviderInterface $provider)
254
    {
255
        $this->providers[$provider->getIdentifier()] = $provider;
256
    }
257
258
    /**
259
     * @param Entity\ThirdPartyAwareUserInterface $user
260
     * @param ProviderInterface                   $provider
261
     */
262
    protected function connectUserToThirdParty(Entity\ThirdPartyAwareUserInterface $user, ProviderInterface $provider)
263
    {
264
        $thirdPartyUser = new Entity\ThirdParty(
265
            null,
266
            $provider->getUserId(),
267
            $provider->getIdentifier(),
268
            $user,
269
            $provider->getUserData()
270
        );
271
272
        $user->addThirdParty($thirdPartyUser);
273
    }
274
}
275