120 lines
2.9 KiB
PHP
120 lines
2.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Support\TokenAbilities;
|
|
use Hyperf\Database\Model\Relations\MorphTo;
|
|
|
|
class PersonalAccessToken extends Model
|
|
{
|
|
protected ?string $table = 'personal_access_tokens';
|
|
|
|
public bool $incrementing = true;
|
|
|
|
protected array $fillable = [
|
|
'tokenable_type',
|
|
'tokenable_id',
|
|
'name',
|
|
'description',
|
|
'token',
|
|
'abilities',
|
|
'allowed_ips',
|
|
'last_used_at',
|
|
'last_used_ip',
|
|
'expires_at',
|
|
'created_by',
|
|
'revoked_by',
|
|
'revoked_at',
|
|
];
|
|
|
|
protected array $casts = [
|
|
'abilities' => 'array',
|
|
'allowed_ips' => 'array',
|
|
'last_used_at' => 'datetime',
|
|
'expires_at' => 'datetime',
|
|
'revoked_at' => 'datetime',
|
|
];
|
|
|
|
protected array $hidden = [
|
|
'token',
|
|
];
|
|
|
|
public function tokenable(): MorphTo
|
|
{
|
|
return $this->morphTo();
|
|
}
|
|
|
|
public static function findByPlainToken(string $plain): ?self
|
|
{
|
|
return static::query()->where('token', hash('sha256', $plain))->first();
|
|
}
|
|
|
|
public function isExpired(): bool
|
|
{
|
|
return $this->expires_at !== null && $this->expires_at->isPast();
|
|
}
|
|
|
|
public function isRevoked(): bool
|
|
{
|
|
return $this->revoked_at !== null;
|
|
}
|
|
|
|
public function isActive(): bool
|
|
{
|
|
return ! $this->isExpired() && ! $this->isRevoked();
|
|
}
|
|
|
|
public function can(string $ability): bool
|
|
{
|
|
$abilities = $this->abilities ?? [];
|
|
if (in_array(TokenAbilities::WILDCARD, $abilities, true)) {
|
|
return true;
|
|
}
|
|
return in_array($ability, $abilities, true);
|
|
}
|
|
|
|
public function ipAllowed(string $ip): bool
|
|
{
|
|
$list = $this->allowed_ips ?? [];
|
|
if (empty($list)) {
|
|
return true;
|
|
}
|
|
foreach ($list as $entry) {
|
|
if ($this->ipMatches($ip, $entry)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private function ipMatches(string $ip, string $entry): bool
|
|
{
|
|
$entry = trim($entry);
|
|
if ($entry === '') {
|
|
return false;
|
|
}
|
|
if (! str_contains($entry, '/')) {
|
|
return $ip === $entry;
|
|
}
|
|
[$subnet, $bits] = explode('/', $entry, 2);
|
|
$bits = (int) $bits;
|
|
$ipBin = @inet_pton($ip);
|
|
$subnetBin = @inet_pton($subnet);
|
|
if ($ipBin === false || $subnetBin === false || strlen($ipBin) !== strlen($subnetBin)) {
|
|
return false;
|
|
}
|
|
$bytes = intdiv($bits, 8);
|
|
$remainder = $bits % 8;
|
|
if (substr($ipBin, 0, $bytes) !== substr($subnetBin, 0, $bytes)) {
|
|
return false;
|
|
}
|
|
if ($remainder === 0) {
|
|
return true;
|
|
}
|
|
$mask = chr(0xff << (8 - $remainder) & 0xff);
|
|
return (ord($ipBin[$bytes]) & ord($mask)) === (ord($subnetBin[$bytes]) & ord($mask));
|
|
}
|
|
}
|