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