Passed
Push — 5.0 ( b624c7...101f25 )
by Samuel
03:13
created

MultiAuthenticate::canBeAuthenticated()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
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
     * @param ResourceServer $server
40
     * @param ProviderRepository $providers
41
     * @param Auth $auth
42
     */
43 15
    public function __construct(
44
        ResourceServer $server,
45
        ProviderRepository $providers,
46
        Auth $auth
47
    ) {
48 15
        parent::__construct($auth);
49
50 15
        $this->server = $server;
51 15
        $this->providers = $providers;
52 15
    }
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
     *
63
     * @throws \Illuminate\Auth\AuthenticationException
64
     * @throws \SMartins\PassportMultiauth\Exceptions\MissingConfigException
65
     */
66 15
    public function handle($request, Closure $next, ...$guards)
67
    {
68
        // If don't has any guard follow the flow
69 15
        if (empty($guards)) {
70 2
            $this->authenticate($request, $guards);
71
72
            // Stop laravel from checking for a token if session is not set
73
            return $next($request);
74
        }
75
76 13
        $psrRequest = ServerRequest::createRequest($request);
77
78
        try {
79 13
            $psrRequest = $this->server->validateAuthenticatedRequest($psrRequest);
80
81 10
            if (! ($accessToken = $this->getAccessTokenFromRequest($psrRequest))) {
82 2
                throw new AuthenticationException('Unauthenticated', $guards);
83
            }
84
85 8
            $guard = $this->getTokenGuard($accessToken, $guards);
86
87 8
            if (empty($guard)) {
88 3
                throw new AuthenticationException('Unauthenticated', $guards);
89
            }
90
91
            // At this point, the authentication will be done by Laravel Passport default driver.
92 5
            $this->authenticate($request, $guard);
93
94 5
            $guardsModels = $this->getGuardsModels($guards);
95
96
            // The laravel passport will define the logged user on request.
97
            // The returned model can be anywhere, depending on the guard.
98 5
            $user = $request->user();
99
100
            // But we need check if the user logged has the correct guard.
101
            $request->setUserResolver(function ($guard) use ($user, $guardsModels) {
102
                // If don't exists any guard on $request->user() parameter, the
103
                // default user will be returned.
104
                // If has the guard on guards passed on middleware and the model
105
                // instance are the same on an guard.
106 4
                if (! $guard || (isset($guardsModels[$guard]) && $user instanceof $guardsModels[$guard])) {
107 4
                    return $user;
108
                }
109
110 4
                return null;
111 5
            });
112
113
            // After it, we'll change the passport driver behavior to get the
114
            // authenticated user. It'll change on methods like Auth::user(),
115
            // Auth::guard('company')->user(), Auth::check().
116 5
            AuthFacade::extend(
117 5
                'passport',
118
                function ($app, $name, array $config) use ($request) {
119 4
                    $providerGuard = AuthConfigHelper::getProviderGuard($config['provider']);
120
                    return tap($this->makeGuard($request, $providerGuard), function ($guard) {
121 4
                        Application::getInstance()->refresh('request', $guard, 'setRequest');
122 4
                    });
123 5
                }
124
            );
125 5
            AuthFacade::clearGuardsCache();
126 8
        } catch (OAuthServerException $e) {
127
            // If has an OAuthServerException check if has unit tests and fake
128
            // user authenticated.
129 3
            if (($user = PassportMultiauth::userActing()) &&
130 3
                $this->canBeAuthenticated($user, $guards)
131
            ) {
132
                return $next($request);
133
            }
134
135
            // @todo Check if it's the best way to handle with OAuthServerException
136 3
            throw new AuthenticationException('Unauthenticated', $guards);
137
        }
138
139 5
        return $next($request);
140
    }
141
142
    /**
143
     * @param ServerRequestInterface $request
144
     * @return null|Token
145
     */
146 10
    public function getAccessTokenFromRequest(ServerRequestInterface $request)
147
    {
148 10
        if (! ($tokenId = $request->getAttribute('oauth_access_token_id'))) {
149 1
            return;
150
        }
151
152 9
        return $this->providers->findForToken($tokenId);
153
    }
154
155
    /**
156
     * Check if user acting has the required guards and scopes on request.
157
     *
158
     * @param Authenticatable $user
159
     * @param  array $guards
160
     * @return bool
161
     * @throws \SMartins\PassportMultiauth\Exceptions\MissingConfigException
162
     */
163 2
    public function canBeAuthenticated(Authenticatable $user, $guards)
164
    {
165 2
        $userGuard = AuthConfigHelper::getUserGuard($user);
166
167 2
        return in_array($userGuard, $guards);
168
    }
169
170
    /**
171
     * Get guard related with token.
172
     *
173
     * @param Token $token
174
     * @param $guards
175
     * @return array
176
     */
177 8
    public function getTokenGuard(Token $token, $guards)
178
    {
179 8
        $providers = GuardChecker::getGuardsProviders($guards);
180
181
        // use only guard associated to access token provider
182 8
        return $providers->has($token->provider) ? [$providers->get($token->provider)] : [];
183
    }
184
185
    /**
186
     * @param \Illuminate\Http\Request $request
187
     * @param string $guard
188
     * @return RequestGuard
189
     */
190 4
    private function makeGuard($request, $guard)
191
    {
192
        return new RequestGuard(function ($request) use ($guard) {
193 4
            return $request->user($guard);
194 4
        }, $request);
195
    }
196
197
    /**
198
     * Get models from guards. It'll return an associative array where the keys
199
     * are the guards and the values are the correspondent models.
200
     *
201
     * @param array $guards
202
     * @return array
203
     */
204 5
    private function getGuardsModels(array $guards)
205
    {
206 5
        $guardsModels = [];
207 5
        foreach ($guards as $guard) {
208 5
            $provider = GuardChecker::defaultGuardProvider($guard);
209 5
            $guardsModels[$guard] = AuthConfigHelper::getProviderModel($provider);
210
        }
211
212 5
        return $guardsModels;
213
    }
214
}
215