Server::loadApplication()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2

Importance

Changes 3
Bugs 0 Features 2
Metric Value
c 3
b 0
f 2
dl 0
loc 15
rs 9.4285
ccs 10
cts 10
cp 1
cc 2
eloc 8
nc 2
nop 1
crap 2
1
<?php
2
3
namespace Majora\Component\OAuth\Server;
4
5
use Majora\Component\OAuth\Entity\AccessToken;
6
use Majora\Component\OAuth\Entity\LoginAttempt;
7
use Majora\Component\OAuth\Entity\RefreshToken;
8
use Majora\Component\OAuth\Event\AccessTokenEvent;
9
use Majora\Component\OAuth\Event\AccessTokenEvents;
10
use Majora\Component\OAuth\Exception\InvalidAccessTokenException;
11
use Majora\Component\OAuth\Exception\InvalidGrantException;
12
use Majora\Component\OAuth\Generator\RandomTokenGenerator;
13
use Majora\Component\OAuth\GrantType\GrantExtensionInterface;
14
use Majora\Component\OAuth\Loader\AccessTokenLoaderInterface;
15
use Majora\Component\OAuth\Loader\ApplicationLoaderInterface;
16
use Majora\Component\OAuth\Model\AccessTokenInterface;
17
use Majora\Component\OAuth\Model\AccountInterface;
18
use Majora\Component\OAuth\Model\ApplicationInterface;
19
use Majora\Component\OAuth\Model\RefreshTokenInterface;
20
use Majora\Framework\Model\EntityCollection;
21
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
22
use Symfony\Component\OptionsResolver\OptionsResolver;
23
24
/**
25
 * OAuth server main class.
26
 */
