]> BookStack Code Mirror - bookstack/blob - app/Access/Guards/LdapSessionGuard.php
Add optional OIDC avatar fetching from the “picture” claim
[bookstack] / app / Access / Guards / LdapSessionGuard.php
1 <?php
2
3 namespace BookStack\Access\Guards;
4
5 use BookStack\Access\LdapService;
6 use BookStack\Access\RegistrationService;
7 use BookStack\Exceptions\JsonDebugException;
8 use BookStack\Exceptions\LdapException;
9 use BookStack\Exceptions\LoginAttemptEmailNeededException;
10 use BookStack\Exceptions\LoginAttemptException;
11 use BookStack\Exceptions\UserRegistrationException;
12 use BookStack\Users\Models\User;
13 use Illuminate\Contracts\Auth\UserProvider;
14 use Illuminate\Contracts\Session\Session;
15 use Illuminate\Support\Str;
16
17 class LdapSessionGuard extends ExternalBaseSessionGuard
18 {
19     protected LdapService $ldapService;
20
21     /**
22      * LdapSessionGuard constructor.
23      */
24     public function __construct(
25         $name,
26         UserProvider $provider,
27         Session $session,
28         LdapService $ldapService,
29         RegistrationService $registrationService
30     ) {
31         $this->ldapService = $ldapService;
32         parent::__construct($name, $provider, $session, $registrationService);
33     }
34
35     /**
36      * Validate a user's credentials.
37      *
38      * @param array $credentials
39      *
40      * @throws LdapException
41      *
42      * @return bool
43      */
44     public function validate(array $credentials = [])
45     {
46         $userDetails = $this->ldapService->getUserDetails($credentials['username']);
47
48         if (isset($userDetails['uid'])) {
49             $this->lastAttempted = $this->provider->retrieveByCredentials([
50                 'external_auth_id' => $userDetails['uid'],
51             ]);
52         }
53
54         return $this->ldapService->validateUserCredentials($userDetails, $credentials['password']);
55     }
56
57     /**
58      * Attempt to authenticate a user using the given credentials.
59      *
60      * @param array $credentials
61      * @param bool  $remember
62      *
63      * @throws LdapException*@throws \BookStack\Exceptions\JsonDebugException
64      * @throws LoginAttemptException
65      * @throws JsonDebugException
66      *
67      * @return bool
68      */
69     public function attempt(array $credentials = [], $remember = false)
70     {
71         $username = $credentials['username'];
72         $userDetails = $this->ldapService->getUserDetails($username);
73
74         $user = null;
75         if (isset($userDetails['uid'])) {
76             $this->lastAttempted = $user = $this->provider->retrieveByCredentials([
77                 'external_auth_id' => $userDetails['uid'],
78             ]);
79         }
80
81         if (!$this->ldapService->validateUserCredentials($userDetails, $credentials['password'])) {
82             return false;
83         }
84
85         if (is_null($user)) {
86             try {
87                 $user = $this->createNewFromLdapAndCreds($userDetails, $credentials);
88             } catch (UserRegistrationException $exception) {
89                 throw new LoginAttemptException($exception->getMessage());
90             }
91         }
92
93         // Sync LDAP groups if required
94         if ($this->ldapService->shouldSyncGroups()) {
95             $this->ldapService->syncGroups($user, $username);
96         }
97
98         // Attach avatar if non-existent
99         if (!$user->avatar()->exists()) {
100             $this->ldapService->saveAndAttachAvatar($user, $userDetails);
101         }
102
103         $this->login($user, $remember);
104
105         return true;
106     }
107
108     /**
109      * Create a new user from the given ldap credentials and login credentials.
110      *
111      * @throws LoginAttemptEmailNeededException
112      * @throws LoginAttemptException
113      * @throws UserRegistrationException
114      */
115     protected function createNewFromLdapAndCreds(array $ldapUserDetails, array $credentials): User
116     {
117         $email = trim($ldapUserDetails['email'] ?: ($credentials['email'] ?? ''));
118
119         if (empty($email)) {
120             throw new LoginAttemptEmailNeededException();
121         }
122
123         $details = [
124             'name'             => $ldapUserDetails['name'],
125             'email'            => $ldapUserDetails['email'] ?: $credentials['email'],
126             'external_auth_id' => $ldapUserDetails['uid'],
127             'password'         => Str::random(32),
128         ];
129
130         $user = $this->registrationService->registerUser($details, null, false);
131         $this->ldapService->saveAndAttachAvatar($user, $ldapUserDetails);
132
133         return $user;
134     }
135 }