Passed
Push — analysis-8QZVGP ( deb773 )
by Samuel
06:50 queued 02:26
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\PassportMultiauth;
15
use SMartins\PassportMultiauth\Provider as Token;
16
use Illuminate\Support\Facades\Auth as AuthFacade;
17
use SMartins\PassportMultiauth\ProviderRepository;
18
use SMartins\PassportMultiauth\Guards\GuardChecker;
19
use SMartins\PassportMultiauth\Facades\ServerRequest;
20
use SMartins\PassportMultiauth\Config\AuthConfigHelper;
21
use League\OAuth2\Server\Exception\OAuthServerException;
22
23
class MultiAuthenticate extends Authenticate
24
{
25
    /**
26
     * @var ResourceServer
27
     */
28
    protected $server;
29
30
    /**
31
     * @var ProviderRepository
32
     */
33
    protected $providers;
34
35
    /**
36
     * Create a new middleware instance.
37
     *
38
     * @param ResourceServer $server
39
     * @param ProviderRepository $providers
40
     * @param Auth $auth
41
     */
42 13
    public function __construct(
43
        ResourceServer $server,
44
        ProviderRepository $providers,
45
        Auth $auth
46
    ) {
47 13
        parent::__construct($auth);
48
49 13
        $this->server = $server;
50 13
        $this->providers = $providers;
51 13
    }
52
53
    /**
54
     * Handle an incoming request. Authenticates the guard from access token
55
     * used on request.
56
     *
57
     * @param \Illuminate\Http\Request $request
58
     * @param \Closure $next
59
     * @param string[] ...$guards
60
     * @return mixed
61
     *
62
     * @throws \Illuminate\Auth\AuthenticationException
63
     * @throws \SMartins\PassportMultiauth\Exceptions\MissingConfigException
64
     */
65 13
    public function handle($request, Closure $next, ...$guards)
66
    {
67
        // If don't has any guard follow the flow
68 13
        if (empty($guards)) {
69 2
            $this->authenticate($request, $guards);
70
71
            // Stop laravel from checking for a token if session is not set
72
            return $next($request);
73
        }
74
75 11
        $psrRequest = ServerRequest::createRequest($request);
76
77
        try {
78 11
            $psrRequest = $this->server->validateAuthenticatedRequest($psrRequest);
79
80 8
            if (! ($accessToken = $this->getAccessTokenFromRequest($psrRequest))) {
81 2
                throw new AuthenticationException('Unauthenticated', $guards);
82
            }
83
84 6
            $guard = $this->getTokenGuard($accessToken, $guards);
85
86
            // At this point, the authentication will be done by Laravel Passport default driver.
87 6
            $this->authenticate($request, $guard);
88
89 5
            $guardsModels = $this->getGuardsModels($guards);
90
91
            // The laravel passport will define the logged user on request.
92
            // The returned model can be anywhere, depending on the guard.
93 5
            $user = $request->user();
94
95
            // But we need check if the user logged has the correct guard.
96
            $request->setUserResolver(function ($guard) use ($user, $guardsModels) {
97
                // If don't exists any guard on $request->user() parameter, the
98
                // default user will be returned.
99
                // If has the guard on guards passed on middleware and the model
100
                // instance are the same on an guard.
101 4
                if (! $guard || (isset($guardsModels[$guard]) && $user instanceof $guardsModels[$guard])) {
102 4
                    return $user;
103
                }
104 5
            });
105
106
            // After it, we'll change the passport driver behavior to get the
107
            // authenticated user. It'll change on methods like Auth::user(),
108
            // Auth::guard('company')-user(), Auth::check().
109 5
            AuthFacade::extend(
110 5
                'passport',
111
                function ($app, $name, array $config) use ($request, $guards) {
0 ignored issues
show
Unused Code introduced by
The import $guards is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
112 4
                    $providerGuard = AuthConfigHelper::getProviderGuard($config['provider']);
113
114
                    return tap($this->makeGuard($request, $providerGuard), function ($guard) {
115 4
                        Application::getInstance()->refresh('request', $guard, 'setRequest');
116 4
                    });
117 5
                }
118
            );
119 6
        } catch (OAuthServerException $e) {
120
            // If has an OAuthServerException check if has unit tests and fake
121
            // user authenticated.
122 3
            if (($user = PassportMultiauth::userActing()) &&
123 3
                $this->canBeAuthenticated($user, $guards)
124
            ) {
125
                return $next($request);
126
            }
127
128
            // @todo Check if it's the best way to handle with OAuthServerException
129 3
            throw new AuthenticationException('Unauthenticated', $guards);
130
        }
131
132 5
        return $next($request);
133
    }
134
135
    /**
136
     * @param ServerRequestInterface $request
137
     * @return null|Token
138
     */
139 8
    public function getAccessTokenFromRequest(ServerRequestInterface $request)
140
    {
141 8
        if (! ($tokenId = $request->getAttribute('oauth_access_token_id'))) {
142 1
            return;
143
        }
144
145 7
        return $this->providers->findForToken($tokenId);
146
    }
147
148
    /**
149
     * Check if user acting has the required guards and scopes on request.
150
     *
151
     * @param Authenticatable $user
152
     * @param  array $guards
153
     * @return bool
154
     * @throws \SMartins\PassportMultiauth\Exceptions\MissingConfigException
155
     */
156 2
    public function canBeAuthenticated(Authenticatable $user, $guards)
157
    {
158 2
        $userGuard = AuthConfigHelper::getUserGuard($user);
159
160 2
        return in_array($userGuard, $guards);
161
    }
162
163
    /**
164
     * Get guard related with token.
165
     *
166
     * @param Token $token
167
     * @param $guards
168
     * @return array
169
     */
170 6
    public function getTokenGuard(Token $token, $guards)
171
    {
172 6
        $providers = GuardChecker::getGuardsProviders($guards);
173
174
        // use only guard associated to access token provider
175 6
        return $providers->has($token->provider) ? [$providers->get($token->provider)] : [];
176
    }
177
178
    /**
179
     * @param \Illuminate\Http\Request $request
180
     * @param string $guard
181
     * @return RequestGuard
182
     */
183 4
    private function makeGuard($request, $guard)
184
    {
185
        return new RequestGuard(function ($request) use ($guard) {
186 4
            return $request->user($guard);
187 4
        }, $request);
188
    }
189
190
    /**
191
     * Get models from guards. It'll return an associative array where the keys
192
     * are the guards and the values are the correspondent models.
193
     *
194
     * @param array $guards
195
     * @return array
196
     */
197 5
    private function getGuardsModels(array $guards)
198
    {
199 5
        $guardsModels = [];
200 5
        foreach ($guards as $guard) {
201 5
            $provider = GuardChecker::defaultGuardProvider($guard);
202 5
            $guardsModels[$guard] = AuthConfigHelper::getProviderModel($provider);
203
        }
204
205 5
        return $guardsModels;
206
    }
207
}
208