Laravel AT Protocol Client (alpha & unstable)
at main 4.3 kB view raw
1<?php 2 3namespace SocialDept\AtpClient\Auth; 4 5use Illuminate\Contracts\Auth\Authenticatable; 6use SocialDept\AtpClient\Contracts\HasAtpSession; 7use SocialDept\AtpClient\Enums\Scope; 8use SocialDept\AtpClient\Enums\ScopeAuthorizationFailure; 9use SocialDept\AtpClient\Exceptions\ScopeAuthorizationException; 10use SocialDept\AtpClient\Session\Session; 11use SocialDept\AtpClient\Session\SessionManager; 12 13class ScopeGate 14{ 15 protected ?Session $session = null; 16 17 public function __construct( 18 protected SessionManager $sessions, 19 protected ScopeChecker $checker, 20 ) {} 21 22 /** 23 * Set the session context directly. 24 */ 25 public function forSession(Session $session): self 26 { 27 $instance = new self($this->sessions, $this->checker); 28 $instance->session = $session; 29 30 return $instance; 31 } 32 33 /** 34 * Set the session context via actor (handle or DID). 35 */ 36 public function forUser(string $actor): self 37 { 38 $instance = new self($this->sessions, $this->checker); 39 $instance->session = $this->sessions->session($actor); 40 41 return $instance; 42 } 43 44 /** 45 * Check if the session has the given scope. 46 */ 47 public function can(string|Scope $scope): bool 48 { 49 $session = $this->resolveSession(); 50 51 if (! $session) { 52 return false; 53 } 54 55 return $this->checker->hasScope($session, $scope); 56 } 57 58 /** 59 * Check if the session has any of the given scopes. 60 * 61 * @param array<string|Scope> $scopes 62 */ 63 public function canAny(array $scopes): bool 64 { 65 $session = $this->resolveSession(); 66 67 if (! $session) { 68 return false; 69 } 70 71 foreach ($scopes as $scope) { 72 if ($this->checker->hasScope($session, $scope)) { 73 return true; 74 } 75 } 76 77 return false; 78 } 79 80 /** 81 * Check if the session has all of the given scopes. 82 * 83 * @param array<string|Scope> $scopes 84 */ 85 public function canAll(array $scopes): bool 86 { 87 $session = $this->resolveSession(); 88 89 if (! $session) { 90 return false; 91 } 92 93 return $this->checker->check($session, $scopes); 94 } 95 96 /** 97 * Check if the session does NOT have the given scope. 98 */ 99 public function cannot(string|Scope $scope): bool 100 { 101 return ! $this->can($scope); 102 } 103 104 /** 105 * Authorize the session has all given scopes, or handle failure. 106 * 107 * @param string|Scope ...$scopes 108 * 109 * @throws ScopeAuthorizationException 110 */ 111 public function authorize(string|Scope ...$scopes): void 112 { 113 if ($this->canAll($scopes)) { 114 return; 115 } 116 117 $session = $this->resolveSession(); 118 $granted = $session ? $session->scopes() : []; 119 $required = array_map( 120 fn ($scope) => $scope instanceof Scope ? $scope->value : $scope, 121 $scopes 122 ); 123 $missing = array_diff($required, $granted); 124 125 $exception = new ScopeAuthorizationException($missing, $granted); 126 127 $action = config('atp-client.scope_authorization.failure_action', ScopeAuthorizationFailure::Abort); 128 129 if ($action === ScopeAuthorizationFailure::Exception) { 130 throw $exception; 131 } 132 133 // For Abort and Redirect, let the exception render itself 134 throw $exception; 135 } 136 137 /** 138 * Get the granted scopes for the current session. 139 */ 140 public function granted(): array 141 { 142 $session = $this->resolveSession(); 143 144 return $session ? $session->scopes() : []; 145 } 146 147 /** 148 * Resolve the session from context. 149 */ 150 protected function resolveSession(): ?Session 151 { 152 // If session was explicitly set, use it 153 if ($this->session) { 154 return $this->session; 155 } 156 157 // Try to resolve from authenticated user 158 $user = auth()->user(); 159 160 if (! $user instanceof HasAtpSession) { 161 return null; 162 } 163 164 $did = $user->getAtpDid(); 165 166 if (! $did) { 167 return null; 168 } 169 170 try { 171 return $this->sessions->session($did); 172 } catch (\Exception) { 173 return null; 174 } 175 } 176}