Completed
Push — master ( 1de13c...bf55ce )
by Alex
33:38
created

RefreshTokenGrant   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 110
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 5
Bugs 1 Features 1
Metric Value
wmc 13
c 5
b 1
f 1
lcom 1
cbo 10
dl 0
loc 110
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
B respondToAccessTokenRequest() 0 47 5
B validateOldRefreshToken() 0 30 6
A getIdentifier() 0 4 1
1
<?php
2
/**
3
 * OAuth 2.0 Refresh token grant.
4
 *
5
 * @author      Alex Bilbie <[email protected]>
6
 * @copyright   Copyright (c) Alex Bilbie
7
 * @license     http://mit-license.org/
8
 *
9
 * @link        https://github.com/thephpleague/oauth2-server
10
 */
11
namespace League\OAuth2\Server\Grant;
12
13
use League\OAuth2\Server\Entities\ScopeEntityInterface;
14
use League\OAuth2\Server\Exception\OAuthServerException;
15
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
16
use League\OAuth2\Server\RequestEvent;
17
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
18
use Psr\Http\Message\ServerRequestInterface;
19
20
/**
21
 * Refresh token grant.
22
 */
23
class RefreshTokenGrant extends AbstractGrant
24
{
25
    /**
26
     * @param \League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface $refreshTokenRepository
27
     */
28
    public function __construct(RefreshTokenRepositoryInterface $refreshTokenRepository)
29
    {
30
        $this->setRefreshTokenRepository($refreshTokenRepository);
31
32
        $this->refreshTokenTTL = new \DateInterval('P1M');
33
    }
34
35
    /**
36
     * {@inheritdoc}
37
     */
38
    public function respondToAccessTokenRequest(
39
        ServerRequestInterface $request,
40
        ResponseTypeInterface $responseType,
41
        \DateInterval $accessTokenTTL
42
    ) {
43
        // Validate request
44
        $client = $this->validateClient($request);
45
        $oldRefreshToken = $this->validateOldRefreshToken($request, $client->getIdentifier());
46
        $scopes = $this->validateScopes($this->getRequestParameter('scope', $request));
47
48
        // If no new scopes are requested then give the access token the original session scopes
49
        if (count($scopes) === 0) {
50
            $scopes = array_map(function ($scopeId) use ($client) {
51
                $scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeId);
52
53
                if (!$scope instanceof ScopeEntityInterface) {
54
                    // @codeCoverageIgnoreStart
55
                    throw OAuthServerException::invalidScope($scopeId);
56
                    // @codeCoverageIgnoreEnd
57
                }
58
59
                return $scope;
60
            }, $oldRefreshToken['scopes']);
61
        } else {
62
            // The OAuth spec says that a refreshed access token can have the original scopes or fewer so ensure
63
            // the request doesn't include any new scopes
64
            foreach ($scopes as $scope) {
65
                if (in_array($scope->getIdentifier(), $oldRefreshToken['scopes']) === false) {
66
                    throw OAuthServerException::invalidScope($scope->getIdentifier());
67
                }
68
            }
69
        }
70
71
        // Expire old tokens
72
        $this->accessTokenRepository->revokeAccessToken($oldRefreshToken['access_token_id']);
73
        $this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_token_id']);
74
75
        // Issue and persist new tokens
76
        $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $oldRefreshToken['user_id'], $scopes);
77
        $refreshToken = $this->issueRefreshToken($accessToken);
78
79
        // Inject tokens into response
80
        $responseType->setAccessToken($accessToken);
81
        $responseType->setRefreshToken($refreshToken);
82
83
        return $responseType;
84
    }
85
86
    /**
87
     * @param \Psr\Http\Message\ServerRequestInterface $request
88
     * @param string                                   $clientId
89
     *
90
     * @throws \League\OAuth2\Server\Exception\OAuthServerException
91
     *
92
     * @return array
93
     */
94
    protected function validateOldRefreshToken(ServerRequestInterface $request, $clientId)
95
    {
96
        $encryptedRefreshToken = $this->getRequestParameter('refresh_token', $request);
97
        if (is_null($encryptedRefreshToken)) {
98
            throw OAuthServerException::invalidRequest('refresh_token');
99
        }
100
101
        // Validate refresh token
102
        try {
103
            $refreshToken = $this->decrypt($encryptedRefreshToken);
104
        } catch (\LogicException $e) {
105
            throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token');
106
        }
107
108
        $refreshTokenData = json_decode($refreshToken, true);
109
        if ($refreshTokenData['client_id'] !== $clientId) {
110
            $this->getEmitter()->emit(new RequestEvent('refresh_token.client.failed', $request));
111
            throw OAuthServerException::invalidRefreshToken('Token is not linked to client');
112
        }
113
114
        if ($refreshTokenData['expire_time'] < time()) {
115
            throw OAuthServerException::invalidRefreshToken('Token has expired');
116
        }
117
118
        if ($this->refreshTokenRepository->isRefreshTokenRevoked($refreshTokenData['refresh_token_id']) === true) {
119
            throw OAuthServerException::invalidRefreshToken('Token has been revoked');
120
        }
121
122
        return $refreshTokenData;
123
    }
124
125
    /**
126
     * {@inheritdoc}
127
     */
128
    public function getIdentifier()
129
    {
130
        return 'refresh_token';
131
    }
132
}
133