Access.php 4.6 KB
Newer Older
1
2
3
4
<?php

namespace Source\UseCases\Door\Access;

Jacob Priddy's avatar
Jacob Priddy committed
5
use Carbon\Carbon;
6
use Source\Entities\User;
7
8
9
use Source\Entities\Entry;
use Source\Entities\Attempt;
use Source\Sanitize\CastsTo;
10
11
use Source\Entities\HashedSearchable;
use Source\Gateways\Users\UsersRepository;
12
13
use Source\Exceptions\AuthorizationException;
use Source\Exceptions\AuthenticationException;
14
15
use Source\Gateways\Entries\EntriesRepository;
use Source\Gateways\Attempts\AttemptsRepository;
16
17
18

class Access implements AccessUseCase
{
19
    public const COMMAND_DELIMITER = '*';
20
21
22
23
24
25
26
27
28
29
30
31
    use CastsTo;

    /**
     * @var \Source\Gateways\Attempts\AttemptsRepository
     */
    protected AttemptsRepository $attempts;

    /**
     * @var \Source\Gateways\Entries\EntriesRepository
     */
    protected EntriesRepository $entries;

32
    /**
33
     * @var AccessAuthorizer[]
34
     */
35
36
37
38
39
40
41
42
    protected array $authorizers = [];

    /**
     * @var \Source\Gateways\Users\UsersRepository
     */
    protected UsersRepository $users;

    protected string $salt;
43

44
    public function __construct(
45
        UsersRepository $users,
46
        AttemptsRepository $attempts,
47
48
49
        EntriesRepository $entries,
        string $salt,
        AccessAuthorizer ...$authorizers
50
    ) {
51
        $this->users = $users;
52
        $this->attempts = $attempts;
Jacob Priddy's avatar
Jacob Priddy committed
53
        $this->entries = $entries;
54
55
56
57
58
        $this->salt = $salt;

        foreach ($authorizers as $authorizer) {
            $this->authorizers[] = $authorizer;
        }
Jacob Priddy's avatar
Jacob Priddy committed
59
60
    }

61
62
63
64
65
    /**
     * @param string $doorId
     * @param string $userId
     * @param bool   $success
     */
Jacob Priddy's avatar
Jacob Priddy committed
66
67
68
69
70
71
72
73
74
75
    protected function logUserAccess(string $doorId, string $userId, bool $success): void
    {
        $this->entries->add(new Entry(
            0,
            $userId,
            $doorId,
            $success
        ));
    }

76
77
78
    /**
     * @param string $doorId
     */
Jacob Priddy's avatar
Jacob Priddy committed
79
80
81
    protected function logDoorAttempt(string $doorId): void
    {
        $this->attempts->add(new Attempt(0, $this->castToInt($doorId)));
82
83
    }

84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
    /**
     * @param \Source\Entities\User|null $user
     * @param string                     $doorId
     */
    protected function logAllow(?User $user, string $doorId): void
    {
        if ($user) {
            $this->logUserAccess($doorId, $user->getId(), true);
        }
    }

    /**
     * @param \Source\Entities\User|null $user
     * @param string                     $doorId
     */
    protected function logDeny(?User $user, string $doorId): void
    {
        if ($user) {
            $this->logUserAccess($doorId, $user->getId(), false);
        } else {
            $this->logDoorAttempt($doorId);
        }
    }

108
109
110
    /**
     * @inheritDoc
     */
111
    public function protectUserDoorAccessAtTime(?string $doorId, string $doorcode, Carbon $date): void
112
    {
Jacob Priddy's avatar
Jacob Priddy committed
113
114
        /*
         * Our job here is to find out if a user has access given a doorcode and the id of the door they want to access
115
         * as well as process any sent commands
Jacob Priddy's avatar
Jacob Priddy committed
116
117
118
119
120
121
122
123
124
125
126
         *
         * This process is a little complicated as we not only have to check whether the user has access to the door
         * but also if the door might possibly be allowing everyone (open mode), or if the user has access to the door
         * during the current time frame based on their assigned group schedules.
         *
         * door ---> group <--- user
         *              ^
         *              |
         *              |
         *          schedule
         *
127
128
         * I have broken this process into small chunks that can easily be enabled and disabled, or changed
         * These authorizers are injected in the service provider, and we will go until allowed or denied
Jacob Priddy's avatar
Jacob Priddy committed
129
         */
130

Jacob Priddy's avatar
Jacob Priddy committed
131
        if (!$doorId) {
132
133
134
            throw new AuthenticationException();
        }

135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
        $parts = explode(self::COMMAND_DELIMITER, $doorcode);

        $code = $parts[0] ?? '';
        $command = $parts[1] ?? null;

        $user = $this->users->findByDoorcode(HashedSearchable::hash($this->salt, $code));

        foreach ($this->authorizers as $authorizer) {
            switch ($authorizer->check($user, $date, $doorId, $code, $command)) {
                case AccessAuthorizer::ALLOW:
                    $this->logAllow($user, $doorId);
                    return;
                case AccessAuthorizer::DENY:
                    $this->logDeny($user, $doorId);
                    throw new AuthorizationException();
                case AccessAuthorizer::CONTINUE:
                    // Fall through to default.
                default:
                    break;
            }
155
        }
156

157
158
        // Default to deny if no authorizer allowed us
        $this->logDeny($user, $doorId);
Jacob Priddy's avatar
Jacob Priddy committed
159
        throw new AuthorizationException();
160
161
    }
}