Authenticate.php 6.31 KB
Newer Older
Jacob Priddy's avatar
Jacob Priddy committed
1 2 3 4
<?php

namespace Source\UseCases\Users\Authenticate;

5
use Carbon\Carbon;
6
use Source\Entities\User;
7
use Source\Entities\HashedSearchable;
8
use Source\Gateways\Saml\SamlRepository;
Jacob Priddy's avatar
Jacob Priddy committed
9 10
use Source\Gateways\Users\UsersRepository;
use Source\Gateways\Tokens\TokensRepository;
11
use Source\Exceptions\AuthorizationException;
12
use Source\Exceptions\AuthenticationException;
Jacob Priddy's avatar
Jacob Priddy committed
13

Jacob Priddy's avatar
Jacob Priddy committed
14 15
class Authenticate implements AuthenticateUseCase
{
16 17 18
    /**
     * @var \Source\Gateways\Users\UsersRepository
     */
Jacob Priddy's avatar
Jacob Priddy committed
19 20
    protected UsersRepository $users;

21 22 23
    /**
     * @var \Source\Gateways\Tokens\TokensRepository
     */
Jacob Priddy's avatar
Jacob Priddy committed
24 25
    protected TokensRepository $tokens;

Jacob Priddy's avatar
Jacob Priddy committed
26 27 28 29
    /**
     * @var \Source\Gateways\Saml\SamlRepository
     */
    protected SamlRepository $saml;
30

31 32
    protected string $salt;

33 34 35
    /**
     * @param \Source\Gateways\Users\UsersRepository   $users
     * @param \Source\Gateways\Tokens\TokensRepository $tokens
Jacob Priddy's avatar
Jacob Priddy committed
36
     * @param \Source\Gateways\Saml\SamlRepository     $saml
37
     * @param string                                   $salt
38
     */
39
    public function __construct(UsersRepository $users, TokensRepository $tokens, SamlRepository $saml, string $salt)
Jacob Priddy's avatar
Jacob Priddy committed
40
    {
Jacob Priddy's avatar
Jacob Priddy committed
41
        $this->saml = $saml;
Jacob Priddy's avatar
Jacob Priddy committed
42 43
        $this->users = $users;
        $this->tokens = $tokens;
44
        $this->salt = $salt;
Jacob Priddy's avatar
Jacob Priddy committed
45 46 47 48 49
    }

    /**
     * @inheritDoc
     */
Jacob Priddy's avatar
Jacob Priddy committed
50 51
    public function attempt(Presenter $presenter, array $credentials): void
    {
Jacob Priddy's avatar
Jacob Priddy committed
52 53 54 55
        $email = $credentials['email'] ?? null;
        $password = $credentials['password'] ?? null;

        if (!$email || !$password) {
56
            throw new AuthenticationException();
Jacob Priddy's avatar
Jacob Priddy committed
57 58
        }

Jacob Priddy's avatar
Jacob Priddy committed
59
        $user = $this->users->findByEmail(strtolower($email));
Jacob Priddy's avatar
Jacob Priddy committed
60

Jacob Priddy's avatar
Jacob Priddy committed
61 62
        if (!$user ||
            !$user->getPassword() ||
63
            !$user->isActiveForDate(Carbon::now()) ||
Jacob Priddy's avatar
Jacob Priddy committed
64
            !$user->getPassword()->matches($password)) {
65 66 67
            /*
             * We must have found a user, they must be active, and their password must be correct
             */
68
            throw new AuthenticationException();
Jacob Priddy's avatar
Jacob Priddy committed
69 70
        }

71
        $token = $this->tokens->createLoginToken($user->getId(), $this->salt);
Jacob Priddy's avatar
Jacob Priddy committed
72

73
        $response = new ResponseModel($user, $token->getRaw(), $token->getToken());
Jacob Priddy's avatar
Jacob Priddy committed
74 75 76

        $presenter->present($response);
    }
77

78 79 80
    /**
     * @inheritDoc
     */
Jacob Priddy's avatar
Jacob Priddy committed
81 82
    public function handToSaml(array $options = []): string
    {
Jacob Priddy's avatar
Jacob Priddy committed
83
        return $this->saml->login($options);
84 85 86 87 88
    }

