Skip to content

Commit a11d565

Browse files
committed
Merge branch 'development' into release
2 parents 1fdf854 + d0dc5e5 commit a11d565

File tree

282 files changed

+5589
-2820
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

282 files changed

+5589
-2820
lines changed

.env.example.complete

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,11 @@ OIDC_ISSUER_DISCOVER=false
263263
OIDC_PUBLIC_KEY=null
264264
OIDC_AUTH_ENDPOINT=null
265265
OIDC_TOKEN_ENDPOINT=null
266+
OIDC_ADDITIONAL_SCOPES=null
266267
OIDC_DUMP_USER_DETAILS=false
268+
OIDC_USER_TO_GROUPS=false
269+
OIDC_GROUPS_CLAIM=groups
270+
OIDC_REMOVE_FROM_GROUPS=false
267271

268272
# Disable default third-party services such as Gravatar and Draw.IO
269273
# Service-specific options will override this option
@@ -295,7 +299,7 @@ APP_DEFAULT_DARK_MODE=false
295299
# Page revision limit
296300
# Number of page revisions to keep in the system before deleting old revisions.
297301
# If set to 'false' a limit will not be enforced.
298-
REVISION_LIMIT=50
302+
REVISION_LIMIT=100
299303

300304
# Recycle Bin Lifetime
301305
# The number of days that content will remain in the recycle bin before

.github/translators.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ Xiphoseer :: German
138138
MerlinSVK (merlinsvk) :: Slovak
139139
Kauê Sena (kaue.sena.ks) :: Portuguese, Brazilian
140140
MatthieuParis :: French
141-
Douradinho :: Portuguese, Brazilian
141+
Douradinho :: Portuguese, Brazilian; Portuguese
142142
Gaku Yaguchi (tama11) :: Japanese
143143
johnroyer :: Chinese Traditional
144144
jackaaa :: Chinese Traditional
@@ -270,3 +270,7 @@ Nanang Setia Budi (sefidananang) :: Indonesian
270270
Андрей Павлов (andrei.pavlov) :: Russian
271271
Alex Navarro (alex.n.navarro) :: Portuguese, Brazilian
272272
Ji-Hyeon Gim (PotatoGim) :: Korean
273+
Mihai Ochian (soulstorm19) :: Romanian
274+
HeartCore :: German Informal; German
275+
simon.pct :: French
276+
okaeiz :: Persian

app/Auth/Access/Oidc/OidcOAuthProvider.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ class OidcOAuthProvider extends AbstractProvider
3030
*/
3131
protected $tokenEndpoint;
3232

33+
/**
34+
* Scopes to use for the OIDC authorization call.
35+
*/
36+
protected array $scopes = ['openid', 'profile', 'email'];
37+
3338
/**
3439
* Returns the base URL for authorizing a client.
3540
*/
@@ -54,6 +59,15 @@ public function getResourceOwnerDetailsUrl(AccessToken $token): string
5459
return '';
5560
}
5661

62+
/**
63+
* Add an additional scope to this provider upon the default.
64+
*/
65+
public function addScope(string $scope): void
66+
{
67+
$this->scopes[] = $scope;
68+
$this->scopes = array_unique($this->scopes);
69+
}
70+
5771
/**
5872
* Returns the default scopes used by this provider.
5973
*
@@ -62,7 +76,7 @@ public function getResourceOwnerDetailsUrl(AccessToken $token): string
6276
*/
6377
protected function getDefaultScopes(): array
6478
{
65-
return ['openid', 'profile', 'email'];
79+
return $this->scopes;
6680
}
6781

6882
/**

app/Auth/Access/Oidc/OidcService.php

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
namespace BookStack\Auth\Access\Oidc;
44

55
use function auth;
6+
use BookStack\Auth\Access\GroupSyncService;
67
use BookStack\Auth\Access\LoginService;
78
use BookStack\Auth\Access\RegistrationService;
89
use BookStack\Auth\User;
910
use BookStack\Exceptions\JsonDebugException;
1011
use BookStack\Exceptions\StoppedAuthenticationException;
1112
use BookStack\Exceptions\UserRegistrationException;
1213
use function config;
14+
use Illuminate\Support\Arr;
1315
use Illuminate\Support\Facades\Cache;
1416
use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider;
1517
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
@@ -26,15 +28,21 @@ class OidcService
2628
protected RegistrationService $registrationService;
2729
protected LoginService $loginService;
2830
protected HttpClient $httpClient;
31+
protected GroupSyncService $groupService;
2932

3033
/**
3134
* OpenIdService constructor.
3235
*/
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+
) {
3542
$this->registrationService = $registrationService;
3643
$this->loginService = $loginService;
3744
$this->httpClient = $httpClient;
45+
$this->groupService = $groupService;
3846
}
3947

4048
/**
@@ -117,10 +125,31 @@ protected function getProviderSettings(): OidcProviderSettings
117125
*/
118126
protected function getProvider(OidcProviderSettings $settings): OidcOAuthProvider
119127
{
120-
return new OidcOAuthProvider($settings->arrayForProvider(), [
128+
$provider = new OidcOAuthProvider($settings->arrayForProvider(), [
121129
'httpClient' => $this->httpClient,
122130
'optionProvider' => new HttpBasicAuthOptionProvider(),
123131
]);
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);
124153
}
125154

126155
/**
@@ -145,10 +174,32 @@ protected function getUserDisplayName(OidcIdToken $token, string $defaultValue):
145174
return implode(' ', $displayName);
146175
}
147176

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+
148199
/**
149200
* Extract the details of a user from an ID token.
150201
*
151-
* @return array{name: string, email: string, external_id: string}
202+
* @return array{name: string, email: string, external_id: string, groups: string[]}
152203
*/
153204
protected function getUserDetails(OidcIdToken $token): array
154205
{
@@ -158,6 +209,7 @@ protected function getUserDetails(OidcIdToken $token): array
158209
'external_id' => $id,
159210
'email' => $token->getClaim('email'),
160211
'name' => $this->getUserDisplayName($token, $id),
212+
'groups' => $this->getUserGroups($token),
161213
];
162214
}
163215