27
class Server
28
{
29
    /**
30
     * @var EntityCollection
31
     */
32
    protected $grantExtensions;
33
34
    /**
35
     * @var ApplicationLoaderInterface
36
     */
37
    protected $applicationLoader;
38
39
    /**
40
     * @var AccessTokenLoaderInterface
41
     */
42
    protected $accessTokenLoader;
43
44
    /**
45
     * @var EventDispatcherInterface
46
     */
47
    protected $eventDispatcher;
48
49
    /**
50
     * @var RandomTokenGenerator
51
     */
52
    protected $randomTokenGenerator;
53
54
    /**
55
     * @var array
56
     */
57
    protected $tokenOptions;
58
59
    /**
60
     * Construct.
61
     *
62
     * @param EventDispatcherInterface   $eventDispatcher
63
     * @param ApplicationLoaderInterface $applicationLoader
64
     * @param AccessTokenLoaderInterface $accessTokenLoader
65
     * @param RandomTokenGenerator       $randomTokenGenerator
66
     * @param array                      $tokenOptions
67
     * @param array                      $grantExtensions
68
     */
69 18
    public function __construct(
70
        EventDispatcherInterface $eventDispatcher,
71
        ApplicationLoaderInterface $applicationLoader,
72
        AccessTokenLoaderInterface $accessTokenLoader,
73
        RandomTokenGenerator $randomTokenGenerator,
74
        array $tokenOptions,
75
        array $grantExtensions = array()
76
    ) {
77 18
        $this->applicationLoader = $applicationLoader;
78 18
        $this->accessTokenLoader = $accessTokenLoader;
79 18
        $this->eventDispatcher = $eventDispatcher;
80 18
        $this->randomTokenGenerator = $randomTokenGenerator;
81
82 18
        $tokenOptionsResolver = new OptionsResolver();
83 18
        $tokenOptionsResolver->setDefaults(array(
84 18
            'access_token_class' => AccessToken::class,
85 18
            'access_token_ttl' => AccessTokenInterface::DEFAULT_TTL,
86 9
            'refresh_token_class' => RefreshToken::class,
87 18
            'refresh_token_ttl' => RefreshTokenInterface::DEFAULT_TTL,
88 9
        ));
89
90 18
        $this->tokenOptions = $tokenOptionsResolver->resolve($tokenOptions);
91
92 18
        $this->grantExtensions = new EntityCollection();
93 18
        foreach ($grantExtensions as $grantType => $extension) {
94 18
            $this->registerGrantExtension($grantType, $extension);
95 9
        }
96 18
    }
97
98
    /**
99
     * Register an extension under given grant type.
100
     *
101
     * @param string                  $grantType
102
     * @param GrantExtensionInterface $extension
103
     */
104 18
    public function registerGrantExtension($grantType, GrantExtensionInterface $extension)
105
    {
106 18
        $this->grantExtensions->set($grantType, $extension);
107 18
    }
108
109
    /**
110
     * Validate given request parameters and build a
111
     * LoginAttempt object with it.
112
     *
113
     * @param array $data
114
     * @param array $headers
115
     * @param array $query
116
     *
117
     * @return LoginAttempt
118
     */
119 18
    protected function createLoginAttempt(array $data, array $headers, array $query)
120
    {
121
        // validate grant_type manually (needed to guess specialized option resolver)
122 18
        if (empty($data['grant_type'])) {
123 2
            throw new \InvalidArgumentException('Any grant_type given.');
124
        }
125 16
        $grantType = $data['grant_type'];
126 16
        if (!$this->grantExtensions->containsKey($grantType)) {
127 2
            throw new \InvalidArgumentException('Given grant_type is invalid.');
128
        }
129
130
        // create option resolver
131 14
        $requestResolver = new OptionsResolver();
132 14
        $requestResolver->setRequired(array(
133 14
            'client_secret',
134 7
            'client_api_key',
135 7
            'grant_type',
136 7
        ));
137 14
        $this->grantExtensions->get($grantType)
138 14
            ->configureRequestParameters($requestResolver)
139
        ;
140
141 14
        return new LoginAttempt(
142 7
            $query,
143 14
            $requestResolver->resolve($data),
144
            $headers
145 7
        );
146
    }
147
148
    /**
149
     * Loads application for given login attempt.
150
     *
151
     * @param LoginAttempt $loginAttempt
152
     *
153
     * @return ApplicationInterface
154
     *
155
     * @throws InvalidGrantException
156
     */
157 14
    protected function loadApplication(LoginAttempt $loginAttempt)
158
    {
159
        // retrieve Application
160 14
        if (!$application = $this->applicationLoader->retrieveByApiKeyAndSecret(
161 14
            $loginAttempt->getData('client_api_key'),
162 14
            $loginAttempt->getData('client_secret')
163 7
        )) {
164 2
            throw new InvalidGrantException(
165 1
                $loginAttempt,
166 1
                'Any application found for given api_key / secret.'
167 1
            );
168
        }
169
170 12
        return $application;
171
    }
172
173
    /**
174
     * Runs grant extension to load accounts.
175
     *
176
     * @param ApplicationInterface $application
177
     * @param LoginAttempt         $loginAttempt
178
     *
179
     * @return AccountInterface
180
     *
181
     * @throws \InvalidArgumentException
182
     * @throws UnknownGrantTypeException
183
     */
184 12
    protected function loadAccount(
185
        ApplicationInterface $application,
186
        LoginAttempt $loginAttempt
187
    ) {
188
        // run grant extension result
189 12
        return $this->grantExtensions
190 12
            ->get($loginAttempt->getData('grant_type'))
191 12
            ->grant($application, $loginAttempt)
192 6
        ;
193
    }
194
195
    /**
196
     * Grant given credentials, or throws an exception if invalid
197
     * credentials for application or account.
198
     *
199
     * @param array $data    login request data
200
     * @param array $headers optionnal login request headers
201
     * @param array $query   optionnal login request query
202
     *
203
     * @return AccessTokenInterface
204
     */
205 18
    public function grant(array $data, array $headers = array(), array $query = array())
206
    {
207
        // create and validate login attempt from given data
208 18
        $loginAttempt = $this->createLoginAttempt(
209 9
            $data, $headers, $query
210 9
        );
211
212
        // load application / account
213 14
        $account = $this->loadAccount(
214 14
            $application = $this->loadApplication($loginAttempt),
215
            $loginAttempt
216 6
        );
217
218
        // event call
219 12
        $this->eventDispatcher->dispatch(
220 12
            AccessTokenEvents::MAJORA_ACCESS_TOKEN_CREATED,
221 12
            new AccessTokenEvent(
222
223
                // access token generation
224 12
                $accessToken = new $this->tokenOptions['access_token_class'](
225 6
                    $application,
226 6
                    $account,
227 12
                    $this->tokenOptions['access_token_ttl'],
228 12
                    null, // for now, we let the expiration date calculate itself
229 12
                    $this->randomTokenGenerator->generate('access_token'),
230
231
                    // refresh token generation only if necessary
232 12
                    in_array('refresh_token', $application->getAllowedGrantTypes())
233 12
                    && $this->grantExtensions->containsKey('refresh_token') ?
234 9
                        new $this->tokenOptions['refresh_token_class'](
235 3
                            $application,
236 3
                            $account,
237 6
                            $this->tokenOptions['refresh_token_ttl'],
238 6
                            null, // same
239 6
                            $this->randomTokenGenerator->generate('refresh_token')
240 3
                        ) :
241 6
                        null
242 6
                )
243 6
            )
244 6
        );
245
246 12
        return $accessToken;
247
    }
248
249
    /**
250
     * Tests if given hash match a valid AccessToken.
251
     *
252
     * @param string $hash
253
     *
254
     * @return AccessToken
255
     *
256
     * @throws InvalidAccessTokenException
257
     */
258
    public function check($hash)
259
    {
260
        if (!$accessToken = $this->accessTokenLoader->retrieveByHash($hash)) {
261
            throw new InvalidAccessTokenException('Given access token is wrong or expired.');
262
        }
263
264
        return $accessToken;
265
    }
266
}
267