Skip to content

Commit

Permalink
Merge branch 'development' into release
Browse files Browse the repository at this point in the history
  • Loading branch information
ssddanbrown committed Sep 8, 2022
2 parents 1fdf854 + d0dc5e5 commit a11d565
Show file tree
Hide file tree
Showing 282 changed files with 5,589 additions and 2,820 deletions.
6 changes: 5 additions & 1 deletion .env.example.complete
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,11 @@ OIDC_ISSUER_DISCOVER=false
OIDC_PUBLIC_KEY=null
OIDC_AUTH_ENDPOINT=null
OIDC_TOKEN_ENDPOINT=null
OIDC_ADDITIONAL_SCOPES=null
OIDC_DUMP_USER_DETAILS=false
OIDC_USER_TO_GROUPS=false
OIDC_GROUPS_CLAIM=groups
OIDC_REMOVE_FROM_GROUPS=false

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

# Recycle Bin Lifetime
# The number of days that content will remain in the recycle bin before
Expand Down
6 changes: 5 additions & 1 deletion .github/translators.txt
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ Xiphoseer :: German
MerlinSVK (merlinsvk) :: Slovak
Kauê Sena (kaue.sena.ks) :: Portuguese, Brazilian
MatthieuParis :: French
Douradinho :: Portuguese, Brazilian
Douradinho :: Portuguese, Brazilian; Portuguese
Gaku Yaguchi (tama11) :: Japanese
johnroyer :: Chinese Traditional
jackaaa :: Chinese Traditional
Expand Down Expand Up @@ -270,3 +270,7 @@ Nanang Setia Budi (sefidananang) :: Indonesian
Андрей Павлов (andrei.pavlov) :: Russian
Alex Navarro (alex.n.navarro) :: Portuguese, Brazilian
Ji-Hyeon Gim (PotatoGim) :: Korean
Mihai Ochian (soulstorm19) :: Romanian
HeartCore :: German Informal; German
simon.pct :: French
okaeiz :: Persian
16 changes: 15 additions & 1 deletion app/Auth/Access/Oidc/OidcOAuthProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ class OidcOAuthProvider extends AbstractProvider
*/
protected $tokenEndpoint;

/**
* Scopes to use for the OIDC authorization call.
*/
protected array $scopes = ['openid', 'profile', 'email'];

/**
* Returns the base URL for authorizing a client.
*/
Expand All @@ -54,6 +59,15 @@ public function getResourceOwnerDetailsUrl(AccessToken $token): string
return '';
}

/**
* Add an additional scope to this provider upon the default.
*/
public function addScope(string $scope): void
{
$this->scopes[] = $scope;
$this->scopes = array_unique($this->scopes);
}

/**
* Returns the default scopes used by this provider.
*
Expand All @@ -62,7 +76,7 @@ public function getResourceOwnerDetailsUrl(AccessToken $token): string
*/
protected function getDefaultScopes(): array
{
return ['openid', 'profile', 'email'];
return $this->scopes;
}

/**
Expand Down
74 changes: 70 additions & 4 deletions app/Auth/Access/Oidc/OidcService.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
namespace BookStack\Auth\Access\Oidc;

use function auth;
use BookStack\Auth\Access\GroupSyncService;
use BookStack\Auth\Access\LoginService;
use BookStack\Auth\Access\RegistrationService;
use BookStack\Auth\User;
use BookStack\Exceptions\JsonDebugException;
use BookStack\Exceptions\StoppedAuthenticationException;
use BookStack\Exceptions\UserRegistrationException;
use function config;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
Expand All @@ -26,15 +28,21 @@ 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;
}

/**
Expand Down Expand Up @@ -117,10 +125,31 @@ protected function getProviderSettings(): OidcProviderSettings
*/
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);
}

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

/**
* Extract the assigned groups from the id token.
*
* @return string[]
*/
protected function getUserGroups(OidcIdToken $token): array
{
$groupsAttr = $this->config()['groups_claim'];
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
{
Expand All @@ -158,6 +209,7 @@ protected function getUserDetails(OidcIdToken $token): array
'external_id' => $id,
'email' => $token->getClaim('email'),
'name' => $this->getUserDisplayName($token, $id),
'groups' => $this->getUserGroups($token),
];
}

