|
15 | 15 | use ActiveCollab\Authentication\AuthenticationResult\AuthenticationResultInterface;
|
16 | 16 | use ActiveCollab\Authentication\AuthenticationResult\Transport\Authentication\AuthenticationTransportInterface;
|
17 | 17 | use ActiveCollab\Authentication\AuthenticationResult\Transport\Authorization\AuthorizationTransport;
|
| 18 | +use ActiveCollab\Authentication\AuthenticationResult\Transport\Authorization\AuthorizationTransportInterface; |
18 | 19 | use ActiveCollab\Authentication\AuthenticationResult\Transport\Transport;
|
19 | 20 | use ActiveCollab\Authentication\AuthenticationResult\Transport\TransportInterface;
|
20 | 21 | use ActiveCollab\Authentication\Authorizer\AuthorizerInterface;
|
| 22 | +use ActiveCollab\Authentication\Exception\IntentExpiredException; |
| 23 | +use ActiveCollab\Authentication\Exception\IntentFulfilledException; |
21 | 24 | use ActiveCollab\Authentication\Exception\InvalidAuthenticationRequestException;
|
| 25 | +use ActiveCollab\Authentication\Exception\InvalidIntentUserException; |
| 26 | +use ActiveCollab\Authentication\Exception\SecondFactorNotRequiredException; |
| 27 | +use ActiveCollab\Authentication\Intent\IntentInterface; |
| 28 | +use ActiveCollab\Authentication\Intent\RepositoryInterface as IntentRepositoryInterface; |
22 | 29 | use Exception;
|
| 30 | +use LogicException; |
23 | 31 | use Psr\Http\Message\ResponseInterface;
|
24 | 32 | use Psr\Http\Message\ServerRequestInterface;
|
25 | 33 | use Psr\Http\Server\RequestHandlerInterface;
|
26 | 34 |
|
27 | 35 | class Authentication implements AuthenticationInterface
|
28 | 36 | {
|
| 37 | + private IntentRepositoryInterface $intent_repository; |
29 | 38 | private array $adapters;
|
30 | 39 | private ?AuthenticatedUserInterface $authenticated_user = null;
|
31 | 40 | private ?AuthenticationResultInterface $authenticated_with = null;
|
32 | 41 | private array $on_user_authenticated = [];
|
33 | 42 | private array $on_user_authorized = [];
|
34 | 43 | private array $on_user_authorization_failed = [];
|
| 44 | + private array $on_intent_authorized = []; |
| 45 | + private array $on_intent_authorization_failed = []; |
| 46 | + private array $on_intent_fulfilled = []; |
| 47 | + private array $on_intent_fulfillment_failed = []; |
35 | 48 | private array $on_user_set = [];
|
36 | 49 | private array $on_user_deauthenticated = [];
|
37 | 50 |
|
38 |
| - public function __construct(AdapterInterface ...$adapters) |
| 51 | + public function __construct( |
| 52 | + IntentRepositoryInterface $intent_repository, |
| 53 | + AdapterInterface ...$adapters, |
| 54 | + ) |
39 | 55 | {
|
| 56 | + $this->intent_repository = $intent_repository; |
40 | 57 | $this->adapters = $adapters;
|
41 | 58 | }
|
42 | 59 |
|
@@ -114,26 +131,93 @@ public function authorize(
|
114 | 131 | AuthorizerInterface $authorizer,
|
115 | 132 | AdapterInterface $adapter,
|
116 | 133 | array $credentials,
|
117 |
| - $payload = null |
118 |
| - ): TransportInterface |
| 134 | + array $payload = null, |
| 135 | + ): TransportInterface|IntentInterface |
119 | 136 | {
|
120 | 137 | try {
|
121 | 138 | $user = $authorizer->verifyCredentials($credentials);
|
122 |
| - $authenticatedWith = $adapter->authenticate($user, $credentials); |
123 | 139 |
|
124 |
| - $this->triggerEvent('user_authorized', $user, $authenticatedWith); |
| 140 | + if ($user->requiresSecondFactor()) { |
| 141 | + $intent = $this->intent_repository->createSecondFactorIntent( |
| 142 | + $user, |
| 143 | + ); |
| 144 | + |
| 145 | + $this->triggerEvent('intent_authorized', $user, $intent); |
125 | 146 |
|
126 |
| - $authorizationResult = new AuthorizationTransport($adapter, $user, $authenticatedWith, $payload); |
127 |
| - $this->lastProcessingResult = $authorizationResult; |
| 147 | + return $intent; |
| 148 | + } |
128 | 149 |
|
129 |
| - return $authorizationResult; |
| 150 | + return $this->completeAuthentication( |
| 151 | + $adapter, |
| 152 | + $user, |
| 153 | + $credentials, |
| 154 | + $payload, |
| 155 | + ); |
130 | 156 | } catch (Exception $e) {
|
131 | 157 | $this->triggerEvent('user_authorization_failed', $credentials, $e);
|
132 | 158 |
|
133 | 159 | throw $e;
|
134 | 160 | }
|
135 | 161 | }
|
136 | 162 |
|
| 163 | + public function fulfillIntent( |
| 164 | + AuthorizerInterface $authorizer, |
| 165 | + AdapterInterface $adapter, |
| 166 | + IntentInterface $intent, |
| 167 | + AuthenticatedUserInterface $user, |
| 168 | + array $credentials, |
| 169 | + array $payload = null, |
| 170 | + ): TransportInterface |
| 171 | + { |
| 172 | + try { |
| 173 | + if ($intent->isFulfilled()) { |
| 174 | + throw new IntentFulfilledException(); |
| 175 | + } |
| 176 | + |
| 177 | + if ($intent->isExpired()) { |
| 178 | + throw new IntentExpiredException(); |
| 179 | + } |
| 180 | + |
| 181 | + if (!$intent->belongsToUser($user)) { |
| 182 | + throw new InvalidIntentUserException(); |
| 183 | + } |
| 184 | + |
| 185 | + if (!$user->requiresSecondFactor()) { |
| 186 | + throw new SecondFactorNotRequiredException(); |
| 187 | + } |
| 188 | + |
| 189 | + $intent->fulfill($user, $credentials); |
| 190 | + $this->triggerEvent('intent_fulfilled', $user, $intent); |
| 191 | + |
| 192 | + return $this->completeAuthentication( |
| 193 | + $adapter, |
| 194 | + $user, |
| 195 | + $credentials, |
| 196 | + $payload, |
| 197 | + ); |
| 198 | + } catch (Exception $e) { |
| 199 | + $this->triggerEvent('intent_fulfillment_failed', $credentials, $e); |
| 200 | + throw $e; |
| 201 | + } |
| 202 | + } |
| 203 | + |
| 204 | + private function completeAuthentication( |
| 205 | + AdapterInterface $adapter, |
| 206 | + AuthenticatedUserInterface $user, |
| 207 | + array $credentials, |
| 208 | + ?array $payload, |
| 209 | + ): AuthorizationTransportInterface |
| 210 | + { |
| 211 | + $authenticatedWith = $adapter->authenticate($user, $credentials); |
| 212 | + |
| 213 | + $this->triggerEvent('user_authorized', $user, $authenticatedWith); |
| 214 | + |
| 215 | + $authorizationResult = new AuthorizationTransport($adapter, $user, $authenticatedWith, $payload); |
| 216 | + $this->lastProcessingResult = $authorizationResult; |
| 217 | + |
| 218 | + return $authorizationResult; |
| 219 | + } |
| 220 | + |
137 | 221 | public function terminate(
|
138 | 222 | AdapterInterface $adapter,
|
139 | 223 | AuthenticationResultInterface $authenticatedWith
|
@@ -217,6 +301,10 @@ private function triggerEvent(string $event_name, ...$arguments): void
|
217 | 301 | {
|
218 | 302 | $property_name = sprintf("on_%s", $event_name);
|
219 | 303 |
|
| 304 | + if (!property_exists($this, $property_name)) { |
| 305 | + throw new LogicException(sprintf('Event %s does not exist.', $event_name)); |
| 306 | + } |
| 307 | + |
220 | 308 | /** @var callable $handler */
|
221 | 309 | foreach ($this->$property_name as $handler) {
|
222 | 310 | call_user_func_array($handler, $arguments);
|
@@ -244,6 +332,34 @@ public function onUserAuthorizationFailed(callable $value): AuthenticationInterf
|
244 | 332 | return $this;
|
245 | 333 | }
|
246 | 334 |
|
| 335 | + public function onIntentAuthorized(callable $value): AuthenticationInterface |
| 336 | + { |
| 337 | + $this->on_intent_authorized[] = $value; |
| 338 | + |
| 339 | + return $this; |
| 340 | + } |
| 341 | + |
| 342 | + public function onIntentAuthorizationFailed(callable $value): AuthenticationInterface |
| 343 | + { |
| 344 | + $this->on_intent_authorization_failed[] = $value; |
| 345 | + |
| 346 | + return $this; |
| 347 | + } |
| 348 | + |
| 349 | + public function onIntentFulfilled(callable $value): AuthenticationInterface |
| 350 | + { |
| 351 | + $this->on_intent_fulfilled[] = $value; |
| 352 | + |
| 353 | + return $this; |
| 354 | + } |
| 355 | + |
| 356 | + public function onIntentFulfillmentFailed(callable $value): AuthenticationInterface |
| 357 | + { |
| 358 | + $this->on_intent_fulfillment_failed[] = $value; |
| 359 | + |
| 360 | + return $this; |
| 361 | + } |
| 362 | + |
247 | 363 | public function onUserSet(callable $value): AuthenticationInterface
|
248 | 364 | {
|
249 | 365 | $this->on_user_set[] = $value;
|
|
0 commit comments