Completed
Pull Request — develop (#11)
by Quentin
05:08
created

Server::__construct()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 26
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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