Authenticate.php 6.44 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
         */
120
        $newlyCreated = false;
Jacob Priddy's avatar
Jacob Priddy committed
121
        if (!$user) {
Jacob Priddy's avatar
Jacob Priddy committed
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
            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 {
137 138
                // We want to direct new users to the
                $newlyCreated = true;
Jacob Priddy's avatar
Jacob Priddy committed
139 140 141 142 143 144
                $user = $this->users->create(new User(
                    0,
                    $samlUser->getFirstName(),
                    $samlUser->getLastName(),
                    $samlUser->getDisplayName(),
                    $samlUser->getEmail(),
145 146 147 148
                    $samlUser->getEmplid(),
                    null,
                    null,
                    Carbon::now()->addYears(5),
Jacob Priddy's avatar
Jacob Priddy committed
149 150
                ));
            }
151
        } else {
Jacob Priddy's avatar
Jacob Priddy committed
152 153 154 155 156 157
            // 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());
            }

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

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

177 178 179 180
        if (!$user->isActiveForDate(Carbon::now())) {
            throw new AuthorizationException();
        }

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

183
        $response = new ResponseModel($user, $token->getRaw(), $token->getToken(), $newlyCreated);
Jacob Priddy's avatar
Jacob Priddy committed
184 185

        $presenter->present($response);
186 187 188 189 190
    }

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

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