]> BookStack Code Mirror - bookstack/blobdiff - app/Auth/Access/Oidc/OidcService.php
Added OIDC group sync functionality
[bookstack] / app / Auth / Access / Oidc / OidcService.php
index eeacdb732357dc135075ef32827b3cfb2fafe74f..3443baaf619c1c6f8cc488f5d7f729417d4d3544 100644 (file)
@@ -2,6 +2,8 @@
 
 namespace BookStack\Auth\Access\Oidc;
 
+use BookStack\Auth\Access\GroupSyncService;
+use Illuminate\Support\Arr;
 use function auth;
 use BookStack\Auth\Access\LoginService;
 use BookStack\Auth\Access\RegistrationService;
@@ -26,15 +28,22 @@ class OidcService
     protected RegistrationService $registrationService;
     protected LoginService $loginService;
     protected HttpClient $httpClient;
+    protected GroupSyncService $groupService;
 
     /**
      * OpenIdService constructor.
      */
-    public function __construct(RegistrationService $registrationService, LoginService $loginService, HttpClient $httpClient)
+    public function __construct(
+        RegistrationService $registrationService,
+        LoginService        $loginService,
+        HttpClient          $httpClient,
+        GroupSyncService    $groupService
+    )
     {
         $this->registrationService = $registrationService;
         $this->loginService = $loginService;
         $this->httpClient = $httpClient;
+        $this->groupService = $groupService;
     }
 
     /**
@@ -117,10 +126,31 @@ class OidcService
      */
     protected function getProvider(OidcProviderSettings $settings): OidcOAuthProvider
     {
-        return new OidcOAuthProvider($settings->arrayForProvider(), [
+        $provider = new OidcOAuthProvider($settings->arrayForProvider(), [
             'httpClient'     => $this->httpClient,
             'optionProvider' => new HttpBasicAuthOptionProvider(),
         ]);
+
+        foreach ($this->getAdditionalScopes() as $scope) {
+            $provider->addScope($scope);
+        }
+
+        return $provider;
+    }
+
+    /**
+     * Get any user-defined addition/custom scopes to apply to the authentication request.
+     *
+     * @return string[]
+     */
+    protected function getAdditionalScopes(): array
+    {
+        $scopeConfig = $this->config()['additional_scopes'] ?: '';
+
+        $scopeArr = explode(',', $scopeConfig);
+        $scopeArr = array_map(fn(string $scope) => trim($scope), $scopeArr);
+
+        return array_filter($scopeArr);
     }
 
     /**
@@ -145,10 +175,32 @@ class OidcService
         return implode(' ', $displayName);
     }
 
+    /**
+     * Extract the assigned groups from the id token.
+     *
+     * @return string[]
+     */
+    protected function getUserGroups(OidcIdToken $token): array
+    {
+        $groupsAttr = $this->config()['group_attribute'];
+        if (empty($groupsAttr)) {
+            return [];
+        }
+
+        $groupsList = Arr::get($token->getAllClaims(), $groupsAttr);
+        if (!is_array($groupsList)) {
+            return [];
+        }
+
+        return array_values(array_filter($groupsList, function($val) {
+            return is_string($val);
+        }));
+    }
+
     /**
      * Extract the details of a user from an ID token.
      *
-     * @return array{name: string, email: string, external_id: string}
+     * @return array{name: string, email: string, external_id: string, groups: string[]}
      */
     protected function getUserDetails(OidcIdToken $token): array
     {
@@ -158,6 +210,7 @@ class OidcService
             'external_id' => $id,
             'email'       => $token->getClaim('email'),
             'name'        => $this->getUserDisplayName($token, $id),
+            'groups'      => $this->getUserGroups($token),
         ];
     }
 
@@ -209,6 +262,12 @@ class OidcService
             throw new OidcException($exception->getMessage());
         }
 
+        if ($this->shouldSyncGroups()) {
+            $groups = $userDetails['groups'];
+            $detachExisting = $this->config()['remove_from_groups'];
+            $this->groupService->syncUserWithFoundGroups($user, $groups, $detachExisting);
+        }
+
         $this->loginService->login($user, 'oidc');
 
         return $user;
@@ -221,4 +280,12 @@ class OidcService
     {
         return config('oidc');
     }
+
+    /**
+     * Check if groups should be synced.
+     */
+    protected function shouldSyncGroups(): bool
+    {
+        return $this->config()['user_to_groups'] !== false;
+    }
 }