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\Http\HttpRequestService;
 | 
			
		||||
use BookStack\Theming\ThemeEvents;
 | 
			
		||||
use BookStack\Uploads\UserAvatars;
 | 
			
		||||
use BookStack\Users\Models\User;
 | 
			
		||||
use Illuminate\Support\Facades\Cache;
 | 
			
		||||
use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider;
 | 
			
		||||
| 
						 | 
				
			
			@ -26,7 +27,8 @@ class OidcService
 | 
			
		|||
        protected RegistrationService $registrationService,
 | 
			
		||||
        protected LoginService $loginService,
 | 
			
		||||
        protected HttpRequestService $http,
 | 
			
		||||
        protected GroupSyncService $groupService
 | 
			
		||||
        protected GroupSyncService $groupService,
 | 
			
		||||
        protected UserAvatars $userAvatars
 | 
			
		||||
    ) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -220,6 +222,10 @@ class OidcService
 | 
			
		|||
            throw new OidcException($exception->getMessage());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($this->config()['fetch_avatar'] && !$user->avatar()->exists() && $userDetails->picture) {
 | 
			
		||||
            $this->userAvatars->assignToUserFromUrl($user, $userDetails->picture);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($this->shouldSyncGroups()) {
 | 
			
		||||
            $detachExisting = $this->config()['remove_from_groups'];
 | 
			
		||||
            $this->groupService->syncUserWithFoundGroups($user, $userDetails->groups ?? [], $detachExisting);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,7 @@ class OidcUserDetails
 | 
			
		|||
        public ?string $email = null,
 | 
			
		||||
        public ?string $name = null,
 | 
			
		||||
        public ?array $groups = null,
 | 
			
		||||
        public ?string $picture = null,
 | 
			
		||||
    ) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -40,15 +41,16 @@ class OidcUserDetails
 | 
			
		|||
        $this->email = $claims->getClaim('email') ?? $this->email;
 | 
			
		||||
        $this->name = static::getUserDisplayName($displayNameClaims, $claims) ?? $this->name;
 | 
			
		||||
        $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);
 | 
			
		||||
 | 
			
		||||
        $displayName = [];
 | 
			
		||||
        foreach ($displayNameClaimParts as $claim) {
 | 
			
		||||
            $component = $token->getClaim(trim($claim)) ?? '';
 | 
			
		||||
            $component = $claims->getClaim(trim($claim)) ?? '';
 | 
			
		||||
            if ($component !== '') {
 | 
			
		||||
                $displayName[] = $component;
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -57,13 +59,13 @@ class OidcUserDetails
 | 
			
		|||
        return implode(' ', $displayName);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected static function getUserGroups(string $groupsClaim, ProvidesClaims $token): ?array
 | 
			
		||||
    protected static function getUserGroups(string $groupsClaim, ProvidesClaims $claims): ?array
 | 
			
		||||
    {
 | 
			
		||||
        if (empty($groupsClaim)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $groupsList = Arr::get($token->getAllClaims(), $groupsClaim);
 | 
			
		||||
        $groupsList = Arr::get($claims->getAllClaims(), $groupsClaim);
 | 
			
		||||
        if (!is_array($groupsList)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -72,4 +74,14 @@ class OidcUserDetails
 | 
			
		|||
            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.
 | 
			
		||||
    '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
 | 
			
		||||
    // Enable syncing, upon login, of OIDC groups to BookStack roles
 | 
			
		||||
    'user_to_groups' => env('OIDC_USER_TO_GROUPS', false),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ namespace BookStack\Uploads;
 | 
			
		|||
use BookStack\Exceptions\HttpFetchException;
 | 
			
		||||
use BookStack\Http\HttpRequestService;
 | 
			
		||||
use BookStack\Users\Models\User;
 | 
			
		||||
use BookStack\Util\WebSafeMimeSniffer;
 | 
			
		||||
use Exception;
 | 
			
		||||
use GuzzleHttp\Psr7\Request;
 | 
			
		||||
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.
 | 
			
		||||
     */
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
     */
 | 
			
		||||
| 
						 | 
				
			
			@ -113,7 +141,19 @@ class UserAvatars
 | 
			
		|||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $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) {
 | 
			
		||||
                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 Collection $roles
 | 
			
		||||
 * @property Collection $mfaValues
 | 
			
		||||
 * @property ?Image     $avatar
 | 
			
		||||
 */
 | 
			
		||||
class User extends Model implements AuthenticatableContract, CanResetPasswordContract, Loggable, Sluggable
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ namespace Tests\Auth;
 | 
			
		|||
use BookStack\Activity\ActivityType;
 | 
			
		||||
use BookStack\Facades\Theme;
 | 
			
		||||
use BookStack\Theming\ThemeEvents;
 | 
			
		||||
use BookStack\Uploads\UserAvatars;
 | 
			
		||||
use BookStack\Users\Models\Role;
 | 
			
		||||
use BookStack\Users\Models\User;
 | 
			
		||||
use GuzzleHttp\Psr7\Response;
 | 
			
		||||
| 
						 | 
				
			
			@ -41,6 +42,7 @@ class OidcTest extends TestCase
 | 
			
		|||
            'oidc.discover'               => false,
 | 
			
		||||
            'oidc.dump_user_details'      => false,
 | 
			
		||||
            'oidc.additional_scopes'      => '',
 | 
			
		||||
            'odic.fetch_avatar'           => false,
 | 
			
		||||
            'oidc.user_to_groups'         => false,
 | 
			
		||||
            'oidc.groups_claim'           => 'group',
 | 
			
		||||
            '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()
 | 
			
		||||
    {
 | 
			
		||||
        config()->set([
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -60,6 +60,14 @@ class FileProvider
 | 
			
		|||
        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.
 | 
			
		||||
     */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -184,7 +184,7 @@ class EntityPermissionsTest extends TestCase
 | 
			
		|||
 | 
			
		||||
        $this->get($bookUrl . '/edit')->assertRedirect('/');
 | 
			
		||||
        $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($bookChapter->getUrl() . '/edit')->assertRedirect('/');
 | 
			
		||||
        $this->get('/')->assertSee('You do not have permission');
 | 
			
		||||
| 
						 | 
				
			
			@ -282,7 +282,7 @@ class EntityPermissionsTest extends TestCase
 | 
			
		|||
 | 
			
		||||
        $this->get($chapterUrl . '/edit')->assertRedirect('/');
 | 
			
		||||
        $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->setRestrictionsForTestRoles($chapter, ['view', 'update']);
 | 
			
		||||
| 
						 | 
				
			
			@ -341,7 +341,7 @@ class EntityPermissionsTest extends TestCase
 | 
			
		|||
 | 
			
		||||
        $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->setRestrictionsForTestRoles($page, ['view', 'update']);
 | 
			
		||||
| 
						 | 
				
			
			@ -565,7 +565,7 @@ class EntityPermissionsTest extends TestCase
 | 
			
		|||
 | 
			
		||||
        $this->get($bookUrl . '/edit')->assertRedirect('/');
 | 
			
		||||
        $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($bookChapter->getUrl() . '/edit')->assertRedirect('/');
 | 
			
		||||
        $this->get('/')->assertSee('You do not have permission');
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,6 @@
 | 
			
		|||
 | 
			
		||||
namespace Tests\Permissions;
 | 
			
		||||
 | 
			
		||||
use BookStack\Activity\ActivityType;
 | 
			
		||||
use BookStack\Activity\Models\Comment;
 | 
			
		||||
use BookStack\Entities\Models\Book;
 | 
			
		||||
use BookStack\Entities\Models\Bookshelf;
 | 
			
		||||
| 
						 | 
				
			
			@ -10,7 +9,6 @@ use BookStack\Entities\Models\Chapter;
 | 
			
		|||
use BookStack\Entities\Models\Entity;
 | 
			
		||||
use BookStack\Entities\Models\Page;
 | 
			
		||||
use BookStack\Uploads\Image;
 | 
			
		||||
use BookStack\Users\Models\Role;
 | 
			
		||||
use BookStack\Users\Models\User;
 | 
			
		||||
use Illuminate\Testing\TestResponse;
 | 
			
		||||
use Tests\TestCase;
 | 
			
		||||
| 
						 | 
				
			
			@ -152,10 +150,14 @@ class RolePermissionsTest extends TestCase
 | 
			
		|||
    /**
 | 
			
		||||
     * 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) {
 | 
			
		||||
            $this->actingAs($this->user)->get($url)->assertRedirect('/');
 | 
			
		||||
            $this->actingAs($this->user)->get($url)->assertRedirect($expectedRedirectUri);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach ($visibles as $url => $text) {
 | 
			
		||||
| 
						 | 
				
			
			@ -535,11 +537,11 @@ class RolePermissionsTest extends TestCase
 | 
			
		|||
            $ownPage->getUrl() . '/edit',
 | 
			
		||||
        ], [
 | 
			
		||||
            $ownPage->getUrl() => 'Edit',
 | 
			
		||||
        ]);
 | 
			
		||||
        ], $ownPage->getUrl());
 | 
			
		||||
 | 
			
		||||
        $resp = $this->get($otherPage->getUrl());
 | 
			
		||||
        $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()
 | 
			
		||||
| 
						 | 
				
			
			@ -550,7 +552,7 @@ class RolePermissionsTest extends TestCase
 | 
			
		|||
            $otherPage->getUrl('/edit'),
 | 
			
		||||
        ], [
 | 
			
		||||
            $otherPage->getUrl() => 'Edit',
 | 
			
		||||
        ]);
 | 
			
		||||
        ], $otherPage->getUrl());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function test_page_delete_own_permission()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 268 B  | 
		Loading…
	
		Reference in New Issue