@@ -209,6 +261,12 @@ protected function processAccessTokenCallback(OidcAccessToken $accessToken, Oidc
209261
throw new OidcException($exception->getMessage());
210262
}
211263

264+
if ($this->shouldSyncGroups()) {
265+
$groups = $userDetails['groups'];
266+
$detachExisting = $this->config()['remove_from_groups'];
267+
$this->groupService->syncUserWithFoundGroups($user, $groups, $detachExisting);
268+
}
269+
212270
$this->loginService->login($user, 'oidc');
213271

214272
return $user;
@@ -221,4 +279,12 @@ protected function config(): array
221279
{
222280
return config('oidc');
223281
}
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+
}
224290
}

app/Auth/Permissions/PermissionApplicator.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,8 @@ protected function hasEntityPermission(Entity $entity, array $userRoleIds, strin
7474
}
7575

7676
foreach ($chain as $currentEntity) {
77-
7877
if (is_null($currentEntity->restricted)) {
79-
throw new InvalidArgumentException("Entity restricted field used but has not been loaded");
78+
throw new InvalidArgumentException('Entity restricted field used but has not been loaded');
8079
}
8180

8281
if ($currentEntity->restricted) {

app/Auth/User.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ public function getAvatar(int $size = 50): string
249249
}
250250

251251
$this->avatarUrl = $avatar;
252+
252253
return $avatar;
253254
}
254255

app/Config/app.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
// The number of revisions to keep in the database.
2323
// Once this limit is reached older revisions will be deleted.
2424
// If set to false then a limit will not be enforced.
25-
'revision_limit' => env('REVISION_LIMIT', 50),
25+
'revision_limit' => env('REVISION_LIMIT', 100),
2626

2727
// The number of days that content will remain in the recycle bin before
2828
// being considered for auto-removal. It is not a guarantee that content will
@@ -75,7 +75,7 @@
7575
'locale' => env('APP_LANG', 'en'),
7676

7777
// Locales available
78-
'locales' => ['en', 'ar', 'bg', 'bs', 'ca', 'cs', 'cy', 'da', 'de', 'de_informal', 'es', 'es_AR', 'et', 'eu', 'fa', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'lt', 'lv', 'nl', 'nb', 'pt', 'pt_BR', 'sk', 'sl', 'sv', 'pl', 'ru', 'th', 'tr', 'uk', 'uz', 'vi', 'zh_CN', 'zh_TW'],
78+
'locales' => ['en', 'ar', 'bg', 'bs', 'ca', 'cs', 'cy', 'da', 'de', 'de_informal', 'es', 'es_AR', 'et', 'eu', 'fa', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'lt', 'lv', 'nl', 'nb', 'pt', 'pt_BR', 'sk', 'sl', 'sv', 'pl', 'ro', 'ru', 'tr', 'uk', 'uz', 'vi', 'zh_CN', 'zh_TW'],
7979

8080
// Application Fallback Locale
8181
'fallback_locale' => 'en',

app/Config/oidc.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,16 @@
3232
// OAuth2 endpoints.
3333
'authorization_endpoint' => env('OIDC_AUTH_ENDPOINT', null),
3434
'token_endpoint' => env('OIDC_TOKEN_ENDPOINT', null),
35+
36+
// Add extra scopes, upon those required, to the OIDC authentication request
37+
// Multiple values can be provided comma seperated.
38+
'additional_scopes' => env('OIDC_ADDITIONAL_SCOPES', null),
39+
40+
// Group sync options
41+
// Enable syncing, upon login, of OIDC groups to BookStack roles
42+
'user_to_groups' => env('OIDC_USER_TO_GROUPS', false),
43+
// Attribute, within a OIDC ID token, to find group names within
44+
'groups_claim' => env('OIDC_GROUPS_CLAIM', 'groups'),
45+
// When syncing groups, remove any groups that no longer match. Otherwise sync only adds new groups.
46+
'remove_from_groups' => env('OIDC_REMOVE_FROM_GROUPS', false),
3547
];

app/Console/Commands/RegenerateCommentContent.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use BookStack\Actions\Comment;
66
use BookStack\Actions\CommentRepo;
77
use Illuminate\Console\Command;
8+
use Illuminate\Support\Facades\DB;
89

910
class RegenerateCommentContent extends Command
1011
{
@@ -43,9 +44,9 @@ public function __construct(CommentRepo $commentRepo)
4344
*/
4445
public function handle()
4546
{
46-
$connection = \DB::getDefaultConnection();
47+
$connection = DB::getDefaultConnection();
4748
if ($this->option('database') !== null) {
48-
\DB::setDefaultConnection($this->option('database'));
49+
DB::setDefaultConnection($this->option('database'));
4950
}
5051

5152
Comment::query()->chunk(100, function ($comments) {
@@ -55,7 +56,9 @@ public function handle()
5556
}
5657
});
5758

58-
\DB::setDefaultConnection($connection);
59+
DB::setDefaultConnection($connection);
5960
$this->comment('Comment HTML content has been regenerated');
61+
62+
return 0;
6063
}
6164
}

app/Console/Commands/RegeneratePermissions.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,7 @@ public function handle()
5050

5151
DB::setDefaultConnection($connection);
5252
$this->comment('Permissions regenerated');
53+
54+
return 0;
5355
}
5456
}

0 commit comments

Comments
 (0)