'encrypted', 'refresh_token' => 'encrypted', 'client_secret' => 'encrypted', 'is_system_wide' => 'boolean', 'is_public' => 'boolean', ]; } /** * Encrypt webhook tokens at rest. Supports legacy plaintext values until they are re-saved. * Not a standard encrypted cast: webhooks look up by token value (see findByWebhookToken). */ protected function webhookToken(): Attribute { return Attribute::make( get: function (?string $value): ?string { if ($value === null || $value === '') { return $value; } try { return Crypt::decryptString($value); } catch (DecryptException) { // Legacy rows stored the token in plaintext. return $value; } }, set: function (?string $value): ?string { if ($value === null || $value === '') { return $value; } return Crypt::encryptString($value); }, ); } public static function findByWebhookToken(string $token): ?self { if ($token === '') { return null; } // Encrypted values cannot be matched with a SQL equality; sources are few per instance. return static::query()->get()->first( fn (self $app): bool => filled($app->webhook_token) && hash_equals((string) $app->webhook_token, $token) ); } protected static function booted(): void { static::deleting(function (GitlabApp $gitlabApp) { if ($gitlabApp->applications()->count() > 0) { throw new \RuntimeException('This source is being used by an application. Please delete all applications first.'); } }); } public static function ownedByCurrentTeam() { return GitlabApp::where(function ($query) { $query->where('team_id', currentTeam()->id) ->orWhere('is_system_wide', true); }); } public static function public() { return GitlabApp::where(function ($query) { $query->where('team_id', currentTeam()->id)->orWhere('is_system_wide', true); })->where('is_public', true); } public static function private() { return GitlabApp::where(function ($query) { $query->where('team_id', currentTeam()->id)->orWhere('is_system_wide', true); })->where('is_public', false)->whereNotNull('access_token'); } public function applications() { return $this->morphMany(Application::class, 'source'); } public function privateKey() { return $this->belongsTo(PrivateKey::class); } public function team() { return $this->belongsTo(Team::class); } public function isConnected(): bool { return ! empty($this->access_token) && ! empty($this->refresh_token); } public function apiUrlBase(): string { $apiUrl = rtrim($this->api_url, '/'); if (! str_contains($apiUrl, '/api/v4')) { $apiUrl .= '/api/v4'; } return $apiUrl; } }