1 <?php namespace BookStack\Auth\Access;
3 use BookStack\Auth\Access;
4 use BookStack\Auth\User;
5 use BookStack\Auth\UserRepo;
6 use BookStack\Exceptions\SamlException;
7 use Illuminate\Contracts\Auth\Authenticatable;
12 * Handles any app-specific SAML tasks.
13 * @package BookStack\Services
15 class Saml2Service extends Access\ExternalAuthService
23 * Saml2Service constructor.
24 * @param \BookStack\Auth\UserRepo $userRepo
26 public function __construct(UserRepo $userRepo, User $user)
28 $this->config = config('services.saml');
29 $this->userRepo = $userRepo;
31 $this->enabled = config('saml2_settings.enabled') === true;
35 * Check if groups should be synced.
38 public function shouldSyncGroups()
40 return $this->enabled && $this->config['user_to_groups'] !== false;
43 /** Calculate the display name
44 * @param array $samlAttributes
45 * @param string $defaultValue
48 protected function getUserDisplayName(array $samlAttributes, string $defaultValue)
50 $displayNameAttr = $this->config['display_name_attribute'];
53 foreach ($displayNameAttr as $dnAttr) {
54 $dnComponent = $this->getSamlResponseAttribute($samlAttributes, $dnAttr, null);
55 if ($dnComponent !== null) {
56 $displayName[] = $dnComponent;
60 if (count($displayName) == 0) {
61 $displayName = $defaultValue;
63 $displayName = implode(' ', $displayName);
69 protected function getUserName(array $samlAttributes, string $defaultValue)
71 $userNameAttr = $this->config['user_name_attribute'];
73 if ($userNameAttr === null) {
74 $userName = $defaultValue;
76 $userName = $this->getSamlResponseAttribute($samlAttributes, $userNameAttr, $defaultValue);
83 * Extract the details of a user from a SAML response.
85 * @param $samlAttributes
88 public function getUserDetails($samlID, $samlAttributes)
90 $emailAttr = $this->config['email_attribute'];
91 $userName = $this->getUserName($samlAttributes, $samlID);
95 'name' => $this->getUserDisplayName($samlAttributes, $userName),
97 'email' => $this->getSamlResponseAttribute($samlAttributes, $emailAttr, null),
102 * Get the groups a user is a part of from the SAML response.
103 * @param array $samlAttributes
106 public function getUserGroups($samlAttributes)
108 $groupsAttr = $this->config['group_attribute'];
109 $userGroups = $samlAttributes[$groupsAttr];
111 if (!is_array($userGroups)) {
119 * For an array of strings, return a default for an empty array,
120 * a string for an array with one element and the full array for
121 * more than one element.
124 * @param $defaultValue
127 protected function simplifyValue(array $data, $defaultValue) {
128 switch (count($data)) {
130 $data = $defaultValue;
140 * Get a property from an SAML response.
141 * Handles properties potentially being an array.
142 * @param array $userDetails
143 * @param string $propertyKey
144 * @param $defaultValue
147 protected function getSamlResponseAttribute(array $samlAttributes, string $propertyKey, $defaultValue)
149 if (isset($samlAttributes[$propertyKey])) {
150 $data = $this->simplifyValue($samlAttributes[$propertyKey], $defaultValue);
152 $data = $defaultValue;
159 * Register a user that is authenticated but not
160 * already registered.
161 * @param array $userDetails
164 protected function registerUser($userDetails)
166 // Create an array of the user data to create a new user instance
168 'name' => $userDetails['name'],
169 'email' => $userDetails['email'],
170 'password' => str_random(30),
171 'external_auth_id' => $userDetails['uid'],
172 'email_confirmed' => true,
175 $user = $this->user->forceCreate($userData);
176 $this->userRepo->attachDefaultRole($user);
177 $this->userRepo->downloadAndAssignUserAvatar($user);
182 * Get the user from the database for the specified details.
183 * @param array $userDetails
186 protected function getOrRegisterUser($userDetails)
188 $isRegisterEnabled = config('services.saml.auto_register') === true;
190 ->where('external_auth_id', $userDetails['uid'])
193 if ($user === null && $isRegisterEnabled) {
194 $user = $this->registerUser($userDetails);
201 * Process the SAML response for a user. Login the user when
202 * they exist, optionally registering them automatically.
203 * @param string $samlID
204 * @param array $samlAttributes
205 * @throws SamlException
207 public function processLoginCallback($samlID, $samlAttributes)
209 $userDetails = $this->getUserDetails($samlID, $samlAttributes);
210 $isLoggedIn = auth()->check();
213 throw new SamlException(trans('errors.saml_already_logged_in'), '/login');
215 $user = $this->getOrRegisterUser($userDetails);
216 if ($user === null) {
217 throw new SamlException(trans('errors.saml_user_not_registered', ['name' => $userDetails['uid']]), '/login');
219 $groups = $this->getUserGroups($samlAttributes);
220 $this->syncWithGroups($user, $groups);
221 auth()->login($user);