Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
72.00% covered (warning)
72.00%
18 / 25
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Apie
72.00% covered (warning)
72.00%
18 / 25
50.00% covered (danger)
50.00%
2 / 4
10.78
0.00% covered (danger)
0.00%
0 / 1
 getFacadeAccessor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 loginAs
77.78% covered (warning)
77.78%
14 / 18
0.00% covered (danger)
0.00%
0 / 1
6.40
 logout
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getCurrentUser
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace Apie\LaravelApie;
4
5use Apie\Common\Events\AddAuthenticationCookie;
6use Apie\Common\ValueObjects\DecryptedAuthenticatedUser;
7use Apie\Core\Actions\ActionInterface;
8use Apie\Core\Actions\ApieFacadeInterface;
9use Apie\Core\BoundedContext\BoundedContext;
10use Apie\Core\BoundedContext\BoundedContextId;
11use Apie\Core\Context\ApieContext;
12use Apie\Core\Datalayers\Lists\EntityListInterface;
13use Apie\Core\Entities\EntityInterface;
14use Apie\Core\Identifiers\IdentifierInterface;
15use Apie\Core\Lists\ItemHashmap;
16use Apie\Core\Lists\ItemList;
17use Apie\LaravelApie\ContextBuilders\ApieCurrentUserContextBuilder;
18use Apie\LaravelApie\Wrappers\Core\BoundedContextSelected;
19use Apie\LaravelApie\Wrappers\Security\ApieUserDecorator;
20use Illuminate\Support\Facades\Auth;
21use Illuminate\Support\Facades\Cookie;
22use Illuminate\Support\Facades\Facade;
23use ReflectionClass;
24use ReflectionMethod;
25
26/**
27 * @method static EntityListInterface<EntityInterface> all(string|ReflectionClass<EntityInterface> $class, BoundedContext|BoundedContextId $boundedContext)
28 * @method static EntityInterface find(IdentifierInterface<EntityInterface> $identifier, BoundedContext|BoundedContextId $boundedContext)
29 * @method static EntityInterface persistNew(EntityInterface $entity, BoundedContext|BoundedContextId $boundedContext)
30 * @method static EntityInterface persistExisting(EntityInterface $entity, BoundedContext|BoundedContextId $boundedContext)
31 * @method static EntityInterface upsert(EntityInterface $entity, BoundedContext|BoundedContextId $boundedContext)
32 * @method static void removeExisting(EntityInterface $entity, BoundedContext|BoundedContextId $boundedContext)
33 * @method static string|int|float|bool|ItemList|ItemHashmap|null normalize(mixed $object, ApieContext $apieContext)
34 * @method static mixed denormalizeNewObject(string|int|float|bool|ItemList|ItemHashmap|array<mixed>|null $object, string $desiredType, ApieContext $apieContext)
35 * @method static mixed denormalizeOnExistingObject(ItemHashmap $object, object $existingObject, ApieContext $apieContext)
36 * @method static mixed denormalizeOnMethodCall(string|int|float|bool|ItemList|ItemHashmap|array<mixed>|null $input, ?object $object, ReflectionMethod $method, ApieContext $apieContext)
37 * @method static ActionInterface createAction(ApieContext $apieContext)
38 *
39 * @see ApieFacadeInterface
40 */
41class Apie extends Facade
42{
43    /** @var DecryptedAuthenticatedUser<EntityInterface>|null */
44    private static ?DecryptedAuthenticatedUser $currentUser = null;
45    protected static function getFacadeAccessor(): string
46    {
47        return 'apie';
48    }
49
50    public static function loginAs(EntityInterface $entity, BoundedContext|BoundedContextId|string|null $boundedContext): void
51    {
52        if (is_string($boundedContext)) {
53            $boundedContext = new BoundedContextId($boundedContext);
54        }
55        $boundedContextId = $boundedContext instanceof BoundedContext ? $boundedContext->getId() : $boundedContext;
56        if ($boundedContextId === null) {
57            $boundedContextId = resolve(BoundedContextSelected::class)->getBoundedContextFromRequest()?->getId();
58        }
59        if ($boundedContextId === null) {
60            $className = $entity->getId()->getReferenceFor()->name;
61            $boundedContextId = resolve(BoundedContextSelected::class)->getBoundedContextFromClassName($className)?->getId();
62        }
63        if ($boundedContextId === null) {
64            throw new \RuntimeException('Could not determine bounded context for the given entity or request.');
65        }
66
67        // Auto-login the new user
68        $decryptedUserId = DecryptedAuthenticatedUser::createFromEntity(
69            $entity,
70            $boundedContextId,
71            time() + 3600
72        );
73        $decoratedUser = new ApieUserDecorator($decryptedUserId, $entity);
74        Auth::login($decoratedUser);
75        self::$currentUser = $decryptedUserId;
76    }
77
78    /**
79     * Logs a user out and removes the authentication cookie apie uses, so the request is not
80     * re-authenticated again by the cookie that was previously set.
81     *
82     * Laravel has no reliable way to detect a logout the way symfony's LogoutEvent does (the
83     * `logout_url` config simply points to an application route), so applications must call this
84     * method from their own logout route/controller to guarantee the cookie is actually removed.
85     */
86    public static function logout(): void
87    {
88        Auth::logout();
89        self::$currentUser = null;
90        Cookie::queue(Cookie::forget(AddAuthenticationCookie::COOKIE_NAME));
91    }
92
93    /**
94     * @internal
95     * @see ApieCurrentUserContextBuilder
96     * @return DecryptedAuthenticatedUser<EntityInterface>|null
97     */
98    public static function getCurrentUser(): ?DecryptedAuthenticatedUser
99    {
100        return tap(self::$currentUser, function () {
101            self::$currentUser = null;
102        });
103    }
104}