3
3
namespace BookStack \Auth \Access \Oidc ;
4
4
5
5
use function auth ;
6
+ use BookStack \Auth \Access \GroupSyncService ;
6
7
use BookStack \Auth \Access \LoginService ;
7
8
use BookStack \Auth \Access \RegistrationService ;
8
9
use BookStack \Auth \User ;
9
10
use BookStack \Exceptions \JsonDebugException ;
10
11
use BookStack \Exceptions \StoppedAuthenticationException ;
11
12
use BookStack \Exceptions \UserRegistrationException ;
12
13
use function config ;
14
+ use Illuminate \Support \Arr ;
13
15
use Illuminate \Support \Facades \Cache ;
14
16
use League \OAuth2 \Client \OptionProvider \HttpBasicAuthOptionProvider ;
15
17
use League \OAuth2 \Client \Provider \Exception \IdentityProviderException ;
@@ -26,15 +28,21 @@ class OidcService
26
28
protected RegistrationService $ registrationService ;
27
29
protected LoginService $ loginService ;
28
30
protected HttpClient $ httpClient ;
31
+ protected GroupSyncService $ groupService ;
29
32
30
33
/**
31
34
* OpenIdService constructor.
32
35
*/
33
- public function __construct (RegistrationService $ registrationService , LoginService $ loginService , HttpClient $ httpClient )
34
- {
36
+ public function __construct (
37
+ RegistrationService $ registrationService ,
38
+ LoginService $ loginService ,
39
+ HttpClient $ httpClient ,
40
+ GroupSyncService $ groupService
41
+ ) {
35
42
$ this ->registrationService = $ registrationService ;
36
43
$ this ->loginService = $ loginService ;
37
44
$ this ->httpClient = $ httpClient ;
45
+ $ this ->groupService = $ groupService ;
38
46
}
39
47
40
48
/**
@@ -117,10 +125,31 @@ protected function getProviderSettings(): OidcProviderSettings
117
125
*/
118
126
protected function getProvider (OidcProviderSettings $ settings ): OidcOAuthProvider
119
127
{
120
- return new OidcOAuthProvider ($ settings ->arrayForProvider (), [
128
+ $ provider = new OidcOAuthProvider ($ settings ->arrayForProvider (), [
121
129
'httpClient ' => $ this ->httpClient ,
122
130
'optionProvider ' => new HttpBasicAuthOptionProvider (),
123
131
]);
132
+
133
+ foreach ($ this ->getAdditionalScopes () as $ scope ) {
134
+ $ provider ->addScope ($ scope );
135
+ }
136
+
137
+ return $ provider ;
138
+ }
139
+
140
+ /**
141
+ * Get any user-defined addition/custom scopes to apply to the authentication request.
142
+ *
143
+ * @return string[]
144
+ */
145
+ protected function getAdditionalScopes (): array
146
+ {
147
+ $ scopeConfig = $ this ->config ()['additional_scopes ' ] ?: '' ;
148
+
149
+ $ scopeArr = explode (', ' , $ scopeConfig );
150
+ $ scopeArr = array_map (fn (string $ scope ) => trim ($ scope ), $ scopeArr );
151
+
152
+ return array_filter ($ scopeArr );
124
153
}
125
154
126
155
/**
@@ -145,10 +174,32 @@ protected function getUserDisplayName(OidcIdToken $token, string $defaultValue):
145
174
return implode (' ' , $ displayName );
146
175
}
147
176
177
+ /**
178
+ * Extract the assigned groups from the id token.
179
+ *
180
+ * @return string[]
181
+ */
182
+ protected function getUserGroups (OidcIdToken $ token ): array
183
+ {
184
+ $ groupsAttr = $ this ->config ()['groups_claim ' ];
185
+ if (empty ($ groupsAttr )) {
186
+ return [];
187
+ }
188
+
189
+ $ groupsList = Arr::get ($ token ->getAllClaims (), $ groupsAttr );
190
+ if (!is_array ($ groupsList )) {
191
+ return [];
192
+ }
193
+
194
+ return array_values (array_filter ($ groupsList , function ($ val ) {
195
+ return is_string ($ val );
196
+ }));
197
+ }
198
+
148
199
/**
149
200
* Extract the details of a user from an ID token.
150
201
*
151
- * @return array{name: string, email: string, external_id: string}
202
+ * @return array{name: string, email: string, external_id: string, groups: string[] }
152
203
*/
153
204
protected function getUserDetails (OidcIdToken $ token ): array
154
205
{
@@ -158,6 +209,7 @@ protected function getUserDetails(OidcIdToken $token): array
158
209
'external_id ' => $ id ,
159
210
'email ' => $ token ->getClaim ('email ' ),
160
211
'name ' => $ this ->getUserDisplayName ($ token , $ id ),
212
+ 'groups ' => $ this ->getUserGroups ($ token ),
161
213
];
162
214
}
163
215
@@ -209,6 +261,12 @@ protected function processAccessTokenCallback(OidcAccessToken $accessToken, Oidc
209
261
throw new OidcException ($ exception ->getMessage ());
210
262
}
211
263
264
+ if ($ this ->shouldSyncGroups ()) {
265
+ $ groups = $ userDetails ['groups ' ];
266
+ $ detachExisting = $ this ->config ()['remove_from_groups ' ];
267
+ $ this ->groupService ->syncUserWithFoundGroups ($ user , $ groups , $ detachExisting );
268
+ }
269
+
212
270
$ this ->loginService ->login ($ user , 'oidc ' );
213
271
214
272
return $ user ;
@@ -221,4 +279,12 @@ protected function config(): array
221
279
{
222
280
return config ('oidc ' );
223
281
}
282
+
283
+ /**
284
+ * Check if groups should be synced.
285
+ */
286
+ protected function shouldSyncGroups (): bool
287
+ {
288
+ return $ this ->config ()['user_to_groups ' ] !== false ;
289
+ }
224
290
}
0 commit comments