404labfr /
laravel-auth-checker
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | |||
| 3 | namespace Lab404\AuthChecker\Services; |
||
| 4 | |||
| 5 | use Carbon\Carbon; |
||
| 6 | use Illuminate\Config\Repository as Config; |
||
| 7 | use Illuminate\Database\Eloquent\Builder; |
||
| 8 | use Illuminate\Foundation\Application; |
||
| 9 | use Illuminate\Http\Request; |
||
| 10 | use Illuminate\Support\Collection; |
||
| 11 | use Jenssegers\Agent\Agent; |
||
| 12 | use Lab404\AuthChecker\Events\DeviceCreated; |
||
| 13 | use Lab404\AuthChecker\Events\FailedAuth; |
||
| 14 | use Lab404\AuthChecker\Events\LockoutAuth; |
||
| 15 | use Lab404\AuthChecker\Events\LoginCreated; |
||
| 16 | use Lab404\AuthChecker\Interfaces\HasLoginsAndDevicesInterface; |
||
| 17 | use Lab404\AuthChecker\Models\Device; |
||
| 18 | use Lab404\AuthChecker\Models\Login; |
||
| 19 | |||
| 20 | class AuthChecker |
||
| 21 | { |
||
| 22 | /** @var Application $app */ |
||
| 23 | private $app; |
||
| 24 | /** @var Request $request */ |
||
| 25 | private $request; |
||
| 26 | /** @var Config $config */ |
||
| 27 | private $config; |
||
| 28 | |||
| 29 | public function __construct(Application $app, Request $request) |
||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
| 30 | { |
||
| 31 | $this->app = $app; |
||
| 32 | $this->request = $request; |
||
| 33 | $this->config = $app['config']; |
||
| 34 | } |
||
| 35 | |||
| 36 | public function handleLogin(HasLoginsAndDevicesInterface $user): void |
||
| 37 | { |
||
| 38 | $device = $this->findOrCreateUserDeviceByAgent($user); |
||
| 39 | |||
| 40 | if ($this->shouldLogDeviceLogin($device)) { |
||
| 41 | $this->createUserLoginForDevice($user, $device); |
||
| 42 | } |
||
| 43 | } |
||
| 44 | |||
| 45 | public function handleFailed(HasLoginsAndDevicesInterface $user): void |
||
| 46 | { |
||
| 47 | $device = $this->findOrCreateUserDeviceByAgent($user); |
||
| 48 | $this->createUserLoginForDevice($user, $device, Login::TYPE_FAILED); |
||
| 49 | |||
| 50 | event(new FailedAuth($device->login, $device)); |
||
| 51 | } |
||
| 52 | |||
| 53 | public function handleLockout(array $payload = []): void |
||
| 54 | { |
||
| 55 | $payload = Collection::make($payload); |
||
| 56 | |||
| 57 | $user = $this->findUserFromPayload($payload); |
||
| 58 | |||
| 59 | if ($user) { |
||
| 60 | $device = $this->findOrCreateUserDeviceByAgent($user); |
||
| 61 | $this->createUserLoginForDevice($user, $device, Login::TYPE_LOCKOUT); |
||
| 62 | |||
| 63 | event(new LockoutAuth($device->login, $device)); |
||
| 64 | } |
||
| 65 | } |
||
| 66 | |||
| 67 | public function findOrCreateUserDeviceByAgent(HasLoginsAndDevicesInterface $user, Agent $agent = null): Device |
||
| 68 | { |
||
| 69 | $agent = is_null($agent) ? $this->app['agent'] : $agent; |
||
| 70 | $device = $this->findUserDeviceByAgent($user, $agent); |
||
| 71 | |||
| 72 | if (is_null($device)) { |
||
| 73 | $device = $this->createUserDeviceByAgent($user, $agent); |
||
| 74 | } |
||
| 75 | |||
| 76 | return $device; |
||
| 77 | } |
||
| 78 | |||
| 79 | public function findUserDeviceByAgent(HasLoginsAndDevicesInterface $user, Agent $agent): ?Device |
||
| 80 | { |
||
| 81 | if (!$user->hasDevices()) { |
||
| 82 | return null; |
||
| 83 | } |
||
| 84 | |||
| 85 | $matching = $user->devices->filter(function ($item) use ($agent) { |
||
|
0 ignored issues
–
show
Accessing
devices on the interface Lab404\AuthChecker\Inter...ginsAndDevicesInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
If you access a property on an interface, you most likely code against a concrete implementation of the interface. Available Fixes
Loading history...
|
|||
| 86 | return $this->deviceMatch($item, $agent); |
||
| 87 | })->first(); |
||
| 88 | |||
| 89 | return $matching ? $matching : null; |
||
| 90 | } |
||
| 91 | |||
| 92 | public function createUserDeviceByAgent(HasLoginsAndDevicesInterface $user, Agent $agent): Device |
||
| 93 | { |
||
| 94 | $model = config('auth-checker.models.device') ?? Device::class; |
||
| 95 | $device = new $model; |
||
| 96 | |||
| 97 | $device->platform = $agent->platform(); |
||
| 98 | $device->platform_version = $agent->version($device->platform); |
||
| 99 | $device->browser = $agent->browser(); |
||
| 100 | $device->browser_version = $agent->version($device->browser); |
||
| 101 | $device->is_desktop = $agent->isDesktop() ? true : false; |
||
| 102 | $device->is_mobile = $agent->isMobile() ? true : false; |
||
| 103 | $device->language = count($agent->languages()) ? $agent->languages()[0] : null; |
||
| 104 | |||
| 105 | $device->user()->associate($user); |
||
| 106 | |||
| 107 | $device->save(); |
||
| 108 | |||
| 109 | event(new DeviceCreated($device)); |
||
| 110 | |||
| 111 | return $device; |
||
| 112 | } |
||
| 113 | |||
| 114 | public function findUserFromPayload(Collection $payload): ?HasLoginsAndDevicesInterface |
||
| 115 | { |
||
| 116 | $login_column = $this->getLoginColumnConfig(); |
||
| 117 | |||
| 118 | if ($payload->has($login_column)) { |
||
| 119 | $model = (string)$this->config->get('auth.providers.users.model'); |
||
| 120 | $login_value = $payload->get($login_column); |
||
| 121 | |||
| 122 | /** @var Builder $model */ |
||
| 123 | $user = $model::where($login_column, '=', $login_value)->first(); |
||
| 124 | return $user; |
||
| 125 | } |
||
| 126 | |||
| 127 | return null; |
||
| 128 | } |
||
| 129 | |||
| 130 | public function createUserLoginForDevice( |
||
| 131 | HasLoginsAndDevicesInterface $user, |
||
| 132 | Device $device, |
||
| 133 | string $type = Login::TYPE_LOGIN |
||
| 134 | ): Login { |
||
| 135 | $model = config('auth-checker.models.login') ?? Login::class; |
||
| 136 | $ip = $this->request->ip(); |
||
| 137 | |||
| 138 | $login = new $model([ |
||
| 139 | 'ip_address' => $ip, |
||
| 140 | 'device_id' => $device->id, |
||
| 141 | 'type' => $type, |
||
| 142 | ]); |
||
| 143 | |||
| 144 | $login->user()->associate($user); |
||
| 145 | |||
| 146 | $device->login()->save($login); |
||
| 147 | |||
| 148 | event(new LoginCreated($login)); |
||
| 149 | |||
| 150 | return $login; |
||
| 151 | } |
||
| 152 | |||
| 153 | public function findDeviceForUser(HasLoginsAndDevicesInterface $user, Agent $agent): ?Device |
||
| 154 | { |
||
| 155 | if (!$user->hasDevices()) { |
||
| 156 | return false; |
||
| 157 | } |
||
| 158 | |||
| 159 | $device = $user->devices->filter(function ($item) use ($agent) { |
||
|
0 ignored issues
–
show
Accessing
devices on the interface Lab404\AuthChecker\Inter...ginsAndDevicesInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
If you access a property on an interface, you most likely code against a concrete implementation of the interface. Available Fixes
Loading history...
|
|||
| 160 | return $this->deviceMatch($item, $agent); |
||
| 161 | })->first(); |
||
| 162 | |||
| 163 | return is_null($device) ? false : $device; |
||
| 164 | } |
||
| 165 | |||
| 166 | public function shouldLogDeviceLogin(Device $device): bool |
||
| 167 | { |
||
| 168 | $throttle = $this->getLoginThrottleConfig(); |
||
| 169 | |||
| 170 | if ($throttle === 0 || is_null($device->login)) { |
||
| 171 | return true; |
||
| 172 | } |
||
| 173 | |||
| 174 | $limit = Carbon::now()->subMinutes($throttle); |
||
| 175 | $login = $device->login; |
||
| 176 | |||
| 177 | if (isset($login->created_at) && $login->created_at->gt($limit)) { |
||
| 178 | return false; |
||
| 179 | } |
||
| 180 | |||
| 181 | return true; |
||
| 182 | } |
||
| 183 | |||
| 184 | public function deviceMatch(Device $device, Agent $agent, array $attributes = null): bool |
||
| 185 | { |
||
| 186 | $attributes = is_null($attributes) ? $this->getDeviceMatchingAttributesConfig() : $attributes; |
||
| 187 | $matches = 0; |
||
| 188 | |||
| 189 | if (in_array('platform', $attributes)) { |
||
| 190 | $matches += $device->platform === $agent->platform(); |
||
| 191 | } |
||
| 192 | |||
| 193 | if (in_array('platform_version', $attributes)) { |
||
| 194 | $agentPlatformVersion = $agent->version($device->platform); |
||
| 195 | $agentPlatformVersion = empty($agentPlatformVersion) ? '0' : $agentPlatformVersion; |
||
| 196 | $matches += $device->platform_version === $agentPlatformVersion; |
||
| 197 | } |
||
| 198 | |||
| 199 | if (in_array('browser', $attributes)) { |
||
| 200 | $matches += $device->browser === $agent->browser(); |
||
| 201 | } |
||
| 202 | |||
| 203 | if (in_array('browser_version', $attributes)) { |
||
| 204 | $matches += $device->browser_version === $agent->version($device->browser); |
||
| 205 | } |
||
| 206 | |||
| 207 | if (in_array('language', $attributes)) { |
||
| 208 | $matches += $device->language === $agent->version($device->language); |
||
| 209 | } |
||
| 210 | |||
| 211 | return $matches === count($attributes); |
||
| 212 | } |
||
| 213 | |||
| 214 | public function getDeviceMatchingAttributesConfig(): array |
||
| 215 | { |
||
| 216 | return $this->config->get('auth-checker.device_matching_attributes', [ |
||
| 217 | 'platform', |
||
| 218 | 'platform_version', |
||
| 219 | 'browser', |
||
| 220 | ]); |
||
| 221 | } |
||
| 222 | |||
| 223 | public function getLoginThrottleConfig(): int |
||
| 224 | { |
||
| 225 | return (int)$this->config->get('auth-checker.throttle', 0); |
||
| 226 | } |
||
| 227 | |||
| 228 | public function getLoginColumnConfig(): string |
||
| 229 | { |
||
| 230 | return (string)$this->config->get('auth-checker.login_column', 'email'); |
||
| 231 | } |
||
| 232 | } |
||
| 233 |