Merge pull request #5626 from BookStackApp/rubentalstra-development
Review of #5429, OIDC avatar fetching
This commit is contained in:
commit
d149b809b1
|
@ -11,6 +11,7 @@ use BookStack\Exceptions\UserRegistrationException;
|
||||||
use BookStack\Facades\Theme;
|
use BookStack\Facades\Theme;
|
||||||
use BookStack\Http\HttpRequestService;
|
use BookStack\Http\HttpRequestService;
|
||||||
use BookStack\Theming\ThemeEvents;
|
use BookStack\Theming\ThemeEvents;
|
||||||
|
use BookStack\Uploads\UserAvatars;
|
||||||
use BookStack\Users\Models\User;
|
use BookStack\Users\Models\User;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider;
|
use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider;
|
||||||
|
@ -26,7 +27,8 @@ class OidcService
|
||||||
protected RegistrationService $registrationService,
|
protected RegistrationService $registrationService,
|
||||||
protected LoginService $loginService,
|
protected LoginService $loginService,
|
||||||
protected HttpRequestService $http,
|
protected HttpRequestService $http,
|
||||||
protected GroupSyncService $groupService
|
protected GroupSyncService $groupService,
|
||||||
|
protected UserAvatars $userAvatars
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,6 +222,10 @@ class OidcService
|
||||||
throw new OidcException($exception->getMessage());
|
throw new OidcException($exception->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->config()['fetch_avatar'] && !$user->avatar()->exists() && $userDetails->picture) {
|
||||||
|
$this->userAvatars->assignToUserFromUrl($user, $userDetails->picture);
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->shouldSyncGroups()) {
|
if ($this->shouldSyncGroups()) {
|
||||||
$detachExisting = $this->config()['remove_from_groups'];
|
$detachExisting = $this->config()['remove_from_groups'];
|
||||||
$this->groupService->syncUserWithFoundGroups($user, $userDetails->groups ?? [], $detachExisting);
|
$this->groupService->syncUserWithFoundGroups($user, $userDetails->groups ?? [], $detachExisting);
|
||||||
|
|
|
@ -11,6 +11,7 @@ class OidcUserDetails
|
||||||
public ?string $email = null,
|
public ?string $email = null,
|
||||||
public ?string $name = null,
|
public ?string $name = null,
|
||||||
public ?array $groups = null,
|
public ?array $groups = null,
|
||||||
|
public ?string $picture = null,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,15 +41,16 @@ class OidcUserDetails
|
||||||
$this->email = $claims->getClaim('email') ?? $this->email;
|
$this->email = $claims->getClaim('email') ?? $this->email;
|
||||||
$this->name = static::getUserDisplayName($displayNameClaims, $claims) ?? $this->name;
|
$this->name = static::getUserDisplayName($displayNameClaims, $claims) ?? $this->name;
|
||||||
$this->groups = static::getUserGroups($groupsClaim, $claims) ?? $this->groups;
|
$this->groups = static::getUserGroups($groupsClaim, $claims) ?? $this->groups;
|
||||||
|
$this->picture = static::getPicture($claims) ?: $this->picture;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function getUserDisplayName(string $displayNameClaims, ProvidesClaims $token): string
|
protected static function getUserDisplayName(string $displayNameClaims, ProvidesClaims $claims): string
|
||||||
{
|
{
|
||||||
$displayNameClaimParts = explode('|', $displayNameClaims);
|
$displayNameClaimParts = explode('|', $displayNameClaims);
|
||||||
|
|
||||||
$displayName = [];
|
$displayName = [];
|
||||||
foreach ($displayNameClaimParts as $claim) {
|
foreach ($displayNameClaimParts as $claim) {
|
||||||
$component = $token->getClaim(trim($claim)) ?? '';
|
$component = $claims->getClaim(trim($claim)) ?? '';
|
||||||
if ($component !== '') {
|
if ($component !== '') {
|
||||||
$displayName[] = $component;
|
$displayName[] = $component;
|
||||||
}
|
}
|
||||||
|
@ -57,13 +59,13 @@ class OidcUserDetails
|
||||||
return implode(' ', $displayName);
|
return implode(' ', $displayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function getUserGroups(string $groupsClaim, ProvidesClaims $token): ?array
|
protected static function getUserGroups(string $groupsClaim, ProvidesClaims $claims): ?array
|
||||||
{
|
{
|
||||||
if (empty($groupsClaim)) {
|
if (empty($groupsClaim)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$groupsList = Arr::get($token->getAllClaims(), $groupsClaim);
|
$groupsList = Arr::get($claims->getAllClaims(), $groupsClaim);
|
||||||
if (!is_array($groupsList)) {
|
if (!is_array($groupsList)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -72,4 +74,14 @@ class OidcUserDetails
|
||||||
return is_string($val);
|
return is_string($val);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static function getPicture(ProvidesClaims $claims): ?string
|
||||||
|
{
|
||||||
|
$picture = $claims->getClaim('picture');
|
||||||
|
if (is_string($picture) && str_starts_with($picture, 'http')) {
|
||||||
|
return $picture;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,12 @@ return [
|
||||||
// Multiple values can be provided comma seperated.
|
// Multiple values can be provided comma seperated.
|
||||||
'additional_scopes' => env('OIDC_ADDITIONAL_SCOPES', null),
|
'additional_scopes' => env('OIDC_ADDITIONAL_SCOPES', null),
|
||||||
|
|
||||||
|
// Enable fetching of the user's avatar from the 'picture' claim on login.
|
||||||
|
// Will only be fetched if the user doesn't already have an avatar image assigned.
|
||||||
|
// This can be a security risk due to performing server-side fetching (with up to 3 redirects) of
|
||||||
|
// data from external URLs. Only enable if you trust the OIDC auth provider to provide safe URLs for user images.
|
||||||
|
'fetch_avatar' => env('OIDC_FETCH_AVATAR', false),
|
||||||
|
|
||||||
// Group sync options
|
// Group sync options
|
||||||
// Enable syncing, upon login, of OIDC groups to BookStack roles
|
// Enable syncing, upon login, of OIDC groups to BookStack roles
|
||||||
'user_to_groups' => env('OIDC_USER_TO_GROUPS', false),
|
'user_to_groups' => env('OIDC_USER_TO_GROUPS', false),
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace BookStack\Uploads;
|
||||||
use BookStack\Exceptions\HttpFetchException;
|
use BookStack\Exceptions\HttpFetchException;
|
||||||
use BookStack\Http\HttpRequestService;
|
use BookStack\Http\HttpRequestService;
|
||||||
use BookStack\Users\Models\User;
|
use BookStack\Users\Models\User;
|
||||||
|
use BookStack\Util\WebSafeMimeSniffer;
|
||||||
use Exception;
|
use Exception;
|
||||||
use GuzzleHttp\Psr7\Request;
|
use GuzzleHttp\Psr7\Request;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
@ -53,6 +54,33 @@ class UserAvatars
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign a new avatar image to the given user by fetching from a remote URL.
|
||||||
|
*/
|
||||||
|
public function assignToUserFromUrl(User $user, string $avatarUrl): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->destroyAllForUser($user);
|
||||||
|
$imageData = $this->getAvatarImageData($avatarUrl);
|
||||||
|
|
||||||
|
$mime = (new WebSafeMimeSniffer())->sniff($imageData);
|
||||||
|
[$format, $type] = explode('/', $mime, 2);
|
||||||
|
if ($format !== 'image' || !ImageService::isExtensionSupported($type)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$avatar = $this->createAvatarImageFromData($user, $imageData, $type);
|
||||||
|
$user->avatar()->associate($avatar);
|
||||||
|
$user->save();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::error('Failed to save user avatar image from URL', [
|
||||||
|
'exception' => $e->getMessage(),
|
||||||
|
'url' => $avatarUrl,
|
||||||
|
'user_id' => $user->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy all user avatars uploaded to the given user.
|
* Destroy all user avatars uploaded to the given user.
|
||||||
*/
|
*/
|
||||||
|
@ -105,7 +133,7 @@ class UserAvatars
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets an image from url and returns it as a string of image data.
|
* Get an image from a URL and return it as a string of image data.
|
||||||
*
|
*
|
||||||
* @throws HttpFetchException
|
* @throws HttpFetchException
|
||||||
*/
|
*/
|
||||||
|
@ -113,7 +141,19 @@ class UserAvatars
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$client = $this->http->buildClient(5);
|
$client = $this->http->buildClient(5);
|
||||||
$response = $client->sendRequest(new Request('GET', $url));
|
$responseCount = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
$response = $client->sendRequest(new Request('GET', $url));
|
||||||
|
$responseCount++;
|
||||||
|
$isRedirect = ($response->getStatusCode() === 301 || $response->getStatusCode() === 302);
|
||||||
|
$url = $response->getHeader('Location')[0] ?? '';
|
||||||
|
} while ($responseCount < 3 && $isRedirect && is_string($url) && str_starts_with($url, 'http'));
|
||||||
|
|
||||||
|
if ($responseCount === 3) {
|
||||||
|
throw new HttpFetchException("Failed to fetch image, max redirect limit of 3 tries reached. Last fetched URL: {$url}");
|
||||||
|
}
|
||||||
|
|
||||||
if ($response->getStatusCode() !== 200) {
|
if ($response->getStatusCode() !== 200) {
|
||||||
throw new HttpFetchException(trans('errors.cannot_get_image_from_url', ['url' => $url]));
|
throw new HttpFetchException(trans('errors.cannot_get_image_from_url', ['url' => $url]));
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ use Illuminate\Support\Collection;
|
||||||
* @property string $system_name
|
* @property string $system_name
|
||||||
* @property Collection $roles
|
* @property Collection $roles
|
||||||
* @property Collection $mfaValues
|
* @property Collection $mfaValues
|
||||||
|
* @property ?Image $avatar
|
||||||
*/
|
*/
|
||||||
class User extends Model implements AuthenticatableContract, CanResetPasswordContract, Loggable, Sluggable
|
class User extends Model implements AuthenticatableContract, CanResetPasswordContract, Loggable, Sluggable
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace Tests\Auth;
|
||||||
use BookStack\Activity\ActivityType;
|
use BookStack\Activity\ActivityType;
|
||||||
use BookStack\Facades\Theme;
|
use BookStack\Facades\Theme;
|
||||||
use BookStack\Theming\ThemeEvents;
|
use BookStack\Theming\ThemeEvents;
|
||||||
|
use BookStack\Uploads\UserAvatars;
|
||||||
use BookStack\Users\Models\Role;
|
use BookStack\Users\Models\Role;
|
||||||
use BookStack\Users\Models\User;
|
use BookStack\Users\Models\User;
|
||||||
use GuzzleHttp\Psr7\Response;
|
use GuzzleHttp\Psr7\Response;
|
||||||
|
@ -41,6 +42,7 @@ class OidcTest extends TestCase
|
||||||
'oidc.discover' => false,
|
'oidc.discover' => false,
|
||||||
'oidc.dump_user_details' => false,
|
'oidc.dump_user_details' => false,
|
||||||
'oidc.additional_scopes' => '',
|
'oidc.additional_scopes' => '',
|
||||||
|
'odic.fetch_avatar' => false,
|
||||||
'oidc.user_to_groups' => false,
|
'oidc.user_to_groups' => false,
|
||||||
'oidc.groups_claim' => 'group',
|
'oidc.groups_claim' => 'group',
|
||||||
'oidc.remove_from_groups' => false,
|
'oidc.remove_from_groups' => false,
|
||||||
|
@ -457,6 +459,105 @@ class OidcTest extends TestCase
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_user_avatar_fetched_from_picture_on_first_login_if_enabled()
|
||||||
|
{
|
||||||
|
config()->set(['oidc.fetch_avatar' => true]);
|
||||||
|
|
||||||
|
$this->runLogin([
|
||||||
|
'email' => 'avatar@example.com',
|
||||||
|
'picture' => 'https://example.com/my-avatar.jpg',
|
||||||
|
], [
|
||||||
|
new Response(200, ['Content-Type' => 'image/jpeg'], $this->files->jpegImageData())
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = User::query()->where('email', '=', 'avatar@example.com')->first();
|
||||||
|
$this->assertNotNull($user);
|
||||||
|
|
||||||
|
$this->assertTrue($user->avatar()->exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_user_avatar_fetched_for_existing_user_when_no_avatar_already_assigned()
|
||||||
|
{
|
||||||
|
config()->set(['oidc.fetch_avatar' => true]);
|
||||||
|
$editor = $this->users->editor();
|
||||||
|
$editor->external_auth_id = 'benny509';
|
||||||
|
$editor->save();
|
||||||
|
|
||||||
|
$this->assertFalse($editor->avatar()->exists());
|
||||||
|
|
||||||
|
$this->runLogin([
|
||||||
|
'picture' => 'https://example.com/my-avatar.jpg',
|
||||||
|
'sub' => 'benny509',
|
||||||
|
], [
|
||||||
|
new Response(200, ['Content-Type' => 'image/jpeg'], $this->files->jpegImageData())
|
||||||
|
]);
|
||||||
|
|
||||||
|
$editor->refresh();
|
||||||
|
$this->assertTrue($editor->avatar()->exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_user_avatar_not_fetched_if_image_data_format_unknown()
|
||||||
|
{
|
||||||
|
config()->set(['oidc.fetch_avatar' => true]);
|
||||||
|
|
||||||
|
$this->runLogin([
|
||||||
|
'email' => 'avatar-format@example.com',
|
||||||
|
'picture' => 'https://example.com/my-avatar.jpg',
|
||||||
|
], [
|
||||||
|
new Response(200, ['Content-Type' => 'image/jpeg'], str_repeat('abc123', 5))
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = User::query()->where('email', '=', 'avatar-format@example.com')->first();
|
||||||
|
$this->assertNotNull($user);
|
||||||
|
|
||||||
|
$this->assertFalse($user->avatar()->exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_user_avatar_not_fetched_when_avatar_already_assigned()
|
||||||
|
{
|
||||||
|
config()->set(['oidc.fetch_avatar' => true]);
|
||||||
|
$editor = $this->users->editor();
|
||||||
|
$editor->external_auth_id = 'benny509';
|
||||||
|
$editor->save();
|
||||||
|
|
||||||
|
$avatars = $this->app->make(UserAvatars::class);
|
||||||
|
$originalImageData = $this->files->pngImageData();
|
||||||
|
$avatars->assignToUserFromExistingData($editor, $originalImageData, 'png');
|
||||||
|
|
||||||
|
$this->runLogin([
|
||||||
|
'picture' => 'https://example.com/my-avatar.jpg',
|
||||||
|
'sub' => 'benny509',
|
||||||
|
], [
|
||||||
|
new Response(200, ['Content-Type' => 'image/jpeg'], $this->files->jpegImageData())
|
||||||
|
]);
|
||||||
|
|
||||||
|
$editor->refresh();
|
||||||
|
$newAvatarData = file_get_contents($this->files->relativeToFullPath($editor->avatar->path));
|
||||||
|
$this->assertEquals($originalImageData, $newAvatarData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_user_avatar_fetch_follows_up_to_three_redirects()
|
||||||
|
{
|
||||||
|
config()->set(['oidc.fetch_avatar' => true]);
|
||||||
|
|
||||||
|
$logger = $this->withTestLogger();
|
||||||
|
|
||||||
|
$this->runLogin([
|
||||||
|
'email' => 'avatar@example.com',
|
||||||
|
'picture' => 'https://example.com/my-avatar.jpg',
|
||||||
|
], [
|
||||||
|
new Response(302, ['Location' => 'https://example.com/a']),
|
||||||
|
new Response(302, ['Location' => 'https://example.com/b']),
|
||||||
|
new Response(302, ['Location' => 'https://example.com/c']),
|
||||||
|
new Response(302, ['Location' => 'https://example.com/d']),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = User::query()->where('email', '=', 'avatar@example.com')->first();
|
||||||
|
$this->assertFalse($user->avatar()->exists());
|
||||||
|
|
||||||
|
$this->assertStringContainsString('"Failed to fetch image, max redirect limit of 3 tries reached. Last fetched URL: https://example.com/c"', $logger->getRecords()[0]->formatted);
|
||||||
|
}
|
||||||
|
|
||||||
public function test_login_group_sync()
|
public function test_login_group_sync()
|
||||||
{
|
{
|
||||||
config()->set([
|
config()->set([
|
||||||
|
|
|
@ -60,6 +60,14 @@ class FileProvider
|
||||||
return file_get_contents($this->testFilePath('test-image.png'));
|
return file_get_contents($this->testFilePath('test-image.png'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get raw data for a Jpeg image test file.
|
||||||
|
*/
|
||||||
|
public function jpegImageData(): string
|
||||||
|
{
|
||||||
|
return file_get_contents($this->testFilePath('test-image.jpg'));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the expected relative path for an uploaded image of the given type and filename.
|
* Get the expected relative path for an uploaded image of the given type and filename.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -184,7 +184,7 @@ class EntityPermissionsTest extends TestCase
|
||||||
|
|
||||||
$this->get($bookUrl . '/edit')->assertRedirect('/');
|
$this->get($bookUrl . '/edit')->assertRedirect('/');
|
||||||
$this->get('/')->assertSee('You do not have permission');
|
$this->get('/')->assertSee('You do not have permission');
|
||||||
$this->get($bookPage->getUrl() . '/edit')->assertRedirect('/');
|
$this->get($bookPage->getUrl() . '/edit')->assertRedirect($bookPage->getUrl());
|
||||||
$this->get('/')->assertSee('You do not have permission');
|
$this->get('/')->assertSee('You do not have permission');
|
||||||
$this->get($bookChapter->getUrl() . '/edit')->assertRedirect('/');
|
$this->get($bookChapter->getUrl() . '/edit')->assertRedirect('/');
|
||||||
$this->get('/')->assertSee('You do not have permission');
|
$this->get('/')->assertSee('You do not have permission');
|
||||||
|
@ -282,7 +282,7 @@ class EntityPermissionsTest extends TestCase
|
||||||
|
|
||||||
$this->get($chapterUrl . '/edit')->assertRedirect('/');
|
$this->get($chapterUrl . '/edit')->assertRedirect('/');
|
||||||
$this->get('/')->assertSee('You do not have permission');
|
$this->get('/')->assertSee('You do not have permission');
|
||||||
$this->get($chapterPage->getUrl() . '/edit')->assertRedirect('/');
|
$this->get($chapterPage->getUrl() . '/edit')->assertRedirect($chapterPage->getUrl());
|
||||||
$this->get('/')->assertSee('You do not have permission');
|
$this->get('/')->assertSee('You do not have permission');
|
||||||
|
|
||||||
$this->setRestrictionsForTestRoles($chapter, ['view', 'update']);
|
$this->setRestrictionsForTestRoles($chapter, ['view', 'update']);
|
||||||
|
@ -341,7 +341,7 @@ class EntityPermissionsTest extends TestCase
|
||||||
|
|
||||||
$this->setRestrictionsForTestRoles($page, ['view', 'delete']);
|
$this->setRestrictionsForTestRoles($page, ['view', 'delete']);
|
||||||
|
|
||||||
$this->get($pageUrl . '/edit')->assertRedirect('/');
|
$this->get($pageUrl . '/edit')->assertRedirect($pageUrl);
|
||||||
$this->get('/')->assertSee('You do not have permission');
|
$this->get('/')->assertSee('You do not have permission');
|
||||||
|
|
||||||
$this->setRestrictionsForTestRoles($page, ['view', 'update']);
|
$this->setRestrictionsForTestRoles($page, ['view', 'update']);
|
||||||
|
@ -565,7 +565,7 @@ class EntityPermissionsTest extends TestCase
|
||||||
|
|
||||||
$this->get($bookUrl . '/edit')->assertRedirect('/');
|
$this->get($bookUrl . '/edit')->assertRedirect('/');
|
||||||
$this->get('/')->assertSee('You do not have permission');
|
$this->get('/')->assertSee('You do not have permission');
|
||||||
$this->get($bookPage->getUrl() . '/edit')->assertRedirect('/');
|
$this->get($bookPage->getUrl() . '/edit')->assertRedirect($bookPage->getUrl());
|
||||||
$this->get('/')->assertSee('You do not have permission');
|
$this->get('/')->assertSee('You do not have permission');
|
||||||
$this->get($bookChapter->getUrl() . '/edit')->assertRedirect('/');
|
$this->get($bookChapter->getUrl() . '/edit')->assertRedirect('/');
|
||||||
$this->get('/')->assertSee('You do not have permission');
|
$this->get('/')->assertSee('You do not have permission');
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace Tests\Permissions;
|
namespace Tests\Permissions;
|
||||||
|
|
||||||
use BookStack\Activity\ActivityType;
|
|
||||||
use BookStack\Activity\Models\Comment;
|
use BookStack\Activity\Models\Comment;
|
||||||
use BookStack\Entities\Models\Book;
|
use BookStack\Entities\Models\Book;
|
||||||
use BookStack\Entities\Models\Bookshelf;
|
use BookStack\Entities\Models\Bookshelf;
|
||||||
|
@ -10,7 +9,6 @@ use BookStack\Entities\Models\Chapter;
|
||||||
use BookStack\Entities\Models\Entity;
|
use BookStack\Entities\Models\Entity;
|
||||||
use BookStack\Entities\Models\Page;
|
use BookStack\Entities\Models\Page;
|
||||||
use BookStack\Uploads\Image;
|
use BookStack\Uploads\Image;
|
||||||
use BookStack\Users\Models\Role;
|
|
||||||
use BookStack\Users\Models\User;
|
use BookStack\Users\Models\User;
|
||||||
use Illuminate\Testing\TestResponse;
|
use Illuminate\Testing\TestResponse;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
@ -152,10 +150,14 @@ class RolePermissionsTest extends TestCase
|
||||||
/**
|
/**
|
||||||
* Check a standard entity access permission.
|
* Check a standard entity access permission.
|
||||||
*/
|
*/
|
||||||
private function checkAccessPermission(string $permission, array $accessUrls = [], array $visibles = [])
|
private function checkAccessPermission(
|
||||||
{
|
string $permission,
|
||||||
|
array $accessUrls = [],
|
||||||
|
array $visibles = [],
|
||||||
|
string $expectedRedirectUri = '/',
|
||||||
|
) {
|
||||||
foreach ($accessUrls as $url) {
|
foreach ($accessUrls as $url) {
|
||||||
$this->actingAs($this->user)->get($url)->assertRedirect('/');
|
$this->actingAs($this->user)->get($url)->assertRedirect($expectedRedirectUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($visibles as $url => $text) {
|
foreach ($visibles as $url => $text) {
|
||||||
|
@ -535,11 +537,11 @@ class RolePermissionsTest extends TestCase
|
||||||
$ownPage->getUrl() . '/edit',
|
$ownPage->getUrl() . '/edit',
|
||||||
], [
|
], [
|
||||||
$ownPage->getUrl() => 'Edit',
|
$ownPage->getUrl() => 'Edit',
|
||||||
]);
|
], $ownPage->getUrl());
|
||||||
|
|
||||||
$resp = $this->get($otherPage->getUrl());
|
$resp = $this->get($otherPage->getUrl());
|
||||||
$this->withHtml($resp)->assertElementNotContains('.action-buttons', 'Edit');
|
$this->withHtml($resp)->assertElementNotContains('.action-buttons', 'Edit');
|
||||||
$this->get($otherPage->getUrl() . '/edit')->assertRedirect('/');
|
$this->get($otherPage->getUrl() . '/edit')->assertRedirect($otherPage->getUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_page_edit_all_permission()
|
public function test_page_edit_all_permission()
|
||||||
|
@ -550,7 +552,7 @@ class RolePermissionsTest extends TestCase
|
||||||
$otherPage->getUrl('/edit'),
|
$otherPage->getUrl('/edit'),
|
||||||
], [
|
], [
|
||||||
$otherPage->getUrl() => 'Edit',
|
$otherPage->getUrl() => 'Edit',
|
||||||
]);
|
], $otherPage->getUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_page_delete_own_permission()
|
public function test_page_delete_own_permission()
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 268 B |
Loading…
Reference in New Issue