Completed
Pull Request — master (#101)
by
unknown
01:21
created

ImpersonateManager::getCurrentAuthGuardName()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 0
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
1
<?php
2
3
namespace Lab404\Impersonate\Services;
4
5
use Exception;
6
use Illuminate\Database\Eloquent\ModelNotFoundException;
7
use Illuminate\Foundation\Application;
8
use Lab404\Impersonate\Events\LeaveImpersonation;
9
use Lab404\Impersonate\Events\TakeImpersonation;
10
11
class ImpersonateManager
12
{
13
    const REMEMBER_PREFIX = 'remember_web';
14
15
    /** @var Application $app */
16
    private $app;
17
    /**
18
     * Authentication manager
19
     * @var
20
     */
21
    private $auth;
22
    /** @var string $token */
23
    private $token;
24
25
    public function __construct(Application $app)
26
    {
27
        $this->app = $app;
28
        $this->auth = $app['auth'];
29
    }
30
31
    /**
32
     * @param int $id
33
     * @return \Illuminate\Contracts\Auth\Authenticatable
34
     * @throws Exception
35
     */
36
    public function findUserById($id, $guardName = null)
37
    {
38
        if (empty($guardName)) {
39
            $guardName = $this->app['config']->get('auth.default.guard', 'web');
40
        }
41
42
        $providerName = $this->app['config']->get("auth.guards.$guardName.provider");
43
        $userProvider = $this->auth->createUserProvider($providerName);
44
45
        if (!($modelInstance = $userProvider->retrieveById($id))) {
46
            $model = $this->app['config']->get("auth.providers.$providerName.model");
47
48
            throw (new ModelNotFoundException())->setModel(
49
                $model,
50
                $id
51
            );
52
        }
53
54
        return $modelInstance;
55
    }
56
57
    public function isImpersonating(): bool
58
    {
59
        return !empty($this->getImpersonatorId());
60
    }
61
62
    /**
63
     * @return  int|null
64
     */
65
    public function getImpersonatorId()
66
    {
67
        return $this->auth->guard($this->getDefaultSessionGuard())->parseToken()->getPayLoad()
68
            ->get($this->getSessionKey());
69
    }
70
71
    /**
72
     * @return \Illuminate\Contracts\Auth\Authenticatable
73
     */
74
    public function getImpersonator()
75
    {
76
        $id = $this->getImpersonatorId();
77
        $guard = $this->getImpersonatorGuardName();
78
79
        return is_null($id) ? null : $this->findUserById($id, $guard);
80
    }
81
82
    /**
83
     * @return string|null
84
     */
85
    public function getImpersonatorGuardName()
86
    {
87
        return $this->auth->guard($this->getDefaultSessionGuard())->parseToken()->getPayLoad()
88
            ->get($this->getSessionGuard());
89
    }
90
91
    /**
92
     * @return string|null
93
     */
94
    public function getImpersonatorGuardUsingName()
95
    {
96
        return $this->auth->guard($this->getDefaultSessionGuard())->parseToken()->getPayLoad()
97
            ->get($this->getSessionGuardUsing());
98
    }
99
100
    /**
101
     * @param \Illuminate\Contracts\Auth\Authenticatable $from
102
     * @param \Illuminate\Contracts\Auth\Authenticatable $to
103
     * @param string|null                         $guardName
104
     * @return bool
105
     */
106
    public function take($from, $to, $guardName = null)
107
    {
108
        try {
109
            $currentGuard = $this->getCurrentAuthGuardName();
110
            $this->auth->guard($guardName)->customClaims([
111
                $this->getSessionKey() => $from->getKey(),
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Illuminate\Contracts\Auth\Authenticatable as the method getKey() does only exist in the following implementations of said interface: Illuminate\Foundation\Auth\User.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
112
                $this->getSessionGuard() => $currentGuard,
113
                $this->getSessionGuardUsing() => $guardName,
114
                static::REMEMBER_PREFIX => $this->saveAuthCookies(),
115
            ]);
116
117
            $this->token = $this->auth->guard($guardName)->login($to);
118
            $this->auth->guard($guardName)->setToken($this->token);
119
        } catch (\Exception $e) {
120
            unset($e);
121
            return false;
122
        }
123
124
        $this->app['events']->dispatch(new TakeImpersonation($from, $to));
125
126
        return true;
127
    }
128
129
    public function leave(): bool
130
    {
131
        try {
132
            $impersonated = $this->auth->guard($this->getImpersonatorGuardUsingName())->user();
133
            $impersonator = $this->findUserById($this->getImpersonatorId(), $this->getImpersonatorGuardName());
134
135
            $this->auth->guard($this->getCurrentAuthGuardName())->quietLogout();
136
137
            $this->extractAuthCookies();
138
139
            $this->clear();
140
141
        } catch (\Exception $e) {
142
            unset($e);
143
            return false;
144
        }
145
146
        $this->app['events']->dispatch(new LeaveImpersonation($impersonator, $impersonated));
147
148
        return true;
149
    }
150
151
    public function clear()
152
    {
153
        $this->auth->guard($this->getDefaultSessionGuard())->customClaims([
154
            $this->getSessionKey() => null,
155
            $this->getSessionGuard() => null,
156
            $this->getSessionGuardUsing() => null,
157
            static::REMEMBER_PREFIX => null
158
        ]);
159
    }
160
161
    public function getSessionKey(): string
162
    {
163
        return config('laravel-impersonate.session_key');
164
    }
165
166
    public function getSessionGuard(): string
167
    {
168
        return config('laravel-impersonate.session_guard');
169
    }
170
171
    public function getSessionGuardUsing(): string
172
    {
173
        return config('laravel-impersonate.session_guard_using');
174
    }
175
176
    public function getDefaultSessionGuard(): string
177
    {
178
        return config('laravel-impersonate.default_impersonator_guard');
179
    }
180
181
    public function getTakeRedirectTo(): string
182
    {
183
        try {
184
            $uri = route(config('laravel-impersonate.take_redirect_to'), ['token' => $this->token]);
185
        } catch (\InvalidArgumentException $e) {
186
            $uri = config('laravel-impersonate.take_redirect_to') . '?' . http_build_query(['token' => $this->token]);
187
        }
188
189
        return $uri;
190
    }
191
192
    public function getLeaveRedirectTo(): string
193
    {
194
        try {
195
            $uri = route(config('laravel-impersonate.leave_redirect_to'));
196
        } catch (\InvalidArgumentException $e) {
197
            $uri = config('laravel-impersonate.leave_redirect_to');
198
        }
199
200
        return $uri;
201
    }
202
203
    /**
204
     * @return array|null
205
     */
206
    public function getCurrentAuthGuardName()
207
    {
208
        $guards = array_keys(config('auth.guards'));
209
210
        foreach ($guards as $guard) {
211
            if ($this->auth->guard($guard)->check()) {
212
                return $guard;
213
            }
214
        }
215
216
        return null;
217
    }
218
219
    protected function saveAuthCookies(): array
220
    {
221
        $cookie = $this->findByKeyInArray($this->app['request']->cookies->all(), static::REMEMBER_PREFIX);
222
        $key = $cookie->keys()->first();
223
        $val = $cookie->values()->first();
224
225
        if (!$key || !$val) {
226
            return [];
227
        }
228
229
        return [$key, $val];
230
    }
231
232
    protected function extractAuthCookies(): void
233
    {
234
        if (!$session = $this->auth->guard($this->getDefaultSessionGuard())->parseToken()->getPayLoad()
235
            ->get(static::REMEMBER_PREFIX)
236
        ) {
237
            return;
238
        }
239
240
        $this->app['cookie']->queue($session[0], $session[1]);
241
        session()->forget($session);
242
    }
243
244
    /**
245
     * @param array $values
246
     * @param string $search
247
     * @return \Illuminate\Support\Collection
248
     */
249
    protected function findByKeyInArray(array $values, string $search)
250
    {
251
        return collect($values ?? session()->all())
252
            ->filter(function ($val, $key) use ($search) {
253
                return strpos($key, $search) !== false;
254
            });
255
    }
256
}
257