null, 'root_admin' => false, 'language' => 'en', 'use_totp' => false, 'totp_secret' => null, ]; /** * Rules verifying that the data being stored matches the expectations of the database. */ public static array $validationRules = [ 'uuid' => 'required|string|size:36|unique:users,uuid', 'email' => 'required|email|between:1,191|unique:users,email', 'external_id' => 'sometimes|nullable|string|max:191|unique:users,external_id', 'username' => 'required|between:1,191|unique:users,username', 'name_first' => 'required|string|between:1,191', 'name_last' => 'required|string|between:1,191', 'password' => 'sometimes|nullable|string', 'root_admin' => 'boolean', 'language' => 'string', 'use_totp' => 'boolean', 'totp_secret' => 'nullable|string', ]; protected function casts(): array { return [ 'root_admin' => 'boolean', 'use_totp' => 'boolean', 'gravatar' => 'boolean', 'totp_authenticated_at' => 'datetime', ]; } protected static function booted(): void { static::deleting(function (self $user) { throw_if($user->servers()->count() > 0, new DisplayException(__('admin/user.exceptions.user_has_servers'))); throw_if(request()->user()?->id === $user->id, new DisplayException(__('admin/user.exceptions.user_is_self'))); }); } public function getRouteKeyName(): string { return 'id'; } /** * Implement language verification by overriding Eloquence's gather * rules function. */ public static function getRules(): array { $rules = parent::getRules(); $rules['language'][] = new In(array_keys((new self())->getAvailableLanguages())); $rules['username'][] = new Username(); return $rules; } /** * Return the user model in a format that can be passed over to Vue templates. */ public function toVueObject(): array { return Collection::make($this->toArray())->except(['id', 'external_id'])->toArray(); } /** * Send the password reset notification. * * @param string $token */ public function sendPasswordResetNotification($token) { Activity::event('auth:reset-password') ->withRequestMetadata() ->subject($this) ->log('sending password reset email'); $this->notify(new ResetPasswordNotification($token)); } /** * Store the username as a lowercase string. */ public function setUsernameAttribute(string $value) { $this->attributes['username'] = mb_strtolower($value); } /** * Return a concatenated result for the accounts full name. */ public function getNameAttribute(): string { return trim($this->name_first . ' ' . $this->name_last); } /** * Returns all servers that a user owns. */ public function servers(): HasMany { return $this->hasMany(Server::class, 'owner_id'); } public function apiKeys(): HasMany { return $this->hasMany(ApiKey::class) ->where('key_type', ApiKey::TYPE_ACCOUNT); } public function recoveryTokens(): HasMany { return $this->hasMany(RecoveryToken::class); } public function sshKeys(): HasMany { return $this->hasMany(UserSSHKey::class); } /** * Returns all the activity logs where this user is the subject — not to * be confused by activity logs where this user is the _actor_. */ public function activity(): MorphToMany { return $this->morphToMany(ActivityLog::class, 'subject', 'activity_log_subjects'); } /** * Returns all the servers that a user can access by way of being the owner of the * server, or because they are assigned as a subuser for that server. */ public function accessibleServers(): Builder { return Server::query() ->select('servers.*') ->leftJoin('subusers', 'subusers.server_id', '=', 'servers.id') ->where(function (Builder $builder) { $builder->where('servers.owner_id', $this->id)->orWhere('subusers.user_id', $this->id); }) ->groupBy('servers.id'); } public function subusers(): HasMany { return $this->hasMany(Subuser::class); } protected function checkPermission(Server $server, string $permission = ''): bool { if ($this->root_admin || $server->owner_id === $this->id) { return true; } $subuser = $server->subusers->where('user_id', $this->id)->first(); if (!$subuser || empty($permission)) { return false; } $check = in_array($permission, $subuser->permissions); return $check; } /** * Laravel's policies strictly check for the existence of a real method, * this checks if the ability is one of our permissions and then checks if the user can do it or not * Otherwise it calls the Authorizable trait's parent method */ public function can($abilities, mixed $arguments = []): bool { if (is_string($abilities) && str_contains($abilities, '.')) { [$permission, $key] = str($abilities)->explode('.', 2); if (isset(Permission::permissions()[$permission]['keys'][$key])) { if ($arguments instanceof Server) { return $this->checkPermission($arguments, $abilities); } } } return $this->canned($abilities, $arguments); } public function canAccessPanel(Panel $panel): bool { return $this->root_admin; } }