Completed
Pull Request — 4.0 (#72)
by Samuel
07:40 queued 04:55
created

MultiAuthenticate   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 185
Duplicated Lines 0 %

Test Coverage

Coverage 96.77%

Importance

Changes 0
Metric Value
eloc 50
dl 0
loc 185
ccs 30
cts 31
cp 0.9677
rs 10
c 0
b 0
f 0
wmc 18

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A getGuardsModels() 0 9 2
A canBeAuthenticated() 0 5 1
A getTokenGuard() 0 6 2
A makeGuard() 0 5 1
B handle() 0 70 9
A getAccessTokenFromRequest() 0 7 2
1
<?php
2
3
namespace SMartins\PassportMultiauth\Http\Middleware;
4
5
use Closure;
6
use Illuminate\Auth\RequestGuard;
7
use Illuminate\Foundation\Application;
8
use League\OAuth2\Server\ResourceServer;
9
use Illuminate\Auth\AuthenticationException;
10
use Illuminate\Auth\Middleware\Authenticate;
11
use Psr\Http\Message\ServerRequestInterface;
12
use Illuminate\Contracts\Auth\Authenticatable;
13
use Illuminate\Contracts\Auth\Factory as Auth;
14
use SMartins\PassportMultiauth\Auth\AuthManager;
15
use SMartins\PassportMultiauth\PassportMultiauth;
16
use SMartins\PassportMultiauth\Provider as Token;
17
use Illuminate\Support\Facades\Auth as AuthFacade;
18
use SMartins\PassportMultiauth\ProviderRepository;
19
use SMartins\PassportMultiauth\Guards\GuardChecker;
20
use SMartins\PassportMultiauth\Facades\ServerRequest;
21
use SMartins\PassportMultiauth\Config\AuthConfigHelper;
22
use League\OAuth2\Server\Exception\OAuthServerException;
23
24
class MultiAuthenticate extends Authenticate
25
{
26
    /**
27
     * @var ResourceServer
28
     */
29
    protected $server;
30
31
    /**
32
     * @var ProviderRepository
33
     */
34
    protected $providers;
35
36
    /**
37
     * Create a new middleware instance.
38
     *
39 13
     * @param ResourceServer $server
40
     * @param ProviderRepository $providers
41
     * @param Auth $auth
42
     */
43
    public function __construct(
44 13
        ResourceServer $server,
45
        ProviderRepository $providers,
46 13
        Auth $auth
47 13
    ) {
48 13
        parent::__construct($auth);
49
50
        $this->server = $server;
51
        $this->providers = $providers;
52
    }
53
54
    /**
55
     * Handle an incoming request. Authenticates the guard from access token
56
     * used on request.
57
     *
58
     * @param \Illuminate\Http\Request $request
59
     * @param \Closure $next
60
     * @param string[] ...$guards
61
     * @return mixed
62 13
     *
63
     * @throws \Illuminate\Auth\AuthenticationException
64
     * @throws \SMartins\PassportMultiauth\Exceptions\MissingConfigException
65 13
     */
66 2
    public function handle($request, Closure $next, ...$guards)
67
    {
68
        // If don't has any guard follow the flow
69
        if (empty($guards)) {
70
            $this->authenticate($request, $guards);
71
72 11
            // Stop laravel from checking for a token if session is not set
73
            return $next($request);
74
        }
75 11
76
        $psrRequest = ServerRequest::createRequest($request);
77 4
78 2
        try {
79
            $psrRequest = $this->server->validateAuthenticatedRequest($psrRequest);
80
81 2
            if (! ($accessToken = $this->getAccessTokenFromRequest($psrRequest))) {
82
                throw new AuthenticationException('Unauthenticated', $guards);
83 2
            }
84 10
85
            $guard = $this->getTokenGuard($accessToken, $guards);
86
87 7
            // At this point, the authentication will be done by Laravel Passport default driver.
88 7
            $this->authenticate($request, $guard);
89
90 4
            $guardsModels = $this->getGuardsModels($guards);
91
92
            // The laravel passport will define the logged user on request.
93
            // The returned model can be anywhere, depending on the guard.
94 3
            $user = $request->user();
95
96
            // But we need check if the user logged has the correct guard.
97 1
            $request->setUserResolver(function ($guard) use ($user, $guardsModels) {
98
                // If don't exists any guard on $request->user() parameter, the
99
                // default user will be returned.
100
                // If has the guard on guards passed on middleware and the model
101
                // instance are the same on an guard.
102
                if (! $guard || (isset($guardsModels[$guard]) && $user instanceof $guardsModels[$guard])) {
103
                    return $user;
104 4
                }
105
106 4
                return null;
107 1
            });
108
109
            // After it, we'll change the passport driver behavior to get the
110 3
            // authenticated user. It'll change on methods like Auth::user(),
111
            // Auth::guard('company')->user(), Auth::check().
112
            AuthFacade::extend(
113
                'passport',
114
                function ($app, $name, array $config) use ($request) {
115
                    $providerGuard = AuthConfigHelper::getProviderGuard($config['provider']);
116
                    return tap($this->makeGuard($request, $providerGuard), function ($guard) {
117
                        Application::getInstance()->refresh('request', $guard, 'setRequest');
118
                    });
119
                }
120
            );
121 6
            AuthFacade::clearGuardsCache();
122
        } catch (OAuthServerException $e) {
123 6
            // If has an OAuthServerException check if has unit tests and fake
124
            // user authenticated.
125 6
            if (($user = PassportMultiauth::userActing()) &&
126
                $this->canBeAuthenticated($user, $guards)
127
            ) {
128
                return $next($request);
129
            }
130
131
            // @todo Check if it's the best way to handle with OAuthServerException
132
            throw new AuthenticationException('Unauthenticated', $guards);
133
        }
134
135 2
        return $next($request);
136
    }
137 2
138
    /**
139
     * @param ServerRequestInterface $request
140 2
     * @return null|Token
141
     */
142
    public function getAccessTokenFromRequest(ServerRequestInterface $request)
143
    {
144
        if (! ($tokenId = $request->getAttribute('oauth_access_token_id'))) {
145
            return;
146
        }
147
148
        return $this->providers->findForToken($tokenId);
149
    }
150
151
    /**
152
     * Check if user acting has the required guards and scopes on request.
153
     *
154
     * @param Authenticatable $user
155
     * @param  array $guards
156
     * @return bool
157
     * @throws \SMartins\PassportMultiauth\Exceptions\MissingConfigException
158
     */
159
    public function canBeAuthenticated(Authenticatable $user, $guards)
160
    {
161
        $userGuard = AuthConfigHelper::getUserGuard($user);
162
163
        return in_array($userGuard, $guards);
164
    }
165
166
    /**
167
     * Get guard related with token.
168
     *
169
     * @param Token $token
170
     * @param $guards
171
     * @return array
172
     */
173
    public function getTokenGuard(Token $token, $guards)
174
    {
175
        $providers = GuardChecker::getGuardsProviders($guards);
176
177
        // use only guard associated to access token provider
178
        return $providers->has($token->provider) ? [$providers->get($token->provider)] : [];
179
    }
180
181
    /**
182
     * @param \Illuminate\Http\Request $request
183
     * @param string $guard
184
     * @return RequestGuard
185
     */
186
    private function makeGuard($request, $guard)
187
    {
188
        return new RequestGuard(function ($request) use ($guard) {
189
            return $request->user($guard);
190
        }, $request);
191
    }
192
193
    /**
194
     * Get models from guards. It'll return an associative array where the keys
195
     * are the guards and the values are the correspondent models.
196
     *
197
     * @param array $guards
198
     * @return array
199
     */
200
    private function getGuardsModels(array $guards)
201
    {
202
        $guardsModels = [];
203
        foreach ($guards as $guard) {
204
            $provider = GuardChecker::defaultGuardProvider($guard);
205
            $guardsModels[$guard] = AuthConfigHelper::getProviderModel($provider);
206
        }
207
208
        return $guardsModels;
209
    }
210
}
211