Expand Down Expand Up @@ -209,6 +261,12 @@ protected function processAccessTokenCallback(OidcAccessToken $accessToken, Oidc
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;
Expand All @@ -221,4 +279,12 @@ protected function config(): array
{
return config('oidc');
}

/**
* Check if groups should be synced.
*/
protected function shouldSyncGroups(): bool
{
return $this->config()['user_to_groups'] !== false;
}
}
3 changes: 1 addition & 2 deletions app/Auth/Permissions/PermissionApplicator.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,8 @@ protected function hasEntityPermission(Entity $entity, array $userRoleIds, strin
}

foreach ($chain as $currentEntity) {

if (is_null($currentEntity->restricted)) {
throw new InvalidArgumentException("Entity restricted field used but has not been loaded");
throw new InvalidArgumentException('Entity restricted field used but has not been loaded');
}

if ($currentEntity->restricted) {
Expand Down
1 change: 1 addition & 0 deletions app/Auth/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ public function getAvatar(int $size = 50): string
}

$this->avatarUrl = $avatar;

return $avatar;
}

Expand Down
4 changes: 2 additions & 2 deletions app/Config/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
// The number of revisions to keep in the database.
// Once this limit is reached older revisions will be deleted.
// If set to false then a limit will not be enforced.
'revision_limit' => env('REVISION_LIMIT', 50),
'revision_limit' => env('REVISION_LIMIT', 100),

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

// Locales available
'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'],
'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'],

// Application Fallback Locale
'fallback_locale' => 'en',
Expand Down
12 changes: 12 additions & 0 deletions app/Config/oidc.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,16 @@
// OAuth2 endpoints.
'authorization_endpoint' => env('OIDC_AUTH_ENDPOINT', null),
'token_endpoint' => env('OIDC_TOKEN_ENDPOINT', null),

// Add extra scopes, upon those required, to the OIDC authentication request
// Multiple values can be provided comma seperated.
'additional_scopes' => env('OIDC_ADDITIONAL_SCOPES', null),

// Group sync options
// Enable syncing, upon login, of OIDC groups to BookStack roles
'user_to_groups' => env('OIDC_USER_TO_GROUPS', false),
// Attribute, within a OIDC ID token, to find group names within
'groups_claim' => env('OIDC_GROUPS_CLAIM', 'groups'),
// When syncing groups, remove any groups that no longer match. Otherwise sync only adds new groups.
'remove_from_groups' => env('OIDC_REMOVE_FROM_GROUPS', false),
];
9 changes: 6 additions & 3 deletions app/Console/Commands/RegenerateCommentContent.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use BookStack\Actions\Comment;
use BookStack\Actions\CommentRepo;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;

class RegenerateCommentContent extends Command
{
Expand Down Expand Up @@ -43,9 +44,9 @@ public function __construct(CommentRepo $commentRepo)
*/
public function handle()
{
$connection = \DB::getDefaultConnection();
$connection = DB::getDefaultConnection();
if ($this->option('database') !== null) {
\DB::setDefaultConnection($this->option('database'));
DB::setDefaultConnection($this->option('database'));
}

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

\DB::setDefaultConnection($connection);
DB::setDefaultConnection($connection);
$this->comment('Comment HTML content has been regenerated');

return 0;
}
}
2 changes: 2 additions & 0 deletions app/Console/Commands/RegeneratePermissions.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,7 @@ public function handle()

DB::setDefaultConnection($connection);
$this->comment('Permissions regenerated');

return 0;
}
}
59 changes: 59 additions & 0 deletions app/Console/Commands/RegenerateReferences.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace BookStack\Console\Commands;

use BookStack\References\ReferenceStore;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;

class RegenerateReferences extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'bookstack:regenerate-references {--database= : The database connection to use.}';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Regenerate all the cross-item model reference index';

protected ReferenceStore $references;

/**
* Create a new command instance.
*
* @return void
*/
public function __construct(ReferenceStore $references)
{
$this->references = $references;
parent::__construct();
}

/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$connection = DB::getDefaultConnection();

if ($this->option('database')) {
DB::setDefaultConnection($this->option('database'));
}

$this->references->updateForAllPages();

DB::setDefaultConnection($connection);

$this->comment('References have been regenerated');

return 0;
}
}
2 changes: 1 addition & 1 deletion app/Console/Commands/RegenerateSearch.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace BookStack\Console\Commands;

use BookStack\Entities\Models\Entity;
use BookStack\Entities\Tools\SearchIndex;
use BookStack\Search\SearchIndex;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;

Expand Down
Loading

0 comments on commit a11d565

Please sign in to comment.