    /**
     * @inheritDoc
     */
Jacob Priddy's avatar
Jacob Priddy committed
89 90
    public function handleSamlLogin(Presenter $presenter): void
    {
Jacob Priddy's avatar
Jacob Priddy committed
91 92 93 94 95 96 97
        $samlUser = $this->saml->handleLogin();

        if (!$samlUser) {
            throw new UserCreationException();
        }

        // First check to see if the user exists in the database.
Jacob Priddy's avatar
Jacob Priddy committed
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
        // We check by emplid as university emails can be recycled,
        // but emplids are always unique. In our db structure,
        // emails are also unique so we get both and have to do some
        // extra checking to make sure we get the right user.
        $user = $this->users->findByEmplid($samlUser->getEmplid());
        $emailUser = $this->users->findByEmail($samlUser->getEmail());

        /*
         * Determine Update Or Create with Incoming SAML User
         *
         * At this point, we have several options.
         *  - Emplid not found, email is found
         *      Update found email user with saml attributes, but clear set password
         *      A person can get in this state by either having same email as somebody previously who left,
         *      or if they were created without having an emplid be put in
         *  - Emplid not found, email not found
         *      Create a brand new user
         *  - Emplid found, email found
         *      Update found emplid user if they are the same, if not, delete email user and update emplid user
         *  - Emplid found, email not found
         *      Update found emplid user
         */
Jacob Priddy's avatar
Jacob Priddy committed
120
        if (!$user) {
Jacob Priddy's avatar
Jacob Priddy committed
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
            if ($emailUser) {
                $user = $this->users->update($emailUser->getId(), new User(
                    $emailUser->getId(),
                    $samlUser->getFirstName(),
                    $samlUser->getLastName(),
                    $samlUser->getDisplayName(),
                    $samlUser->getEmail(),
                    $samlUser->getEmplid(),
                    null,
                    $emailUser->getDoorcode(),
                    $emailUser->getExpiresAt(),
                    $emailUser->getCreatedAt(),
                    $emailUser->getUpdatedAt()
                ));
            } else {
                $user = $this->users->create(new User(
                    0,
                    $samlUser->getFirstName(),
                    $samlUser->getLastName(),
                    $samlUser->getDisplayName(),
                    $samlUser->getEmail(),
142 143 144 145
                    $samlUser->getEmplid(),
                    null,
                    null,
                    Carbon::now()->addYears(5),
Jacob Priddy's avatar
Jacob Priddy committed
146 147
                ));
            }
148
        } else {
Jacob Priddy's avatar
Jacob Priddy committed
149 150 151 152 153 154
            // If they are not the same users, something is weird,
            // so we'll delete the email user and use the emplid user
            if ($emailUser && !$user->hasIdOf($emailUser->getId())) {
                $this->users->delete($emailUser->getId());
            }

155 156 157 158
            $user = $this->users->update($user->getId(), new User(
                $user->getId(),
                $samlUser->getFirstName(),
                $samlUser->getLastName(),
Jacob Priddy's avatar
Jacob Priddy committed
159
                $user->getDisplayName(),
160 161 162 163 164 165 166 167
                $samlUser->getEmail(),
                $samlUser->getEmplid(),
                $user->getPassword(),
                $user->getDoorcode(),
                $user->getExpiresAt(),
                $user->getCreatedAt(),
                $user->getUpdatedAt()
            ));
Jacob Priddy's avatar
Jacob Priddy committed
168 169 170 171 172 173
        }

        if (!$user) {
            throw new UserCreationException();
        }

174 175 176 177
        if (!$user->isActiveForDate(Carbon::now())) {
            throw new AuthorizationException();
        }

178
        $token = $this->tokens->createLoginToken($user->getId(), $this->salt);
Jacob Priddy's avatar
Jacob Priddy committed
179

180
        $response = new ResponseModel($user, $token->getRaw(), $token->getToken());
Jacob Priddy's avatar
Jacob Priddy committed
181 182

        $presenter->present($response);
183 184 185 186 187
    }

    /**
     * @inheritDoc
     */
Jacob Priddy's avatar
Jacob Priddy committed
188 189
    public function samlLogout(?string $token): string
    {
Jacob Priddy's avatar
Jacob Priddy committed
190
        if ($token) {
191
            $this->tokens->invalidateToken(HashedSearchable::hash($this->salt, $token));
Jacob Priddy's avatar
Jacob Priddy committed
192 193 194
        }

        return $this->saml->logout();
195
    }
Jacob Priddy's avatar
Jacob Priddy committed
196
}