Only that relevant to the additional testing work.
--- /dev/null
+# Permission Scenario Testing
+
+Due to complexity that can arise in the various combinations of permissions, this document details scenarios and their expected results.
+
+Test cases are written ability abstract, since all abilities should act the same in theory. Functional test cases may test abilities separate due to implementation differences.
+
+Tests are categorised by the most specific element involved in the scenario, where the below list is most specific to least:
+
+- User entity permissions.
+- Role entity permissions.
+- Fallback entity permissions.
+- Role permissions.
+
+- TODO - Test fallback in the context of the above.
+
+## General Permission Logical Rules
+
+The below are some general rules we follow to standardise the behaviour of permissions in the platform:
+
+- Most specific permission application (as above) take priority and can deny less specific permissions.
+- Parent user/role entity permissions that may be inherited, are considered to essentially be applied on the item they are inherited to unless a lower level has its own permission rule for an already specific role/user.
+- Where both grant and deny exist at the same specificity, we side towards grant.
+
+## Cases
+
+### Content Role Permissions
+
+These are tests related to item/entity permissions that are set only at a role level.
+
+#### test_01_allow
+
+- Role A has role all-page permission.
+- User has Role A.
+
+User granted page permission.
+
+#### test_02_deny
+
+- Role A has no page permission.
+- User has Role A.
+
+User denied page permission.
+
+#### test_10_allow_on_own_with_own
+
+- Role A has role own-page permission.
+- User has Role A.
+- User is owner of page.
+
+User granted page permission.
+
+#### test_11_deny_on_other_with_own
+
+- Role A has role own-page permission.
+- User has Role A.
+- User is not owner of page.
+
+User denied page permission.
+
+#### test_20_multiple_role_conflicting_all
+
+- Role A has role all-page permission.
+- Role B has no page permission.
+- User has Role A & B.
+
+User granted page permission.
+
+#### test_21_multiple_role_conflicting_own
+
+- Role A has role own-page permission.
+- Role B has no page permission.
+- User has Role A & B.
+- User is owner of page.
+
+User granted page permission.
+
+---
+
+### Entity Role Permissions
+
+These are tests related to entity-level role-specific permission overrides.
+
+#### test_01_explicit_allow
+
+- Page permissions have inherit disabled.
+- Role A has entity allow page permission.
+- User has Role A.
+
+User granted page permission.
+
+#### test_02_explicit_deny
+
+- Page permissions have inherit disabled.
+- Role A has entity deny page permission.
+- User has Role A.
+
+User denied page permission.
+
+#### test_03_same_level_conflicting
+
+- Page permissions have inherit disabled.
+- Role A has entity allow page permission.
+- Role B has entity deny page permission.
+- User has both Role A & B.
+
+User granted page permission.
+Explicit grant overrides entity deny at same level.
+
+#### test_20_inherit_allow
+
+- Page permissions have inherit enabled.
+- Chapter permissions has inherit disabled.
+- Role A has entity allow chapter permission.
+- User has Role A.
+
+User granted page permission.
+
+#### test_21_inherit_deny
+
+- Page permissions have inherit enabled.
+- Chapter permissions has inherit disabled.
+- Role A has entity deny chapter permission.
+- User has Role A.
+
+User denied page permission.
+
+#### test_22_same_level_conflict_inherit
+
+- Page permissions have inherit enabled.
+- Chapter permissions has inherit disabled.
+- Role A has entity deny chapter permission.
+- Role B has entity allow chapter permission.
+- User has both Role A & B.
+
+User granted page permission.
+
+#### test_30_child_inherit_override_allow
+
+- Page permissions have inherit enabled.
+- Chapter permissions has inherit disabled.
+- Role A has entity deny chapter permission.
+- Role A has entity allow page permission.
+- User has Role A.
+
+User granted page permission.
+
+#### test_31_child_inherit_override_deny
+
+- Page permissions have inherit enabled.
+- Chapter permissions has inherit disabled.
+- Role A has entity allow chapter permission.
+- Role A has entity deny page permission.
+- User has Role A.
+
+User denied page permission.
+
+#### test_40_multi_role_inherit_conflict_override_deny
+
+- Page permissions have inherit enabled.
+- Chapter permissions has inherit disabled.
+- Role A has entity deny page permission.
+- Role B has entity allow chapter permission.
+- User has Role A & B.
+
+User granted page permission.
+
+#### test_41_multi_role_inherit_conflict_retain_allow
+
+- Page permissions have inherit enabled.
+- Chapter permissions has inherit disabled.
+- Role A has entity allow page permission.
+- Role B has entity deny chapter permission.
+- User has Role A & B.
+
+User granted page permission.
+
+#### test_50_role_override_allow
+
+- Page permissions have inherit enabled.
+- Role A has no page role permission.
+- Role A has entity allow page permission.
+- User has Role A.
+
+User granted page permission.
+
+#### test_51_role_override_deny
+
+- Page permissions have inherit enabled.
+- Role A has no page-view-all role permission.
+- Role A has entity deny page permission.
+- User has Role A.
+
+User denied page permission.
+
+#### test_60_inherited_role_override_allow
+
+- Page permissions have inherit enabled.
+- Chapter permissions have inherit enabled.
+- Role A has no page role permission.
+- Role A has entity allow chapter permission.
+- User has Role A.
+
+User granted page permission.
+
+#### test_61_inherited_role_override_deny
+
+- Page permissions have inherit enabled.
+- Chapter permissions have inherit enabled.
+- Role A has page role permission.
+- Role A has entity denied chapter permission.
+- User has Role A.
+
+User denied page permission.
+
+#### test_62_inherited_role_override_deny_on_own
+
+- Page permissions have inherit enabled.
+- Chapter permissions have inherit enabled.
+- Role A has own-page role permission.
+- Role A has entity denied chapter permission.
+- User has Role A.
+- User owns Page.
+
+User denied page permission.
+
+#### test_70_multi_role_inheriting_deny
+
+- Page permissions have inherit enabled.
+- Role A has all page role permission.
+- Role B has entity denied page permission.
+- User has Role A and B.
+
+User denied page permission.
+
+#### test_80_multi_role_inherited_deny_via_parent
+
+- Page permissions have inherit enabled.
+- Chapter permissions have inherit enabled.
+- Role A has all-pages role permission.
+- Role B has entity denied chapter permission.
+- User has Role A & B.
+
+User denied page permission.
+
+---
+
+### Entity User Permissions
+
+These are tests related to entity-level user-specific permission overrides.
+
+#### test_01_explicit_allow
+
+- Page permissions have inherit disabled.
+- User has entity allow page permission.
+
+User granted page permission.
+
+#### test_02_explicit_deny
+
+- Page permissions have inherit disabled.
+- User has entity deny page permission.
+
+User denied page permission.
+
+#### test_10_allow_inherit
+
+- Page permissions have inherit enabled.
+- Chapter permissions have inherit disabled.
+- User has entity allow chapter permission.
+
+User granted page permission.
+
+#### test_11_deny_inherit
+
+- Page permissions have inherit enabled.
+- Chapter permissions have inherit disabled.
+- User has entity deny chapter permission.
+
+User denied page permission.
+
+#### test_12_allow_inherit_override
+
+- Page permissions have inherit enabled.
+- Chapter permissions have inherit disabled.
+- User has entity deny chapter permission.
+- User has entity allow page permission.
+
+User granted page permission.
+
+#### test_13_deny_inherit_override
+
+- Page permissions have inherit enabled.
+- Chapter permissions have inherit disabled.
+- User has entity allow chapter permission.
+- User has entity deny page permission.
+
+User denied page permission.
+
+#### test_40_entity_role_override_allow
+
+- Page permissions have inherit disabled.
+- User has entity allow page permission.
+- Role A has entity deny page permission.
+- User has role A.
+
+User granted page permission.
+
+#### test_41_entity_role_override_deny
+
+- Page permissions have inherit disabled.
+- User has entity deny page permission.
+- Role A has entity allow page permission.
+- User has role A.
+
+User denied page permission.
+
+#### test_42_entity_role_override_allow_via_inherit
+
+- Page permissions have inherit enabled.
+- Chapter permissions have inherit disabled.
+- User has entity allow chapter permission.
+- Role A has entity deny page permission.
+- User has role A.
+
+User granted page permission.
+
+#### test_43_entity_role_override_deny_via_inherit
+
+- Page permissions have inherit enabled.
+- Chapter permissions have inherit disabled.
+- User has entity deny chapter permission.
+- Role A has entity allow page permission.
+- User has role A.
+
+User denied page permission.
+
+#### test_50_role_override_allow
+
+- Page permissions have inherit enabled.
+- Role A has no page role permission.
+- User has entity allow page permission.
+- User has Role A.
+
+User granted page permission.
+
+#### test_51_role_override_deny
+
+- Page permissions have inherit enabled.
+- Role A has all-page role permission.
+- User has entity deny page permission.
+- User has Role A.
+
+User denied page permission.
+
+#### test_60_inherited_role_override_allow
+
+- Page permissions have inherit enabled.
+- Role A has no page role permission.
+- User has entity allow chapter permission.
+- User has Role A.
+
+User granted page permission.
+
+#### test_61_inherited_role_override_deny
+
+- Page permissions have inherit enabled.
+- Role A has view-all page role permission.
+- User has entity deny chapter permission.
+- User has Role A.
+
+User denied page permission.
+
+#### test_61_inherited_role_override_deny_on_own
+
+- Page permissions have inherit enabled.
+- Role A has view-own page role permission.
+- User has entity deny chapter permission.
+- User has Role A.
+- User owns Page.
+
+User denied page permission.
+
+#### test_70_all_override_allow
+
+- Page permissions have inherit enabled.
+- Role A has no page role permission.
+- Role A has entity deny page permission.
+- User has entity allow page permission.
+- User has Role A.
+
+User granted page permission.
+
+#### test_71_all_override_deny
+
+- Page permissions have inherit enabled.
+- Role A has page-all role permission.
+- Role A has entity allow page permission.
+- User has entity deny page permission.
+- User has Role A.
+
+User denied page permission.
+
+#### test_80_inherited_all_override_allow
+
+- Page permissions have inherit enabled.
+- Role A has no page role permission.
+- Role A has entity deny chapter permission.
+- User has entity allow chapter permission.
+- User has Role A.
+
+User granted page permission.
+
+#### test_81_inherited_all_override_deny
+
+- Page permissions have inherit enabled.
+- Role A has view-all page role permission.
+- Role A has entity allow chapter permission.
+- User has entity deny chapter permission.
+- User has Role A.
+
+User denied page permission.
\ No newline at end of file
public function test_only_accessible_with_right_permissions()
{
- $viewer = $this->getViewer();
+ $viewer = $this->users->viewer();
$this->actingAs($viewer);
$resp = $this->get('/settings/audit');
$this->assertPermissionError($resp);
- $this->giveUserPermissions($viewer, ['settings-manage']);
+ $this->permissions->grantUserRolePermissions($viewer, ['settings-manage']);
$resp = $this->get('/settings/audit');
$this->assertPermissionError($resp);
- $this->giveUserPermissions($viewer, ['users-manage']);
+ $this->permissions->grantUserRolePermissions($viewer, ['users-manage']);
$resp = $this->get('/settings/audit');
$resp->assertStatus(200);
$resp->assertSeeText('Audit Log');
public function test_shows_activity()
{
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
$this->actingAs($admin);
$page = $this->entities->page();
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
public function test_shows_name_for_deleted_items()
{
- $this->actingAs($this->getAdmin());
+ $this->actingAs($this->users->admin());
$page = $this->entities->page();
$pageName = $page->name;
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
public function test_shows_activity_for_deleted_users()
{
- $viewer = $this->getViewer();
+ $viewer = $this->users->viewer();
$this->actingAs($viewer);
$page = $this->entities->page();
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
- $this->actingAs($this->getAdmin());
+ $this->actingAs($this->users->admin());
app(UserRepo::class)->destroy($viewer);
$resp = $this->get('settings/audit');
public function test_filters_by_key()
{
- $this->actingAs($this->getAdmin());
+ $this->actingAs($this->users->admin());
$page = $this->entities->page();
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
public function test_date_filters()
{
- $this->actingAs($this->getAdmin());
+ $this->actingAs($this->users->admin());
$page = $this->entities->page();
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
public function test_user_filter()
{
- $admin = $this->getAdmin();
- $editor = $this->getEditor();
+ $admin = $this->users->admin();
+ $editor = $this->users->editor();
$this->actingAs($admin);
$page = $this->entities->page();
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
public function test_ip_address_logged_and_visible()
{
config()->set('app.proxies', '*');
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$page = $this->entities->page();
$this->actingAs($editor)->put($page->getUrl(), [
public function test_ip_address_is_searchable()
{
config()->set('app.proxies', '*');
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$page = $this->entities->page();
$this->actingAs($editor)->put($page->getUrl(), [
{
config()->set('app.proxies', '*');
config()->set('app.env', 'demo');
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$page = $this->entities->page();
$this->actingAs($editor)->put($page->getUrl(), [
{
config()->set('app.proxies', '*');
config()->set('app.ip_address_precision', 2);
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$page = $this->entities->page();
$this->actingAs($editor)->put($page->getUrl(), [
]);
$webhook = $this->newWebhook(['active' => true, 'endpoint' => 'https://wh.example.com'], ['all']);
$page = $this->entities->page();
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->runEvent(ActivityType::PAGE_UPDATE, $page, $editor);
protected function runEvent(string $event, $detail = '', ?User $user = null)
{
if (is_null($user)) {
- $user = $this->getEditor();
+ $user = $this->users->editor();
}
$this->actingAs($user);
protected function getWebhookData(string $event, $detail): array
{
$webhook = Webhook::factory()->make();
- $user = $this->getEditor();
+ $user = $this->users->editor();
$formatter = WebhookFormatter::getDefault($event, $webhook, $detail, $user, time());
return $formatter->format();
public function test_settings_manage_permission_required_for_webhook_routes()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->actingAs($editor);
$routes = [
$this->assertPermissionError($resp);
}
- $this->giveUserPermissions($editor, ['settings-manage']);
+ $this->permissions->grantUserRolePermissions($editor, ['settings-manage']);
foreach ($routes as [$method, $endpoint]) {
$resp = $this->call($method, $endpoint);
public function test_requests_succeed_with_default_auth()
{
- $viewer = $this->getViewer();
- $this->giveUserPermissions($viewer, ['access-api']);
+ $viewer = $this->users->viewer();
+ $this->permissions->grantUserRolePermissions($viewer, ['access-api']);
$resp = $this->get($this->endpoint);
$resp->assertStatus(401);
auth()->logout();
$accessApiPermission = RolePermission::getByName('access-api');
- $editorRole = $this->getEditor()->roles()->first();
+ $editorRole = $this->users->editor()->roles()->first();
$editorRole->detachPermission($accessApiPermission);
$resp = $this->get($this->endpoint, $this->apiAuthHeader());
public function test_api_access_permission_required_to_access_api_with_session_auth()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->actingAs($editor, 'standard');
$resp = $this->get($this->endpoint);
auth('standard')->logout();
$accessApiPermission = RolePermission::getByName('access-api');
- $editorRole = $this->getEditor()->roles()->first();
+ $editorRole = $this->users->editor()->roles()->first();
$editorRole->detachPermission($accessApiPermission);
$editor = User::query()->where('id', '=', $editor->id)->first();
public function test_token_expiry_checked()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$token = $editor->apiTokens()->first();
$resp = $this->get($this->endpoint, $this->apiAuthHeader());
public function test_email_confirmation_checked_using_api_auth()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$editor->email_confirmed = false;
$editor->save();
],
]]);
- $this->entities->setPermissions($page, [], []);
+ $this->permissions->setEntityPermissions($page, [], []);
$resp = $this->getJson($this->baseEndpoint . '?count=1&sort=+id');
$resp->assertJsonMissing(['data' => [
public function test_attachment_not_visible_on_other_users_draft()
{
$this->actingAsApiAdmin();
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$page = $this->entities->page();
$page->draft = true;
$page->owned_by = $editor->id;
$page->save();
- $this->entities->regenPermissions($page);
+ $this->permissions->regenerateForEntity($page);
$attachment = $this->createAttachmentForPage($page, [
'name' => 'my attachment',
protected function createAttachmentForPage(Page $page, $attributes = []): Attachment
{
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
/** @var Attachment $attachment */
$attachment = $page->attachments()->forceCreate(array_merge([
'uploaded_to' => $page->id,
{
$types = ['html', 'plaintext', 'pdf', 'markdown'];
$this->actingAsApiEditor();
- $this->removePermissionFromUser($this->getEditor(), 'content-export');
+ $this->permissions->removeUserRolePermissions($this->users->editor(), ['content-export']);
$book = $this->entities->book();
foreach ($types as $type) {
{
$types = ['html', 'plaintext', 'pdf', 'markdown'];
$this->actingAsApiEditor();
- $this->removePermissionFromUser($this->getEditor(), 'content-export');
+ $this->permissions->removeUserRolePermissions($this->users->editor(), ['content-export']);
$chapter = Chapter::visible()->has('pages')->first();
foreach ($types as $type) {
$this->actingAsApiEditor();
$page = $this->entities->page();
$chapter = Chapter::visible()->where('book_id', '!=', $page->book_id)->first();
- $this->entities->setPermissions($chapter, ['view'], [$this->getEditor()->roles()->first()]);
+ $this->permissions->setEntityPermissions($chapter, ['view'], [$this->users->editor()->roles()->first()]);
$details = [
'name' => 'My updated API page',
'chapter_id' => $chapter->id,
{
$types = ['html', 'plaintext', 'pdf', 'markdown'];
$this->actingAsApiEditor();
- $this->removePermissionFromUser($this->getEditor(), 'content-export');
+ $this->permissions->removeUserRolePermissions($this->users->editor(), ['content-export']);
$page = $this->entities->page();
foreach ($types as $type) {
public function test_settings_manage_permission_needed_for_all_endpoints()
{
- $editor = $this->getEditor();
- $this->giveUserPermissions($editor, ['settings-manage']);
+ $editor = $this->users->editor();
+ $this->permissions->grantUserRolePermissions($editor, ['settings-manage']);
$this->actingAs($editor);
foreach ($this->endpointMap as [$method, $uri]) {
public function test_restrictions_manage_all_permission_needed_for_all_endpoints()
{
- $editor = $this->getEditor();
- $this->giveUserPermissions($editor, ['restrictions-manage-all']);
+ $editor = $this->users->editor();
+ $this->permissions->grantUserRolePermissions($editor, ['restrictions-manage-all']);
$this->actingAs($editor);
foreach ($this->endpointMap as [$method, $uri]) {
public function test_index_endpoint_returns_expected_page()
{
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
$page = $this->entities->page();
$book = $this->entities->book();
public function test_index_endpoint_returns_children_count()
{
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
$book = Book::query()->whereHas('pages')->whereHas('chapters')->withCount(['pages', 'chapters'])->first();
$this->actingAs($admin)->delete($book->getUrl());
public function test_index_endpoint_returns_parent()
{
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
$page = $this->entities->pageWithinChapter();
$this->actingAs($admin)->delete($page->getUrl());
*/
protected function actingAsApiEditor()
{
- $this->actingAs($this->getEditor(), 'api');
+ $this->actingAs($this->users->editor(), 'api');
return $this;
}
*/
protected function actingAsApiAdmin()
{
- $this->actingAs($this->getAdmin(), 'api');
+ $this->actingAs($this->users->admin(), 'api');
return $this;
}
{
$this->actingAsApiAdmin();
/** @var User $user */
- $user = $this->getAdmin();
+ $user = $this->users->admin();
$roles = Role::query()->pluck('id');
$resp = $this->putJson($this->baseEndpoint . "/{$user->id}", [
'name' => 'My updated user',
{
$this->actingAsApiAdmin();
/** @var User $user */
- $user = $this->getAdmin();
+ $user = $this->users->admin();
$roleCount = $user->roles()->count();
$resp = $this->putJson($this->baseEndpoint . "/{$user->id}", []);
{
$this->actingAsApiAdmin();
/** @var User $user */
- $user = User::query()->where('id', '!=', $this->getAdmin()->id)
+ $user = User::query()->where('id', '!=', $this->users->admin()->id)
->whereNull('system_name')
->first();
{
$this->actingAsApiAdmin();
/** @var User $user */
- $user = User::query()->where('id', '!=', $this->getAdmin()->id)
+ $user = User::query()->where('id', '!=', $this->users->admin()->id)
->whereNull('system_name')
->first();
$entityChain = $this->entities->createChainBelongingToUser($user);
public function test_mfa_session_cleared_on_logout()
{
- $user = $this->getEditor();
+ $user = $this->users->editor();
$mfaSession = $this->app->make(MfaSession::class);
$mfaSession->markVerifiedForUser($user);
public function test_login_authenticates_nonadmins_on_default_guard_only()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$editor->password = bcrypt('password');
$editor->save();
public function test_logged_in_user_with_unconfirmed_email_is_logged_out()
{
$this->setSettings(['registration-confirmation' => 'true']);
- $user = $this->getEditor();
+ $user = $this->users->editor();
$user->email_confirmed = false;
$user->save();
{
public function test_user_is_assigned_to_matching_roles()
{
- $user = $this->getViewer();
+ $user = $this->users->viewer();
$roleA = Role::factory()->create(['display_name' => 'Wizards']);
$roleB = Role::factory()->create(['display_name' => 'Gremlins']);
public function test_multiple_values_in_role_external_auth_id_handled()
{
- $user = $this->getViewer();
+ $user = $this->users->viewer();
$role = Role::factory()->create(['display_name' => 'ABC123', 'external_auth_id' => 'sales, engineering, developers, marketers']);
$this->assertFalse($user->hasRole($role->id));
public function test_commas_can_be_used_in_external_auth_id_if_escaped()
{
- $user = $this->getViewer();
+ $user = $this->users->viewer();
$role = Role::factory()->create(['display_name' => 'ABC123', 'external_auth_id' => 'sales\,-developers, marketers']);
$this->assertFalse($user->hasRole($role->id));
public function test_external_auth_id_matches_ignoring_case()
{
- $user = $this->getViewer();
+ $user = $this->users->viewer();
$role = Role::factory()->create(['display_name' => 'ABC123', 'external_auth_id' => 'WaRRioRs']);
$this->assertFalse($user->hasRole($role->id));
public function test_user_edit_form()
{
- $editUser = $this->getNormalUser();
+ $editUser = $this->users->viewer();
$editPage = $this->asAdmin()->get("/settings/users/{$editUser->id}");
$editPage->assertSee('Edit User');
$editPage->assertDontSee('Password');
public function test_non_admins_cannot_change_auth_id()
{
- $testUser = $this->getNormalUser();
+ $testUser = $this->users->viewer();
$this->actingAs($testUser)
->get('/settings/users/' . $testUser->id)
->assertDontSee('External Authentication');
config()->set([
'auth.method' => 'oidc',
]);
- $this->actingAs($this->getEditor());
+ $this->actingAs($this->users->editor());
$req = $this->post('/logout');
$req->assertRedirect('/login?prevent_auto_init=true');
{
public function test_totp_setup()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->assertDatabaseMissing('mfa_values', ['user_id' => $editor->id]);
// Setup page state
public function test_backup_codes_setup()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->assertDatabaseMissing('mfa_values', ['user_id' => $editor->id]);
// Setup page state
public function test_mfa_method_count_is_visible_on_user_edit_page()
{
- $user = $this->getEditor();
- $resp = $this->actingAs($this->getAdmin())->get($user->getEditUrl());
+ $user = $this->users->editor();
+ $resp = $this->actingAs($this->users->admin())->get($user->getEditUrl());
$resp->assertSee('0 methods configured');
MfaValue::upsertWithValue($user, MfaValue::METHOD_TOTP, 'test');
public function test_mfa_setup_link_only_shown_when_viewing_own_user_edit_page()
{
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
$resp = $this->actingAs($admin)->get($admin->getEditUrl());
$this->withHtml($resp)->assertElementExists('a[href$="/mfa/setup"]');
- $resp = $this->actingAs($admin)->get($this->getEditor()->getEditUrl());
+ $resp = $this->actingAs($admin)->get($this->users->editor()->getEditUrl());
$this->withHtml($resp)->assertElementNotExists('a[href$="/mfa/setup"]');
}
public function test_mfa_indicator_shows_in_user_list()
{
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
User::query()->where('id', '!=', $admin->id)->delete();
$resp = $this->actingAs($admin)->get('/settings/users');
public function test_remove_mfa_method()
{
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
MfaValue::upsertWithValue($admin, MfaValue::METHOD_TOTP, 'test');
$this->assertEquals(1, $admin->mfaValues()->count());
public function test_totp_setup_url_shows_correct_user_when_setup_forced_upon_login()
{
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
/** @var Role $role */
$role = $admin->roles()->first();
$role->mfa_enforced = true;
public function test_both_mfa_options_available_if_set_on_profile()
{
- $user = $this->getEditor();
+ $user = $this->users->editor();
$user->password = Hash::make('password');
$user->save();
public function test_mfa_required_with_no_methods_leads_to_setup()
{
- $user = $this->getEditor();
+ $user = $this->users->editor();
$user->password = Hash::make('password');
$user->save();
/** @var Role $role */
// Attempted login user, who has configured mfa, access
// Sets up user that has MFA required after attempted login.
$loginService = $this->app->make(LoginService::class);
- $user = $this->getEditor();
+ $user = $this->users->editor();
/** @var Role $role */
$role = $user->roles->first();
$role->mfa_enforced = true;
protected function startTotpLogin(): array
{
$secret = $this->app->make(TotpService::class)->generateSecret();
- $user = $this->getEditor();
+ $user = $this->users->editor();
$user->password = Hash::make('password');
$user->save();
MfaValue::upsertWithValue($user, MfaValue::METHOD_TOTP, $secret);
*/
protected function startBackupCodeLogin($codes = ['kzzu6-1pgll', 'bzxnf-plygd', 'bwdsp-ysl51', '1vo93-ioy7n', 'lf7nw-wdyka', 'xmtrd-oplac']): array
{
- $user = $this->getEditor();
+ $user = $this->users->editor();
$user->password = Hash::make('password');
$user->save();
MfaValue::upsertWithValue($user, MfaValue::METHOD_BACKUP_CODES, json_encode($codes));
public function test_logout_route_functions()
{
- $this->actingAs($this->getEditor());
+ $this->actingAs($this->users->editor());
$this->post('/logout');
$this->assertFalse(auth()->check());
}
public function test_auth_login_as_existing_user()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$editor->external_auth_id = 'benny505';
$editor->save();
public function test_auth_login_as_existing_user_email_with_different_auth_id_fails()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$editor->external_auth_id = 'editor101';
$editor->save();
public function test_reset_request_is_throttled()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
Notification::fake();
$this->get('/password/email');
$this->followingRedirects()->post('/password/email', [
'saml2.onelogin.strict' => false,
]);
- $resp = $this->actingAs($this->getEditor())->get('/');
+ $resp = $this->actingAs($this->users->editor())->get('/');
$this->withHtml($resp)->assertElementContains('form[action$="/saml2/logout"] button', 'Logout');
}
// Test social callback with matching social account
DB::table('social_accounts')->insert([
- 'user_id' => $this->getAdmin()->id,
+ 'user_id' => $this->users->admin()->id,
'driver' => 'github',
'driver_id' => 'logintest123',
]);
$resp = $this->followingRedirects()->get('/login/service/github/callback');
$resp->assertDontSee('login-form');
- $this->assertActivityExists(ActivityType::AUTH_LOGIN, null, 'github; (' . $this->getAdmin()->id . ') ' . $this->getAdmin()->name);
+ $this->assertActivityExists(ActivityType::AUTH_LOGIN, null, 'github; (' . $this->users->admin()->id . ') ' . $this->users->admin()->name);
}
public function test_social_account_detach()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
config([
'GITHUB_APP_ID' => 'abc123', 'GITHUB_APP_SECRET' => '123abc',
'APP_URL' => 'http://localhost',
public function test_user_creation_creates_invite()
{
Notification::fake();
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
$email = Str::random(16) . '@example.com';
$resp = $this->actingAs($admin)->post('/settings/users/create', [
public function test_user_invite_sent_in_selected_language()
{
Notification::fake();
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
$email = Str::random(16) . '@example.com';
$resp = $this->actingAs($admin)->post('/settings/users/create', [
public function test_invite_set_password()
{
Notification::fake();
- $user = $this->getViewer();
+ $user = $this->users->viewer();
$inviteService = app(UserInviteService::class);
$inviteService->sendInvitation($user);
public function test_invite_set_has_password_validation()
{
Notification::fake();
- $user = $this->getViewer();
+ $user = $this->users->viewer();
$inviteService = app(UserInviteService::class);
$inviteService->sendInvitation($user);
public function test_token_expires_after_two_weeks()
{
Notification::fake();
- $user = $this->getViewer();
+ $user = $this->users->viewer();
$inviteService = app(UserInviteService::class);
$inviteService->sendInvitation($user);
$this->assertDatabaseHas('activities', [
'type' => 'page_update',
'entity_id' => $page->id,
- 'user_id' => $this->getEditor()->id,
+ 'user_id' => $this->users->editor()->id,
]);
DB::rollBack();
$this->get($page->getUrl());
$this->assertDatabaseHas('views', [
- 'user_id' => $this->getEditor()->id,
+ 'user_id' => $this->users->editor()->id,
'viewable_id' => $page->id,
'views' => 1,
]);
$this->assertTrue($exitCode === 0, 'Command executed successfully');
$this->assertDatabaseMissing('views', [
- 'user_id' => $this->getEditor()->id,
+ 'user_id' => $this->users->editor()->id,
]);
}
}
{
$shelf = $this->entities->shelf();
$child = $shelf->books()->first();
- $editorRole = $this->getEditor()->roles()->first();
+ $editorRole = $this->users->editor()->roles()->first();
$this->assertFalse($child->hasPermissions(), 'Child book should not be restricted by default');
$this->assertTrue($child->permissions()->count() === 0, 'Child book should have no permissions by default');
- $this->entities->setPermissions($shelf, ['view', 'update'], [$editorRole]);
+ $this->permissions->setEntityPermissions($shelf, ['view', 'update'], [$editorRole]);
$this->artisan('bookstack:copy-shelf-permissions', [
'--slug' => $shelf->slug,
]);
$shelf = $this->entities->shelf();
Bookshelf::query()->where('id', '!=', $shelf->id)->delete();
$child = $shelf->books()->first();
- $editorRole = $this->getEditor()->roles()->first();
+ $editorRole = $this->users->editor()->roles()->first();
$this->assertFalse($child->hasPermissions(), 'Child book should not be restricted by default');
$this->assertTrue($child->permissions()->count() === 0, 'Child book should have no permissions by default');
- $this->entities->setPermissions($shelf, ['view', 'update'], [$editorRole]);
+ $this->permissions->setEntityPermissions($shelf, ['view', 'update'], [$editorRole]);
$this->artisan('bookstack:copy-shelf-permissions --all')
->expectsQuestion('Permission settings for all shelves will be cascaded. Books assigned to multiple shelves will receive only the permissions of it\'s last processed shelf. Are you sure you want to proceed?', 'y');
$child = $shelf->books()->first();
namespace Tests\Commands;
-use BookStack\Auth\Permissions\JointPermission;
-use BookStack\Entities\Models\Page;
+use BookStack\Auth\Permissions\CollapsedPermission;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;
public function test_regen_permissions_command()
{
DB::rollBack();
- JointPermission::query()->truncate();
- $page = Page::first();
+ $page = $this->entities->page();
+ $editor = $this->users->editor();
+ $this->permissions->addEntityPermission($page, ['view'], null, $editor);
+ CollapsedPermission::query()->truncate();
- $this->assertDatabaseMissing('joint_permissions', ['entity_id' => $page->id]);
+ $this->assertDatabaseMissing('entity_permissions_collapsed', ['entity_id' => $page->id]);
$exitCode = Artisan::call('bookstack:regenerate-permissions');
$this->assertTrue($exitCode === 0, 'Command executed successfully');
- DB::beginTransaction();
- $this->assertDatabaseHas('joint_permissions', ['entity_id' => $page->id]);
+ $this->assertDatabaseHas('entity_permissions_collapsed', [
+ 'entity_id' => $page->id,
+ 'user_id' => $editor->id,
+ 'view' => 1,
+ ]);
+
+ CollapsedPermission::query()->truncate();
+ DB::beginTransaction();
}
}
public function test_shelves_shows_in_header_if_have_view_permissions()
{
- $viewer = $this->getViewer();
+ $viewer = $this->users->viewer();
$resp = $this->actingAs($viewer)->get('/');
$this->withHtml($resp)->assertElementContains('header', 'Shelves');
$viewer->roles()->delete();
- $this->giveUserPermissions($viewer);
$resp = $this->actingAs($viewer)->get('/');
$this->withHtml($resp)->assertElementNotContains('header', 'Shelves');
- $this->giveUserPermissions($viewer, ['bookshelf-view-all']);
+ $this->permissions->grantUserRolePermissions($viewer, ['bookshelf-view-all']);
$resp = $this->actingAs($viewer)->get('/');
$this->withHtml($resp)->assertElementContains('header', 'Shelves');
$viewer->roles()->delete();
- $this->giveUserPermissions($viewer, ['bookshelf-view-own']);
+ $this->permissions->grantUserRolePermissions($viewer, ['bookshelf-view-own']);
$resp = $this->actingAs($viewer)->get('/');
$this->withHtml($resp)->assertElementContains('header', 'Shelves');
}
public function test_shelves_shows_in_header_if_have_any_shelve_view_permission()
{
$user = User::factory()->create();
- $this->giveUserPermissions($user, ['image-create-all']);
+ $this->permissions->grantUserRolePermissions($user, ['image-create-all']);
$shelf = $this->entities->shelf();
$userRole = $user->roles()->first();
$resp = $this->actingAs($user)->get('/');
$this->withHtml($resp)->assertElementNotContains('header', 'Shelves');
- $this->entities->setPermissions($shelf, ['view'], [$userRole]);
+ $this->permissions->setEntityPermissions($shelf, ['view'], [$userRole]);
$resp = $this->get('/');
$this->withHtml($resp)->assertElementContains('header', 'Shelves');
$resp->assertSee($book->name);
$resp->assertSee($book->getUrl());
- $this->entities->setPermissions($book, []);
+ $this->permissions->setEntityPermissions($book, []);
$resp = $this->asEditor()->get('/shelves');
$resp->assertDontSee($book->name);
],
]));
$resp->assertRedirect();
- $editorId = $this->getEditor()->id;
+ $editorId = $this->users->editor()->id;
$this->assertDatabaseHas('bookshelves', array_merge($shelfInfo, ['created_by' => $editorId, 'updated_by' => $editorId]));
$shelf = Bookshelf::where('name', '=', $shelfInfo['name'])->first();
$this->withHtml($resp)->assertElementContains('.book-content a.grid-card:nth-child(1)', $books[0]->name);
$this->withHtml($resp)->assertElementNotContains('.book-content a.grid-card:nth-child(3)', $books[0]->name);
- setting()->putUser($this->getEditor(), 'shelf_books_sort_order', 'desc');
+ setting()->putUser($this->users->editor(), 'shelf_books_sort_order', 'desc');
$resp = $this->asEditor()->get($shelf->getUrl());
$this->withHtml($resp)->assertElementNotContains('.book-content a.grid-card:nth-child(1)', $books[0]->name);
$this->withHtml($resp)->assertElementContains('.book-content a.grid-card:nth-child(3)', $books[0]->name);
- setting()->putUser($this->getEditor(), 'shelf_books_sort_order', 'desc');
- setting()->putUser($this->getEditor(), 'shelf_books_sort', 'name');
+ setting()->putUser($this->users->editor(), 'shelf_books_sort_order', 'desc');
+ setting()->putUser($this->users->editor(), 'shelf_books_sort', 'name');
$resp = $this->asEditor()->get($shelf->getUrl());
$this->withHtml($resp)->assertElementContains('.book-content a.grid-card:nth-child(1)', 'hdgfgdfg');
$this->withHtml($resp)->assertElementContains('.book-content a.grid-card:nth-child(2)', 'bsfsdfsdfsd');
$resp->assertRedirect($shelf->getUrl());
$this->assertSessionHas('success');
- $editorId = $this->getEditor()->id;
+ $editorId = $this->users->editor()->id;
$this->assertDatabaseHas('bookshelves', array_merge($shelfInfo, ['id' => $shelf->id, 'created_by' => $editorId, 'updated_by' => $editorId]));
$shelfPage = $this->get($shelf->getUrl());
$resp->assertSee("action=\"{$shelf->getUrl('/copy-permissions')}\"", false);
$child = $shelf->books()->first();
- $editorRole = $this->getEditor()->roles()->first();
+ $editorRole = $this->users->editor()->roles()->first();
$this->assertFalse($child->hasPermissions(), 'Child book should not be restricted by default');
$this->assertTrue($child->permissions()->count() === 0, 'Child book should have no permissions by default');
- $this->entities->setPermissions($shelf, ['view', 'update'], [$editorRole]);
+ $this->permissions->setEntityPermissions($shelf, ['view', 'update'], [$editorRole]);
$resp = $this->post($shelf->getUrl('/copy-permissions'));
$child = $shelf->books()->first();
public function test_books_view_shows_view_toggle_option()
{
/** @var Book $book */
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
setting()->putUser($editor, 'books_view_type', 'list');
$resp = $this->actingAs($editor)->get('/books');
// Hide child content
/** @var BookChild $page */
foreach ($book->getDirectChildren() as $child) {
- $this->entities->setPermissions($child, [], []);
+ $this->permissions->setEntityPermissions($child, [], []);
}
$this->asEditor()->post($book->getUrl('/copy'), ['name' => 'My copy book']);
{
/** @var Book $book */
$book = Book::query()->whereHas('chapters')->whereHas('directPages')->whereHas('chapters')->first();
- $viewer = $this->getViewer();
- $this->giveUserPermissions($viewer, ['book-create-all']);
+ $viewer = $this->users->viewer();
+ $this->permissions->grantUserRolePermissions($viewer, ['book-create-all']);
$this->actingAs($viewer)->post($book->getUrl('/copy'), ['name' => 'My copy book']);
/** @var Book $copy */
$shelfA->appendBook($book);
$shelfB->appendBook($book);
- $viewer = $this->getViewer();
- $this->giveUserPermissions($viewer, ['book-update-all', 'book-create-all', 'bookshelf-update-all']);
- $this->entities->setPermissions($shelfB);
+ $viewer = $this->users->viewer();
+ $this->permissions->grantUserRolePermissions($viewer, ['book-update-all', 'book-create-all', 'bookshelf-update-all']);
+ $this->permissions->setEntityPermissions($shelfB);
$this->asEditor()->post($book->getUrl('/copy'), ['name' => 'My copy book']);
// Hide pages to all non-admin roles
/** @var Page $page */
foreach ($chapter->pages as $page) {
- $this->entities->setPermissions($page, [], []);
+ $this->permissions->setEntityPermissions($page, [], []);
}
$this->asEditor()->post($chapter->getUrl('/copy'), [
public function test_copy_does_not_copy_pages_if_user_cant_page_create()
{
$chapter = $this->entities->chapterHasPages();
- $viewer = $this->getViewer();
- $this->giveUserPermissions($viewer, ['chapter-create-all']);
+ $viewer = $this->users->viewer();
+ $this->permissions->grantUserRolePermissions($viewer, ['chapter-create-all']);
// Lacking permission results in no copied pages
$this->actingAs($viewer)->post($chapter->getUrl('/copy'), [
$newChapter = Chapter::query()->where('name', '=', 'My copied chapter')->first();
$this->assertEquals(0, $newChapter->pages()->count());
- $this->giveUserPermissions($viewer, ['page-create-all']);
+ $this->permissions->grantUserRolePermissions($viewer, ['page-create-all']);
// Having permission rules in copied pages
$this->actingAs($viewer)->post($chapter->getUrl('/copy'), [
{
$chapter = $this->entities->chapter();
- $resp = $this->actingAs($this->getViewer())->get($chapter->getUrl());
+ $resp = $this->actingAs($this->users->viewer())->get($chapter->getUrl());
$this->withHtml($resp)->assertLinkNotExists($chapter->book->getUrl('sort'));
$resp = $this->asEditor()->get($chapter->getUrl());
public function test_convert_chapter_to_book_requires_permissions()
{
$chapter = $this->entities->chapter();
- $user = $this->getViewer();
+ $user = $this->users->viewer();
$permissions = ['chapter-delete-all', 'book-create-all', 'chapter-update-all'];
- $this->giveUserPermissions($user, $permissions);
+ $this->permissions->grantUserRolePermissions($user, $permissions);
foreach ($permissions as $permission) {
- $this->removePermissionFromUser($user, $permission);
+ $this->permissions->removeUserRolePermissions($user, [$permission]);
$resp = $this->actingAs($user)->post($chapter->getUrl('/convert-to-book'));
$this->assertPermissionError($resp);
- $this->giveUserPermissions($user, [$permission]);
+ $this->permissions->grantUserRolePermissions($user, [$permission]);
}
$resp = $this->actingAs($user)->post($chapter->getUrl('/convert-to-book'));
public function test_book_convert_to_shelf_requires_permissions()
{
$book = $this->entities->book();
- $user = $this->getViewer();
+ $user = $this->users->viewer();
$permissions = ['book-delete-all', 'bookshelf-create-all', 'book-update-all', 'book-create-all'];
- $this->giveUserPermissions($user, $permissions);
+ $this->permissions->grantUserRolePermissions($user, $permissions);
foreach ($permissions as $permission) {
- $this->removePermissionFromUser($user, $permission);
+ $this->permissions->removeUserRolePermissions($user, [$permission]);
$resp = $this->actingAs($user)->post($book->getUrl('/convert-to-shelf'));
$this->assertPermissionError($resp);
- $this->giveUserPermissions($user, [$permission]);
+ $this->permissions->grantUserRolePermissions($user, [$permission]);
}
$resp = $this->actingAs($user)->post($book->getUrl('/convert-to-shelf'));
public function test_entities_viewable_after_creator_deletion()
{
// Create required assets and revisions
- $creator = $this->getEditor();
- $updater = $this->getViewer();
+ $creator = $this->users->editor();
+ $updater = $this->users->viewer();
$entities = $this->entities->createChainBelongingToUser($creator, $updater);
app()->make(UserRepo::class)->destroy($creator);
$this->entities->updatePage($entities['page'], ['html' => '<p>hello!</p>>']);
public function test_entities_viewable_after_updater_deletion()
{
// Create required assets and revisions
- $creator = $this->getViewer();
- $updater = $this->getEditor();
+ $creator = $this->users->viewer();
+ $updater = $this->users->editor();
$entities = $this->entities->createChainBelongingToUser($creator, $updater);
app()->make(UserRepo::class)->destroy($updater);
$this->entities->updatePage($entities['page'], ['html' => '<p>Hello there!</p>']);
public function test_search_filters()
{
$page = $this->entities->newPage(['name' => 'My new test quaffleachits', 'html' => 'this is about an orange donkey danzorbhsing']);
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->actingAs($editor);
// Viewed filter searches
// Restricted filter
$this->get('/search?term=' . urlencode('danzorbhsing {is_restricted}'))->assertDontSee($page->name);
- $this->entities->setPermissions($page, ['view'], [$editor->roles->first()]);
+ $this->permissions->setEntityPermissions($page, ['view'], [$editor->roles->first()]);
$this->get('/search?term=' . urlencode('danzorbhsing {is_restricted}'))->assertSee($page->name);
// Date filters
$this->withHtml($resp)->assertElementContains($baseSelector, $page->name);
$this->withHtml($resp)->assertElementNotContains($baseSelector, "You don't have the required permissions to select this item");
- $resp = $this->actingAs($this->getViewer())->get($searchUrl);
+ $resp = $this->actingAs($this->users->viewer())->get($searchUrl);
$this->withHtml($resp)->assertElementContains($baseSelector, $page->name);
$this->withHtml($resp)->assertElementContains($baseSelector, "You don't have the required permissions to select this item");
}
$this->assertGreaterThan(2, count($chapter->pages), 'Ensure we\'re testing with at least 1 sibling');
$page = $chapter->pages->first();
- $search = $this->actingAs($this->getViewer())->get("/search/entity/siblings?entity_id={$page->id}&entity_type=page");
+ $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$page->id}&entity_type=page");
$search->assertSuccessful();
foreach ($chapter->pages as $page) {
$search->assertSee($page->name);
$bookChildren = $page->book->getDirectChildren();
$this->assertGreaterThan(2, count($bookChildren), 'Ensure we\'re testing with at least 1 sibling');
- $search = $this->actingAs($this->getViewer())->get("/search/entity/siblings?entity_id={$page->id}&entity_type=page");
+ $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$page->id}&entity_type=page");
$search->assertSuccessful();
foreach ($bookChildren as $child) {
$search->assertSee($child->name);
$bookChildren = $chapter->book->getDirectChildren();
$this->assertGreaterThan(2, count($bookChildren), 'Ensure we\'re testing with at least 1 sibling');
- $search = $this->actingAs($this->getViewer())->get("/search/entity/siblings?entity_id={$chapter->id}&entity_type=chapter");
+ $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$chapter->id}&entity_type=chapter");
$search->assertSuccessful();
foreach ($bookChildren as $child) {
$search->assertSee($child->name);
$book = $books->first();
$this->assertGreaterThan(2, count($books), 'Ensure we\'re testing with at least 1 sibling');
- $search = $this->actingAs($this->getViewer())->get("/search/entity/siblings?entity_id={$book->id}&entity_type=book");
+ $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$book->id}&entity_type=book");
$search->assertSuccessful();
foreach ($books as $expectedBook) {
$search->assertSee($expectedBook->name);
$shelf = $shelves->first();
$this->assertGreaterThan(2, count($shelves), 'Ensure we\'re testing with at least 1 sibling');
- $search = $this->actingAs($this->getViewer())->get("/search/entity/siblings?entity_id={$shelf->id}&entity_type=bookshelf");
+ $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$shelf->id}&entity_type=bookshelf");
$search->assertSuccessful();
foreach ($shelves as $expectedShelf) {
$search->assertSee($expectedShelf->name);
public function test_page_export_with_deleted_creator_and_updater()
{
- $user = $this->getViewer(['name' => 'ExportWizardTheFifth']);
+ $user = $this->users->viewer(['name' => 'ExportWizardTheFifth']);
$page = $this->entities->page();
$page->created_by = $user->id;
$page->updated_by = $user->id;
$chapter = $book->chapters()->first();
$page = $chapter->pages()->first();
$entities = [$book, $chapter, $page];
- $user = $this->getViewer();
+ $user = $this->users->viewer();
$this->actingAs($user);
foreach ($entities as $entity) {
$resp->assertSee('/export/pdf');
}
- /** @var Role $role */
- $this->removePermissionFromUser($user, 'content-export');
+ $this->permissions->removeUserRolePermissions($user, ['content-export']);
foreach ($entities as $entity) {
$resp = $this->get($entity->getUrl());
{
$page = $this->entities->page();
- $this->actingAs($this->getAdmin())
+ $this->actingAs($this->users->admin())
->put($page->getUrl(''), [
'name' => 'Testing',
'html' => '<p>"Hello & welcome"</p>',
$this->withHtml($resp)->assertElementNotContains('[name="html"]', $addedContent);
$newContent = $this->page->html . $addedContent;
- $newUser = $this->getEditor();
+ $newUser = $this->users->editor();
$this->pageRepo->updatePageDraft($this->page, ['html' => $newContent]);
$resp = $this->actingAs($newUser)->get($this->page->getUrl('/edit'));
$this->withHtml($resp)->assertElementNotContains('[name="html"]', $addedContent);
$newContent = $this->page->html . $addedContent;
- $newUser = $this->getEditor();
+ $newUser = $this->users->editor();
$this->pageRepo->updatePageDraft($this->page, ['html' => $newContent]);
$this->actingAs($newUser)
public function test_draft_save_shows_alert_if_draft_older_than_last_page_update()
{
- $admin = $this->getAdmin();
- $editor = $this->getEditor();
+ $admin = $this->users->admin();
+ $editor = $this->users->editor();
$page = $this->entities->page();
$this->actingAs($editor)->put('/ajax/page/' . $page->id . '/save-draft', [
public function test_draft_save_shows_alert_if_draft_edit_started_by_someone_else()
{
- $admin = $this->getAdmin();
- $editor = $this->getEditor();
+ $admin = $this->users->admin();
+ $editor = $this->users->editor();
$page = $this->entities->page();
$this->actingAs($admin)->put('/ajax/page/' . $page->id . '/save-draft', [
{
$book = $this->entities->book();
$chapter = $book->chapters->first();
- $newUser = $this->getEditor();
+ $newUser = $this->users->editor();
$this->actingAs($newUser)->get($book->getUrl('/create-page'));
$this->get($chapter->getUrl('/create-page'));
$page = $this->entities->page();
$this->createRevisions($page, 2);
- $viewer = $this->getViewer();
+ $viewer = $this->users->viewer();
$this->actingAs($viewer);
$respHtml = $this->withHtml($this->get($page->getUrl('/revisions')));
$respHtml->assertElementNotContains('.actions a', 'Restore');
$respHtml->assertElementNotExists('form[action$="/restore"]');
- $this->giveUserPermissions($viewer, ['page-update-all']);
+ $this->permissions->grantUserRolePermissions($viewer, ['page-update-all']);
$respHtml = $this->withHtml($this->get($page->getUrl('/revisions')));
$respHtml->assertElementContains('.actions a', 'Restore');
$page = $this->entities->page();
$this->createRevisions($page, 2);
- $viewer = $this->getViewer();
+ $viewer = $this->users->viewer();
$this->actingAs($viewer);
$respHtml = $this->withHtml($this->get($page->getUrl('/revisions')));
$respHtml->assertElementNotContains('.actions a', 'Delete');
$respHtml->assertElementNotExists('form[action$="/delete"]');
- $this->giveUserPermissions($viewer, ['page-delete-all']);
+ $this->permissions->grantUserRolePermissions($viewer, ['page-delete-all']);
$respHtml = $this->withHtml($this->get($page->getUrl('/revisions')));
$respHtml->assertElementContains('.actions a', 'Delete');
public function test_manage_templates_permission_required_to_change_page_template_status()
{
$page = $this->entities->page();
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->actingAs($editor);
$pageUpdateData = [
'template' => false,
]);
- $this->giveUserPermissions($editor, ['templates-manage']);
+ $this->permissions->grantUserRolePermissions($editor, ['templates-manage']);
$this->put($page->getUrl(), $pageUpdateData);
$this->assertDatabaseHas('pages', [
{
$content = '<div>my_custom_template_content</div>';
$page = $this->entities->page();
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->actingAs($editor);
$templateFetch = $this->get('/templates/' . $page->id);
public function test_template_endpoint_returns_paginated_list_of_templates()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->actingAs($editor);
$toBeTemplates = Page::query()->orderBy('name', 'asc')->take(12)->get();
public function test_page_view_when_creator_is_deleted_but_owner_exists()
{
$page = $this->entities->page();
- $user = $this->getViewer();
- $owner = $this->getEditor();
+ $user = $this->users->viewer();
+ $owner = $this->users->editor();
$page->created_by = $user->id;
$page->owned_by = $owner->id;
$page->save();
$page = $this->entities->page();
$currentBook = $page->book;
$newBook = Book::where('id', '!=', $currentBook->id)->first();
- $viewer = $this->getViewer();
+ $viewer = $this->users->viewer();
$resp = $this->actingAs($viewer)->get($page->getUrl());
$resp->assertDontSee($page->getUrl('/copy'));
$newBook->owned_by = $viewer->id;
$newBook->save();
- $this->giveUserPermissions($viewer, ['page-create-own']);
- $this->entities->regenPermissions($newBook);
+ $this->permissions->grantUserRolePermissions($viewer, ['page-create-own']);
+ $this->permissions->regenerateForEntity($newBook);
$resp = $this->actingAs($viewer)->get($page->getUrl());
$resp->assertSee($page->getUrl('/copy'));
public function test_recently_updated_pages_view()
{
- $user = $this->getEditor();
+ $user = $this->users->editor();
$content = $this->entities->createChainBelongingToUser($user);
$resp = $this->asAdmin()->get('/pages/recently-updated');
public function test_recently_updated_pages_view_shows_updated_by_details()
{
- $user = $this->getEditor();
+ $user = $this->users->editor();
$page = $this->entities->page();
$this->actingAs($user)->put($page->getUrl(), [
public function test_recently_updated_pages_view_shows_parent_chain()
{
- $user = $this->getEditor();
+ $user = $this->users->editor();
$page = $this->entities->pageWithinChapter();
$this->actingAs($user)->put($page->getUrl(), [
public function test_recently_updated_pages_view_does_not_show_parent_if_not_visible()
{
- $user = $this->getEditor();
+ $user = $this->users->editor();
$page = $this->entities->pageWithinChapter();
$this->actingAs($user)->put($page->getUrl(), [
'html' => '<p>Updated content</p>',
]);
- $this->entities->setPermissions($page->book);
- $this->entities->setPermissions($page, ['view'], [$user->roles->first()]);
+ $this->permissions->setEntityPermissions($page->book);
+ $this->permissions->setEntityPermissions($page, ['view'], [$user->roles->first()]);
$resp = $this->get('/pages/recently-updated');
$resp->assertDontSee($page->book->getShortName(42));
$newBook = Book::query()->where('id', '!=', $currentBook->id)->first();
$newChapter = $newBook->chapters()->first();
- $movePageResp = $this->actingAs($this->getEditor())->put($page->getUrl('/move'), [
+ $movePageResp = $this->actingAs($this->users->editor())->put($page->getUrl('/move'), [
'entity_selection' => 'chapter:' . $newChapter->id,
]);
$page->refresh();
$page = $oldChapter->pages()->first();
$newBook = Book::query()->where('id', '!=', $oldChapter->book_id)->first();
- $movePageResp = $this->actingAs($this->getEditor())->put($page->getUrl('/move'), [
+ $movePageResp = $this->actingAs($this->users->editor())->put($page->getUrl('/move'), [
'entity_selection' => 'book:' . $newBook->id,
]);
$page->refresh();
$page = $this->entities->page();
$currentBook = $page->book;
$newBook = Book::query()->where('id', '!=', $currentBook->id)->first();
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
- $this->entities->setPermissions($newBook, ['view', 'update', 'delete'], $editor->roles->all());
+ $this->permissions->setEntityPermissions($newBook, ['view', 'update', 'delete'], $editor->roles->all());
$movePageResp = $this->actingAs($editor)->put($page->getUrl('/move'), [
'entity_selection' => 'book:' . $newBook->id,
]);
$this->assertPermissionError($movePageResp);
- $this->entities->setPermissions($newBook, ['view', 'update', 'delete', 'create'], $editor->roles->all());
+ $this->permissions->setEntityPermissions($newBook, ['view', 'update', 'delete', 'create'], $editor->roles->all());
$movePageResp = $this->put($page->getUrl('/move'), [
'entity_selection' => 'book:' . $newBook->id,
]);
$page = $this->entities->page();
$currentBook = $page->book;
$newBook = Book::query()->where('id', '!=', $currentBook->id)->first();
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
- $this->entities->setPermissions($newBook, ['view', 'update', 'create', 'delete'], $editor->roles->all());
- $this->entities->setPermissions($page, ['view', 'update', 'create'], $editor->roles->all());
+ $this->permissions->setEntityPermissions($newBook, ['view', 'update', 'create', 'delete'], $editor->roles->all());
+ $this->permissions->setEntityPermissions($page, ['view', 'update', 'create'], $editor->roles->all());
$movePageResp = $this->actingAs($editor)->put($page->getUrl('/move'), [
'entity_selection' => 'book:' . $newBook->id,
$pageView = $this->get($page->getUrl());
$pageView->assertDontSee($page->getUrl('/move'));
- $this->entities->setPermissions($page, ['view', 'update', 'create', 'delete'], $editor->roles->all());
+ $this->permissions->setEntityPermissions($page, ['view', 'update', 'create', 'delete'], $editor->roles->all());
$movePageResp = $this->put($page->getUrl('/move'), [
'entity_selection' => 'book:' . $newBook->id,
]);
$chapter = $this->entities->chapter();
$currentBook = $chapter->book;
$newBook = Book::query()->where('id', '!=', $currentBook->id)->first();
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
- $this->entities->setPermissions($newBook, ['view', 'update', 'create', 'delete'], $editor->roles->all());
- $this->entities->setPermissions($chapter, ['view', 'update', 'create'], $editor->roles->all());
+ $this->permissions->setEntityPermissions($newBook, ['view', 'update', 'create', 'delete'], $editor->roles->all());
+ $this->permissions->setEntityPermissions($chapter, ['view', 'update', 'create'], $editor->roles->all());
$moveChapterResp = $this->actingAs($editor)->put($chapter->getUrl('/move'), [
'entity_selection' => 'book:' . $newBook->id,
$pageView = $this->get($chapter->getUrl());
$pageView->assertDontSee($chapter->getUrl('/move'));
- $this->entities->setPermissions($chapter, ['view', 'update', 'create', 'delete'], $editor->roles->all());
+ $this->permissions->setEntityPermissions($chapter, ['view', 'update', 'create', 'delete'], $editor->roles->all());
$moveChapterResp = $this->put($chapter->getUrl('/move'), [
'entity_selection' => 'book:' . $newBook->id,
]);
$chapter = $this->entities->chapter();
$currentBook = $chapter->book;
$newBook = Book::query()->where('id', '!=', $currentBook->id)->first();
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
- $this->entities->setPermissions($newBook, ['view', 'update', 'delete'], [$editor->roles->first()]);
- $this->entities->setPermissions($chapter, ['view', 'update', 'create', 'delete'], [$editor->roles->first()]);
+ $this->permissions->setEntityPermissions($newBook, ['view', 'update', 'delete'], [$editor->roles->first()]);
+ $this->permissions->setEntityPermissions($chapter, ['view', 'update', 'create', 'delete'], [$editor->roles->first()]);
$moveChapterResp = $this->actingAs($editor)->put($chapter->getUrl('/move'), [
'entity_selection' => 'book:' . $newBook->id,
]);
$this->assertPermissionError($moveChapterResp);
- $this->entities->setPermissions($newBook, ['view', 'update', 'create', 'delete'], [$editor->roles->first()]);
+ $this->permissions->setEntityPermissions($newBook, ['view', 'update', 'create', 'delete'], [$editor->roles->first()]);
$moveChapterResp = $this->put($chapter->getUrl('/move'), [
'entity_selection' => 'book:' . $newBook->id,
]);
$page = $this->entities->pageWithinChapter();
/** @var Chapter $otherChapter */
$otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
- $this->entities->setPermissions($otherChapter);
+ $this->permissions->setEntityPermissions($otherChapter);
$sortData = [
'id' => $page->id,
$page = $this->entities->pageWithinChapter();
/** @var Chapter $otherChapter */
$otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
- $editor = $this->getEditor();
- $this->entities->setPermissions($otherChapter->book, ['update', 'delete'], [$editor->roles()->first()]);
+ $editor = $this->users->editor();
+ $this->permissions->setEntityPermissions($otherChapter->book, ['update', 'delete'], [$editor->roles()->first()]);
$sortData = [
'id' => $page->id,
$page = $this->entities->pageWithinChapter();
/** @var Chapter $otherChapter */
$otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
- $editor = $this->getEditor();
- $this->entities->setPermissions($otherChapter, ['view', 'delete'], [$editor->roles()->first()]);
+ $editor = $this->users->editor();
+ $this->permissions->setEntityPermissions($otherChapter, ['view', 'delete'], [$editor->roles()->first()]);
$sortData = [
'id' => $page->id,
$page = $this->entities->pageWithinChapter();
/** @var Chapter $otherChapter */
$otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
- $editor = $this->getEditor();
- $this->entities->setPermissions($page, ['view', 'delete'], [$editor->roles()->first()]);
+ $editor = $this->users->editor();
+ $this->permissions->setEntityPermissions($page, ['view', 'delete'], [$editor->roles()->first()]);
$sortData = [
'id' => $page->id,
$page = $this->entities->pageWithinChapter();
/** @var Chapter $otherChapter */
$otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
- $editor = $this->getEditor();
- $this->entities->setPermissions($page, ['view', 'update'], [$editor->roles()->first()]);
+ $editor = $this->users->editor();
+ $this->permissions->setEntityPermissions($page, ['view', 'update'], [$editor->roles()->first()]);
$sortData = [
'id' => $page->id,
$this->asEditor()->get('/ajax/tags/suggest/names?search=co')->assertSimilarJson(['color', 'country']);
// Set restricted permission the page
- $this->entities->setPermissions($page, [], []);
+ $this->permissions->setEntityPermissions($page, [], []);
$this->asAdmin()->get('/ajax/tags/suggest/names?search=co')->assertSimilarJson(['color', 'country']);
$this->asEditor()->get('/ajax/tags/suggest/names?search=co')->assertSimilarJson([]);
$resp = $this->get('/tags?name=SuperCategory');
$resp->assertSee('GreatTestContent');
- $this->entities->setPermissions($page, [], []);
+ $this->permissions->setEntityPermissions($page, [], []);
$resp = $this->asEditor()->get('/tags');
$resp->assertDontSee('SuperCategory');
// Due to middleware being handled differently this will not fail
// if our custom, middleware-loaded handler fails but this is here
// as a reminder and as a general check in the event of other issues.
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$editor->name = 'tester';
$editor->save();
public function test_item_not_found_does_not_get_logged_to_file()
{
- $this->actingAs($this->getViewer());
+ $this->actingAs($this->users->viewer());
$handler = $this->withTestLogger();
$book = $this->entities->book();
public function test_access_to_non_existing_image_location_provides_404_response()
{
- $resp = $this->actingAs($this->getViewer())->get('/uploads/images/gallery/2021-05/anonexistingimage.png');
+ $resp = $this->actingAs($this->users->viewer())->get('/uploads/images/gallery/2021-05/anonexistingimage.png');
$resp->assertStatus(404);
$resp->assertSeeText('Image Not Found');
}
public function test_page_add_favourite_flow()
{
$page = $this->entities->page();
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$resp = $this->actingAs($editor)->get($page->getUrl());
$this->withHtml($resp)->assertElementContains('button', 'Favourite');
public function test_page_remove_favourite_flow()
{
$page = $this->entities->page();
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
Favourite::query()->forceCreate([
'user_id' => $editor->id,
'favouritable_id' => $page->id,
$book->owned_by = $user->id;
$book->save();
- $this->giveUserPermissions($user, ['book-view-own']);
+ $this->permissions->grantUserRolePermissions($user, ['book-view-own']);
$this->actingAs($user)->get($book->getUrl());
$resp = $this->post('/favourites/add', [
public function test_each_entity_type_shows_favourite_button()
{
- $this->actingAs($this->getEditor());
+ $this->actingAs($this->users->editor());
foreach ($this->entities->all() as $entity) {
$resp = $this->get($entity->getUrl());
$this->setSettings(['app-public' => 'true']);
$resp = $this->get('/');
$this->withHtml($resp)->assertElementNotContains('header', 'My Favourites');
- $resp = $this->actingAs($this->getViewer())->get('/');
+ $resp = $this->actingAs($this->users->viewer())->get('/');
$this->withHtml($resp)->assertElementContains('header a', 'My Favourites');
}
public function test_favourites_shown_on_homepage()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$resp = $this->actingAs($editor)->get('/');
$this->withHtml($resp)->assertElementNotExists('#top-favourites');
public function test_favourites_list_page_shows_favourites_and_has_working_pagination()
{
$page = $this->entities->page();
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$resp = $this->actingAs($editor)->get('/favourites');
$resp->assertDontSee($page->name);
namespace Tests\Helpers;
-use BookStack\Auth\Permissions\EntityPermission;
-use BookStack\Auth\Role;
use BookStack\Auth\User;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Bookshelf;
return $pageRepo->publishDraft($draftPage, $input);
}
- /**
- * Regenerate the permission for an entity.
- * Centralised to manage clearing of cached elements between requests.
- */
- public function regenPermissions(Entity $entity): void
- {
- $entity->rebuildPermissions();
- $entity->load('jointPermissions');
- }
-
- /**
- * Set the given entity as having restricted permissions, and apply the given
- * permissions for the given roles.
- * @param string[] $actions
- * @param Role[] $roles
- */
- public function setPermissions(Entity $entity, array $actions = [], array $roles = []): void
- {
- $entity->permissions()->delete();
-
- $permissions = [
- // Set default permissions to not allow actions so that only the provided role permissions are at play.
- ['role_id' => 0, 'view' => false, 'create' => false, 'update' => false, 'delete' => false],
- ];
-
- foreach ($roles as $role) {
- $permission = ['role_id' => $role->id];
- foreach (EntityPermission::PERMISSIONS as $possibleAction) {
- $permission[$possibleAction] = in_array($possibleAction, $actions);
- }
- $permissions[] = $permission;
- }
-
- $entity->permissions()->createMany($permissions);
- $entity->load('permissions');
- $this->regenPermissions($entity);
- }
-
/**
* @param Entity|Entity[] $entities
*/
--- /dev/null
+<?php
+
+namespace Tests\Helpers;
+
+use BookStack\Auth\Permissions\EntityPermission;
+use BookStack\Auth\Permissions\RolePermission;
+use BookStack\Auth\Role;
+use BookStack\Auth\User;
+use BookStack\Entities\Models\Entity;
+
+class PermissionsProvider
+{
+ protected UserRoleProvider $userRoleProvider;
+
+ public function __construct(UserRoleProvider $userRoleProvider)
+ {
+ $this->userRoleProvider = $userRoleProvider;
+ }
+
+ /**
+ * Grant role permissions to the provided user.
+ */
+ public function grantUserRolePermissions(User $user, array $permissions): void
+ {
+ $newRole = $this->userRoleProvider->createRole($permissions);
+ $user->attachRole($newRole);
+ $user->load('roles');
+ $user->clearPermissionCache();
+ }
+
+ /**
+ * Completely remove specific role permissions from the provided user.
+ */
+ public function removeUserRolePermissions(User $user, array $permissions): void
+ {
+ foreach ($permissions as $permissionName) {
+ /** @var RolePermission $permission */
+ $permission = RolePermission::query()
+ ->where('name', '=', $permissionName)
+ ->firstOrFail();
+
+ $roles = $user->roles()->whereHas('permissions', function ($query) use ($permission) {
+ $query->where('id', '=', $permission->id);
+ })->get();
+
+ /** @var Role $role */
+ foreach ($roles as $role) {
+ $role->detachPermission($permission);
+ }
+
+ $user->clearPermissionCache();
+ }
+ }
+
+ /**
+ * Change the owner of the given entity to the given user.
+ */
+ public function changeEntityOwner(Entity $entity, User $newOwner): void
+ {
+ $entity->owned_by = $newOwner->id;
+ $entity->save();
+ $entity->rebuildPermissions();
+ }
+
+ /**
+ * Regenerate the permission for an entity.
+ * Centralised to manage clearing of cached elements between requests.
+ */
+ public function regenerateForEntity(Entity $entity): void
+ {
+ $entity->rebuildPermissions();
+ }
+
+ /**
+ * Set the given entity as having restricted permissions, and apply the given
+ * permissions for the given roles.
+ * @param string[] $actions
+ * @param Role[] $roles
+ */
+ public function setEntityPermissions(Entity $entity, array $actions = [], array $roles = [], $inherit = false): void
+ {
+ $entity->permissions()->delete();
+
+ $permissions = [];
+
+ if (!$inherit) {
+ // Set default permissions to not allow actions so that only the provided role permissions are at play.
+ $permissions[] = ['role_id' => null, 'user_id' => null, 'view' => false, 'create' => false, 'update' => false, 'delete' => false];
+ }
+
+ foreach ($roles as $role) {
+ $permissions[] = $this->actionListToEntityPermissionData($actions, $role->id);
+ }
+
+ $this->addEntityPermissionEntries($entity, $permissions);
+ }
+
+ public function addEntityPermission(Entity $entity, array $actionList, ?Role $role = null, ?User $user = null)
+ {
+ $permissionData = $this->actionListToEntityPermissionData($actionList, $role->id ?? null, $user->id ?? null);
+ $this->addEntityPermissionEntries($entity, [$permissionData]);
+ }
+
+ /**
+ * Disable inherited permissions on the given entity.
+ * Effectively sets the "Other Users" UI permission option to not inherit, with no permissions.
+ */
+ public function disableEntityInheritedPermissions(Entity $entity): void
+ {
+ $entity->permissions()->whereNull(['user_id', 'role_id'])->delete();
+ $fallback = $this->actionListToEntityPermissionData([]);
+ $this->addEntityPermissionEntries($entity, [$fallback]);
+ }
+
+ protected function addEntityPermissionEntries(Entity $entity, array $entityPermissionData): void
+ {
+ $entity->permissions()->createMany($entityPermissionData);
+ $entity->load('permissions');
+ $this->regenerateForEntity($entity);
+ }
+
+ /**
+ * For the given simple array of string actions (view, create, update, delete), convert
+ * the format to entity permission data, where permission is granted if the action is in the
+ * given actionList array.
+ */
+ protected function actionListToEntityPermissionData(array $actionList, int $roleId = null, int $userId = null): array
+ {
+ $permissionData = ['role_id' => $roleId, 'user_id' => $userId];
+ foreach (EntityPermission::PERMISSIONS as $possibleAction) {
+ $permissionData[$possibleAction] = in_array($possibleAction, $actionList);
+ }
+
+ return $permissionData;
+ }
+}
--- /dev/null
+<?php
+
+namespace Tests\Helpers;
+
+use BookStack\Auth\Permissions\PermissionsRepo;
+use BookStack\Auth\Role;
+use BookStack\Auth\User;
+
+class UserRoleProvider
+{
+ protected ?User $admin = null;
+ protected ?User $editor = null;
+
+ /**
+ * Get a typical "Admin" user.
+ */
+ public function admin(): User
+ {
+ if (is_null($this->admin)) {
+ $adminRole = Role::getSystemRole('admin');
+ $this->admin = $adminRole->users->first();
+ }
+
+ return $this->admin;
+ }
+
+ /**
+ * Get a typical "Editor" user.
+ */
+ public function editor(): User
+ {
+ if ($this->editor === null) {
+ $editorRole = Role::getRole('editor');
+ $this->editor = $editorRole->users->first();
+ }
+
+ return $this->editor;
+ }
+
+ /**
+ * Get a typical "Viewer" user.
+ */
+ public function viewer(array $attributes = []): User
+ {
+ $user = Role::getRole('viewer')->users()->first();
+ if (!empty($attributes)) {
+ $user->forceFill($attributes)->save();
+ }
+
+ return $user;
+ }
+
+ /**
+ * Create a new fresh user without any relations.
+ */
+ public function newUser(array $attrs = []): User
+ {
+ return User::factory()->create($attrs);
+ }
+
+ /**
+ * Create a new fresh user, with the given attrs, that has assigned a fresh role
+ * that has the given role permissions.
+ * Intended as a helper to create a blank slate baseline user and role.
+ * @return array{0: User, 1: Role}
+ */
+ public function newUserWithRole(array $userAttrs = [], array $rolePermissions = []): array
+ {
+ $user = $this->newUser($userAttrs);
+ $role = $this->attachNewRole($user, $rolePermissions);
+
+ return [$user, $role];
+ }
+
+ /**
+ * Attach a new role, with the given role permissions, to the given user
+ * and return that role.
+ */
+ public function attachNewRole(User $user, array $rolePermissions = []): Role
+ {
+ $role = $this->createRole($rolePermissions);
+ $user->attachRole($role);
+ return $role;
+ }
+
+ /**
+ * Create a new basic role with the given role permissions.
+ */
+ public function createRole(array $rolePermissions = []): Role
+ {
+ $permissionRepo = app(PermissionsRepo::class);
+ $roleData = Role::factory()->make()->toArray();
+ $roleData['permissions'] = array_flip($rolePermissions);
+
+ return $permissionRepo->saveNewRole($roleData);
+ }
+}
public function test_set_book_homepage()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
setting()->putUser($editor, 'books_view_type', 'grid');
$this->setSettings(['app-homepage-type' => 'books']);
public function test_set_bookshelves_homepage()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
setting()->putUser($editor, 'bookshelves_view_type', 'grid');
$shelf = $this->entities->shelf();
public function test_shelves_list_homepage_adheres_to_book_visibility_permissions()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
setting()->putUser($editor, 'bookshelves_view_type', 'list');
$this->setSettings(['app-homepage-type' => 'bookshelves']);
$this->asEditor();
// Ensure book no longer visible without view permission
$editor->roles()->detach();
- $this->giveUserPermissions($editor, ['bookshelf-view-all']);
+ $this->permissions->grantUserRolePermissions($editor, ['bookshelf-view-all']);
$homeVisit = $this->get('/');
$this->withHtml($homeVisit)->assertElementContains('.content-wrap', $shelf->name);
$this->withHtml($homeVisit)->assertElementNotContains('.content-wrap', $book->name);
// Ensure is visible again with entity-level view permission
- $this->entities->setPermissions($book, ['view'], [$editor->roles()->first()]);
+ $this->permissions->setEntityPermissions($book, ['view'], [$editor->roles()->first()]);
$homeVisit = $this->get('/');
$this->withHtml($homeVisit)->assertElementContains('.content-wrap', $shelf->name);
$this->withHtml($homeVisit)->assertElementContains('.content-wrap', $book->name);
class LanguageTest extends TestCase
{
- protected array $langs;
+ protected $langs;
/**
* LanguageTest constructor.
{
$this->asEditor();
$this->assertFalse(config('app.rtl'), 'App RTL config should be false by default');
- setting()->putUser($this->getEditor(), 'language', 'ar');
+ setting()->putUser($this->users->editor(), 'language', 'ar');
$this->get('/');
$this->assertTrue(config('app.rtl'), 'App RTL config should have been set to true by middleware');
}
-
- public function test_pluralisation_for_non_standard_locales()
- {
- $text = trans_choice('entities.x_pages', 1, [], 'de_informal');
- $this->assertEquals('1 Seite', $text);
-
- $text = trans_choice('entities.x_pages', 2, [], 'de_informal');
- $this->assertEquals('2 Seiten', $text);
-
- $text = trans_choice('entities.x_pages', 0, [], 'de_informal');
- $this->assertEquals('0 Seiten', $text);
- }
}
namespace Tests\Permissions;
+use BookStack\Auth\Role;
use BookStack\Auth\User;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Bookshelf;
protected function setUp(): void
{
parent::setUp();
- $this->user = $this->getEditor();
- $this->viewer = $this->getViewer();
+ $this->user = $this->users->editor();
+ $this->viewer = $this->users->viewer();
}
protected function setRestrictionsForTestRoles(Entity $entity, array $actions = [])
$this->user->roles->first(),
$this->viewer->roles->first(),
];
- $this->entities->setPermissions($entity, $actions, $roles);
+ $this->permissions->setEntityPermissions($entity, $actions, $roles);
}
public function test_bookshelf_view_restriction()
$this->put($modelInstance->getUrl('/permissions'), [
'permissions' => [
- $roleId => [
- $permission => 'true',
+ 'role' => [
+ $roleId => [
+ $permission => 'true',
+ ],
],
],
]);
$resp->assertRedirect($book->getUrl('/page/test-page'));
}
+ public function test_access_to_item_prevented_if_inheritance_active_but_permission_prevented_via_role()
+ {
+ $user = $this->users->viewer();
+ $viewerRole = $user->roles->first();
+ $chapter = $this->entities->chapter();
+ $book = $chapter->book;
+
+ $this->permissions->setEntityPermissions($book, ['edit'], [$viewerRole], false);
+ $this->permissions->setEntityPermissions($chapter, [], [$viewerRole], true);
+
+ $this->assertFalse(userCan('chapter-update', $chapter));
+ }
+
+ public function test_access_to_item_allowed_if_inheritance_active_and_permission_prevented_via_role_but_allowed_via_parent()
+ {
+ $user = $this->users->viewer();
+ $viewerRole = $user->roles->first();
+ $editorRole = Role::getRole('Editor');
+ $user->attachRole($editorRole);
+ $chapter = $this->entities->chapter();
+ $book = $chapter->book;
+
+ $this->permissions->setEntityPermissions($book, ['edit'], [$editorRole], false);
+ $this->permissions->setEntityPermissions($chapter, [], [$viewerRole], true);
+
+ $this->assertTrue(userCan('chapter-update', $chapter));
+ }
+
public function test_book_permissions_can_be_generated_without_error_if_child_chapter_is_in_recycle_bin()
{
$book = $this->entities->bookHasChaptersAndPages();
$error = null;
try {
- $this->entities->setPermissions($book, ['view'], []);
+ $this->permissions->setEntityPermissions($book, ['view'], []);
} catch (Exception $e) {
$error = $e;
}
$pageContent = Str::random(48);
$page->html = '<p>' . $pageContent . '</p>';
$page->save();
- $viewer = $this->getViewer();
+ $viewer = $this->users->viewer();
$this->actingAs($viewer);
$formats = ['html', 'plaintext'];
$resp->assertSee($pageContent);
}
- $this->entities->setPermissions($page, []);
+ $this->permissions->setEntityPermissions($page, []);
foreach ($formats as $format) {
$resp = $this->get($chapter->getUrl("export/{$format}"));
$pageContent = Str::random(48);
$page->html = '<p>' . $pageContent . '</p>';
$page->save();
- $viewer = $this->getViewer();
+ $viewer = $this->users->viewer();
$this->actingAs($viewer);
$formats = ['html', 'plaintext'];
$resp->assertSee($pageContent);
}
- $this->entities->setPermissions($page, []);
+ $this->permissions->setEntityPermissions($page, []);
foreach ($formats as $format) {
$resp = $this->get($book->getUrl("export/{$format}"));
protected function setUp(): void
{
parent::setUp();
- $this->user = $this->getViewer();
+ $this->user = $this->users->viewer();
}
public function test_admin_can_see_settings()
public function test_role_cannot_be_deleted_if_default()
{
- $newRole = $this->createNewRole();
+ $newRole = $this->users->createRole();
$this->setSettings(['registration-role' => $newRole->id]);
$deletePageUrl = '/settings/roles/delete/' . $newRole->id;
{
/** @var Role $adminRole */
$adminRole = Role::query()->where('system_name', '=', 'admin')->first();
- $adminUser = $this->getAdmin();
+ $adminUser = $this->users->admin();
$adminRole->users()->where('id', '!=', $adminUser->id)->delete();
$this->assertEquals(1, $adminRole->users()->count());
- $viewerRole = $this->getViewer()->roles()->first();
+ $viewerRole = $this->users->viewer()->roles()->first();
$editUrl = '/settings/users/' . $adminUser->id;
$resp = $this->actingAs($adminUser)->put($editUrl, [
$roleA = Role::query()->create(['display_name' => 'Entity Permissions Delete Test']);
$page = $this->entities->page();
- $this->entities->setPermissions($page, ['view'], [$roleA]);
+ $this->permissions->setEntityPermissions($page, ['view'], [$roleA]);
$this->assertDatabaseHas('entity_permissions', [
'role_id' => $roleA->id,
public function test_manage_user_permission()
{
$this->actingAs($this->user)->get('/settings/users')->assertRedirect('/');
- $this->giveUserPermissions($this->user, ['users-manage']);
+ $this->permissions->grantUserRolePermissions($this->user, ['users-manage']);
$this->actingAs($this->user)->get('/settings/users')->assertOk();
}
{
$usersLink = 'href="' . url('/settings/users') . '"';
$this->actingAs($this->user)->get('/')->assertDontSee($usersLink, false);
- $this->giveUserPermissions($this->user, ['users-manage']);
+ $this->permissions->grantUserRolePermissions($this->user, ['users-manage']);
$this->actingAs($this->user)->get('/')->assertSee($usersLink, false);
- $this->giveUserPermissions($this->user, ['settings-manage', 'users-manage']);
+ $this->permissions->grantUserRolePermissions($this->user, ['settings-manage', 'users-manage']);
$this->actingAs($this->user)->get('/')->assertDontSee($usersLink, false);
}
'name' => 'my_new_name',
]);
- $this->giveUserPermissions($this->user, ['users-manage']);
+ $this->permissions->grantUserRolePermissions($this->user, ['users-manage']);
$resp = $this->get($userProfileUrl)
->assertOk();
{
$this->actingAs($this->user)->get('/settings/roles')->assertRedirect('/');
$this->get('/settings/roles/1')->assertRedirect('/');
- $this->giveUserPermissions($this->user, ['user-roles-manage']);
+ $this->permissions->grantUserRolePermissions($this->user, ['user-roles-manage']);
$this->actingAs($this->user)->get('/settings/roles')->assertOk();
$this->get('/settings/roles/1')
->assertOk()
public function test_settings_manage_permission()
{
$this->actingAs($this->user)->get('/settings/features')->assertRedirect('/');
- $this->giveUserPermissions($this->user, ['settings-manage']);
+ $this->permissions->grantUserRolePermissions($this->user, ['settings-manage']);
$this->get('/settings/features')->assertOk();
$resp = $this->post('/settings/features', []);
$this->actingAs($this->user)->get($page->getUrl())->assertDontSee('Permissions');
$this->get($page->getUrl('/permissions'))->assertRedirect('/');
- $this->giveUserPermissions($this->user, ['restrictions-manage-all']);
+ $this->permissions->grantUserRolePermissions($this->user, ['restrictions-manage-all']);
$this->actingAs($this->user)->get($page->getUrl())->assertSee('Permissions');
$this->actingAs($this->user)->get($page->getUrl())->assertDontSee('Permissions');
$this->get($page->getUrl('/permissions'))->assertRedirect('/');
- $this->giveUserPermissions($this->user, ['restrictions-manage-own']);
+ $this->permissions->grantUserRolePermissions($this->user, ['restrictions-manage-own']);
// Check can't restrict other's content
$this->actingAs($this->user)->get($otherUsersPage->getUrl())->assertDontSee('Permissions');
$this->withHtml($resp)->assertElementNotContains('.action-buttons', $text);
}
- $this->giveUserPermissions($this->user, [$permission]);
+ $this->permissions->grantUserRolePermissions($this->user, [$permission]);
foreach ($accessUrls as $url) {
$this->actingAs($this->user)->get($url)->assertOk();
$otherShelf = Bookshelf::query()->first();
$ownShelf = $this->entities->newShelf(['name' => 'test-shelf', 'slug' => 'test-shelf']);
$ownShelf->forceFill(['owned_by' => $this->user->id, 'updated_by' => $this->user->id])->save();
- $this->entities->regenPermissions($ownShelf);
+ $this->permissions->regenerateForEntity($ownShelf);
$this->checkAccessPermission('bookshelf-update-own', [
$ownShelf->getUrl('/edit'),
public function test_bookshelves_delete_own_permission()
{
- $this->giveUserPermissions($this->user, ['bookshelf-update-all']);
+ $this->permissions->grantUserRolePermissions($this->user, ['bookshelf-update-all']);
/** @var Bookshelf $otherShelf */
$otherShelf = Bookshelf::query()->first();
$ownShelf = $this->entities->newShelf(['name' => 'test-shelf', 'slug' => 'test-shelf']);
$ownShelf->forceFill(['owned_by' => $this->user->id, 'updated_by' => $this->user->id])->save();
- $this->entities->regenPermissions($ownShelf);
+ $this->permissions->regenerateForEntity($ownShelf);
$this->checkAccessPermission('bookshelf-delete-own', [
$ownShelf->getUrl('/delete'),
public function test_bookshelves_delete_all_permission()
{
- $this->giveUserPermissions($this->user, ['bookshelf-update-all']);
+ $this->permissions->grantUserRolePermissions($this->user, ['bookshelf-update-all']);
/** @var Bookshelf $otherShelf */
$otherShelf = Bookshelf::query()->first();
$this->checkAccessPermission('bookshelf-delete-all', [
public function test_books_delete_own_permission()
{
- $this->giveUserPermissions($this->user, ['book-update-all']);
+ $this->permissions->grantUserRolePermissions($this->user, ['book-update-all']);
/** @var Book $otherBook */
$otherBook = Book::query()->take(1)->get()->first();
$ownBook = $this->entities->createChainBelongingToUser($this->user)['book'];
public function test_books_delete_all_permission()
{
- $this->giveUserPermissions($this->user, ['book-update-all']);
+ $this->permissions->grantUserRolePermissions($this->user, ['book-update-all']);
/** @var Book $otherBook */
$otherBook = Book::query()->take(1)->get()->first();
$this->checkAccessPermission('book-delete-all', [
public function test_chapter_delete_own_permission()
{
- $this->giveUserPermissions($this->user, ['chapter-update-all']);
+ $this->permissions->grantUserRolePermissions($this->user, ['chapter-update-all']);
/** @var Chapter $otherChapter */
$otherChapter = Chapter::query()->first();
$ownChapter = $this->entities->createChainBelongingToUser($this->user)['chapter'];
public function test_chapter_delete_all_permission()
{
- $this->giveUserPermissions($this->user, ['chapter-update-all']);
+ $this->permissions->grantUserRolePermissions($this->user, ['chapter-update-all']);
/** @var Chapter $otherChapter */
$otherChapter = Chapter::query()->first();
$this->checkAccessPermission('chapter-delete-all', [
$ownChapter->getUrl() => 'New Page',
]);
- $this->giveUserPermissions($this->user, ['page-create-own']);
+ $this->permissions->grantUserRolePermissions($this->user, ['page-create-own']);
foreach ($accessUrls as $index => $url) {
$resp = $this->actingAs($this->user)->get($url);
$chapter->getUrl() => 'New Page',
]);
- $this->giveUserPermissions($this->user, ['page-create-all']);
+ $this->permissions->grantUserRolePermissions($this->user, ['page-create-all']);
foreach ($accessUrls as $index => $url) {
$resp = $this->actingAs($this->user)->get($url);
public function test_page_delete_own_permission()
{
- $this->giveUserPermissions($this->user, ['page-update-all']);
+ $this->permissions->grantUserRolePermissions($this->user, ['page-update-all']);
/** @var Page $otherPage */
$otherPage = Page::query()->first();
$ownPage = $this->entities->createChainBelongingToUser($this->user)['page'];
public function test_page_delete_all_permission()
{
- $this->giveUserPermissions($this->user, ['page-update-all']);
+ $this->permissions->grantUserRolePermissions($this->user, ['page-update-all']);
/** @var Page $otherPage */
$otherPage = Page::query()->first();
public function test_image_delete_own_permission()
{
- $this->giveUserPermissions($this->user, ['image-update-all']);
+ $this->permissions->grantUserRolePermissions($this->user, ['image-update-all']);
$page = $this->entities->page();
$image = Image::factory()->create([
'uploaded_to' => $page->id,
$this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertStatus(403);
- $this->giveUserPermissions($this->user, ['image-delete-own']);
+ $this->permissions->grantUserRolePermissions($this->user, ['image-delete-own']);
$this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertOk();
$this->assertDatabaseMissing('images', ['id' => $image->id]);
public function test_image_delete_all_permission()
{
- $this->giveUserPermissions($this->user, ['image-update-all']);
- $admin = $this->getAdmin();
+ $this->permissions->grantUserRolePermissions($this->user, ['image-update-all']);
+ $admin = $this->users->admin();
$page = $this->entities->page();
$image = Image::factory()->create(['uploaded_to' => $page->id, 'created_by' => $admin->id, 'updated_by' => $admin->id]);
$this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertStatus(403);
- $this->giveUserPermissions($this->user, ['image-delete-own']);
+ $this->permissions->grantUserRolePermissions($this->user, ['image-delete-own']);
$this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertStatus(403);
- $this->giveUserPermissions($this->user, ['image-delete-all']);
+ $this->permissions->grantUserRolePermissions($this->user, ['image-delete-all']);
$this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertOk();
$this->assertDatabaseMissing('images', ['id' => $image->id]);
// To cover issue fixed in f99c8ff99aee9beb8c692f36d4b84dc6e651e50a.
$page = $this->entities->page();
$viewerRole = Role::getRole('viewer');
- $viewer = $this->getViewer();
+ $viewer = $this->users->viewer();
$this->actingAs($viewer)->get($page->getUrl())->assertOk();
$this->asAdmin()->put('/settings/roles/' . $viewerRole->id, [
public function test_empty_state_actions_not_visible_without_permission()
{
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
// Book links
$book = Book::factory()->create(['created_by' => $admin->id, 'updated_by' => $admin->id]);
- $this->entities->regenPermissions($book);
- $this->actingAs($this->getViewer())->get($book->getUrl())
+ $this->permissions->regenerateForEntity($book);
+ $this->actingAs($this->users->viewer())->get($book->getUrl())
->assertDontSee('Create a new page')
->assertDontSee('Add a chapter');
// Chapter links
$chapter = Chapter::factory()->create(['created_by' => $admin->id, 'updated_by' => $admin->id, 'book_id' => $book->id]);
- $this->entities->regenPermissions($chapter);
- $this->actingAs($this->getViewer())->get($chapter->getUrl())
+ $this->permissions->regenerateForEntity($chapter);
+ $this->actingAs($this->users->viewer())->get($chapter->getUrl())
->assertDontSee('Create a new page')
->assertDontSee('Sort the current book');
}
->addComment($ownPage)
->assertStatus(403);
- $this->giveUserPermissions($this->user, ['comment-create-all']);
+ $this->permissions->grantUserRolePermissions($this->user, ['comment-create-all']);
$this->actingAs($this->user)
->addComment($ownPage)
public function test_comment_update_own_permission()
{
$ownPage = $this->entities->createChainBelongingToUser($this->user)['page'];
- $this->giveUserPermissions($this->user, ['comment-create-all']);
+ $this->permissions->grantUserRolePermissions($this->user, ['comment-create-all']);
$this->actingAs($this->user)->addComment($ownPage);
/** @var Comment $comment */
$comment = $ownPage->comments()->latest()->first();
// no comment-update-own
$this->actingAs($this->user)->updateComment($comment)->assertStatus(403);
- $this->giveUserPermissions($this->user, ['comment-update-own']);
+ $this->permissions->grantUserRolePermissions($this->user, ['comment-update-own']);
// now has comment-update-own
$this->actingAs($this->user)->updateComment($comment)->assertOk();
// no comment-update-all
$this->actingAs($this->user)->updateComment($comment)->assertStatus(403);
- $this->giveUserPermissions($this->user, ['comment-update-all']);
+ $this->permissions->grantUserRolePermissions($this->user, ['comment-update-all']);
// now has comment-update-all
$this->actingAs($this->user)->updateComment($comment)->assertOk();
{
/** @var Page $ownPage */
$ownPage = $this->entities->createChainBelongingToUser($this->user)['page'];
- $this->giveUserPermissions($this->user, ['comment-create-all']);
+ $this->permissions->grantUserRolePermissions($this->user, ['comment-create-all']);
$this->actingAs($this->user)->addComment($ownPage);
/** @var Comment $comment */
// no comment-delete-own
$this->actingAs($this->user)->deleteComment($comment)->assertStatus(403);
- $this->giveUserPermissions($this->user, ['comment-delete-own']);
+ $this->permissions->grantUserRolePermissions($this->user, ['comment-delete-own']);
// now has comment-update-own
$this->actingAs($this->user)->deleteComment($comment)->assertOk();
// no comment-delete-all
$this->actingAs($this->user)->deleteComment($comment)->assertStatus(403);
- $this->giveUserPermissions($this->user, ['comment-delete-all']);
+ $this->permissions->grantUserRolePermissions($this->user, ['comment-delete-all']);
// now has comment-delete-all
$this->actingAs($this->user)->deleteComment($comment)->assertOk();
--- /dev/null
+<?php
+
+namespace Tests\Permissions\Scenarios;
+
+class EntityRolePermissionsTest extends PermissionScenarioTestCase
+{
+ public function test_01_explicit_allow()
+ {
+ [$user, $role] = $this->users->newUserWithRole();
+ $page = $this->entities->page();
+ $this->permissions->setEntityPermissions($page, ['view'], [$role], false);
+
+ $this->assertVisibleToUser($page, $user);
+ }
+
+ public function test_02_explicit_deny()
+ {
+ [$user, $role] = $this->users->newUserWithRole();
+ $page = $this->entities->page();
+ $this->permissions->setEntityPermissions($page, [], [$role], false);
+
+ $this->assertNotVisibleToUser($page, $user);
+ }
+
+ public function test_03_same_level_conflicting()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole();
+ $roleB = $this->users->attachNewRole($user);
+ $page = $this->entities->page();
+
+ $this->permissions->disableEntityInheritedPermissions($page);
+ $this->permissions->addEntityPermission($page, [], $roleA);
+ $this->permissions->addEntityPermission($page, ['view'], $roleB);
+
+ $this->assertVisibleToUser($page, $user);
+ }
+
+ public function test_20_inherit_allow()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole();
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+
+ $this->permissions->disableEntityInheritedPermissions($chapter);
+ $this->permissions->addEntityPermission($chapter, ['view'], $roleA);
+
+ $this->assertVisibleToUser($page, $user);
+ }
+
+ public function test_21_inherit_deny()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole();
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+
+ $this->permissions->disableEntityInheritedPermissions($chapter);
+ $this->permissions->addEntityPermission($chapter, [], $roleA);
+
+ $this->assertNotVisibleToUser($page, $user);
+ }
+
+ public function test_22_same_level_conflict_inherit()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole();
+ $roleB = $this->users->attachNewRole($user);
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+
+ $this->permissions->disableEntityInheritedPermissions($chapter);
+ $this->permissions->addEntityPermission($chapter, [], $roleA);
+ $this->permissions->addEntityPermission($chapter, ['view'], $roleB);
+
+ $this->assertVisibleToUser($page, $user);
+ }
+
+ public function test_30_child_inherit_override_allow()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole();
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+
+ $this->permissions->disableEntityInheritedPermissions($chapter);
+ $this->permissions->addEntityPermission($chapter, [], $roleA);
+ $this->permissions->addEntityPermission($page, ['view'], $roleA);
+
+ $this->assertVisibleToUser($page, $user);
+ }
+
+ public function test_31_child_inherit_override_deny()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole();
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+
+ $this->permissions->disableEntityInheritedPermissions($chapter);
+ $this->permissions->addEntityPermission($chapter, ['view'], $roleA);
+ $this->permissions->addEntityPermission($page, [], $roleA);
+
+ $this->assertNotVisibleToUser($page, $user);
+ }
+
+ public function test_40_multi_role_inherit_conflict_override_deny()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole();
+ $roleB = $this->users->attachNewRole($user);
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+
+ $this->permissions->disableEntityInheritedPermissions($chapter);
+ $this->permissions->addEntityPermission($page, [], $roleA);
+ $this->permissions->addEntityPermission($chapter, ['view'], $roleB);
+
+ $this->assertVisibleToUser($page, $user);
+ }
+
+ public function test_41_multi_role_inherit_conflict_retain_allow()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole();
+ $roleB = $this->users->attachNewRole($user);
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+
+ $this->permissions->disableEntityInheritedPermissions($chapter);
+ $this->permissions->addEntityPermission($page, ['view'], $roleA);
+ $this->permissions->addEntityPermission($chapter, [], $roleB);
+
+ $this->assertVisibleToUser($page, $user);
+ }
+
+ public function test_50_role_override_allow()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole();
+ $page = $this->entities->page();
+ $this->permissions->addEntityPermission($page, ['view'], $roleA);
+
+ $this->assertVisibleToUser($page, $user);
+ }
+
+ public function test_51_role_override_deny()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole([], ['page-view-all']);
+ $page = $this->entities->page();
+ $this->permissions->addEntityPermission($page, [], $roleA);
+
+ $this->assertNotVisibleToUser($page, $user);
+ }
+
+ public function test_60_inherited_role_override_allow()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole([], []);
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+ $this->permissions->addEntityPermission($chapter, ['view'], $roleA);
+
+ $this->assertVisibleToUser($page, $user);
+ }
+
+ public function test_61_inherited_role_override_deny()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole([], ['page-view-all']);
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+ $this->permissions->addEntityPermission($chapter, [], $roleA);
+
+ $this->assertNotVisibleToUser($page, $user);
+ }
+
+ public function test_62_inherited_role_override_deny_on_own()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole([], ['page-view-own']);
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+ $this->permissions->addEntityPermission($chapter, [], $roleA);
+ $this->permissions->changeEntityOwner($page, $user);
+
+ $this->assertNotVisibleToUser($page, $user);
+ }
+
+ public function test_70_multi_role_inheriting_deny()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole([], ['page-view-all']);
+ $roleB = $this->users->attachNewRole($user);
+ $page = $this->entities->page();
+
+ $this->permissions->addEntityPermission($page, [], $roleB);
+
+ $this->assertNotVisibleToUser($page, $user);
+ }
+
+ public function test_80_multi_role_inherited_deny_via_parent()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole([], ['page-view-all']);
+ $roleB = $this->users->attachNewRole($user);
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+
+ $this->permissions->addEntityPermission($chapter, [], $roleB);
+
+ $this->assertNotVisibleToUser($page, $user);
+ }
+}
--- /dev/null
+<?php
+
+namespace Tests\Permissions\Scenarios;
+
+class EntityUserPermissionsTest extends PermissionScenarioTestCase
+{
+ public function test_01_explicit_allow()
+ {
+ $user = $this->users->newUser();
+ $page = $this->entities->page();
+ $this->permissions->disableEntityInheritedPermissions($page);
+ $this->permissions->addEntityPermission($page, ['view'], null, $user);
+
+ $this->assertVisibleToUser($page, $user);
+ }
+
+ public function test_02_explicit_deny()
+ {
+ $user = $this->users->newUser();
+ $page = $this->entities->page();
+ $this->permissions->disableEntityInheritedPermissions($page);
+ $this->permissions->addEntityPermission($page, [], null, $user);
+
+ $this->assertNotVisibleToUser($page, $user);
+ }
+
+ public function test_10_allow_inherit()
+ {
+ $user = $this->users->newUser();
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+ $this->permissions->disableEntityInheritedPermissions($chapter);
+ $this->permissions->addEntityPermission($chapter, ['view'], null, $user);
+
+ $this->assertVisibleToUser($page, $user);
+ }
+
+ public function test_11_deny_inherit()
+ {
+ $user = $this->users->newUser();
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+ $this->permissions->disableEntityInheritedPermissions($chapter);
+ $this->permissions->addEntityPermission($chapter, [], null, $user);
+
+ $this->assertNotVisibleToUser($page, $user);
+ }
+
+ public function test_12_allow_inherit_override()
+ {
+ $user = $this->users->newUser();
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+ $this->permissions->disableEntityInheritedPermissions($chapter);
+ $this->permissions->addEntityPermission($chapter, [], null, $user);
+ $this->permissions->addEntityPermission($page, ['view'], null, $user);
+
+ $this->assertVisibleToUser($page, $user);
+ }
+
+ public function test_13_deny_inherit_override()
+ {
+ $user = $this->users->newUser();
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+ $this->permissions->disableEntityInheritedPermissions($chapter);
+ $this->permissions->addEntityPermission($chapter, ['view'], null, $user);
+ $this->permissions->addEntityPermission($page, ['deny'], null, $user);
+
+ $this->assertNotVisibleToUser($page, $user);
+ }
+
+ public function test_40_entity_role_override_allow()
+ {
+ [$user, $role] = $this->users->newUserWithRole();
+ $page = $this->entities->page();
+ $this->permissions->disableEntityInheritedPermissions($page);
+ $this->permissions->addEntityPermission($page, ['view'], null, $user);
+ $this->permissions->addEntityPermission($page, [], $role);
+
+ $this->assertVisibleToUser($page, $user);
+ }
+
+ public function test_41_entity_role_override_deny()
+ {
+ [$user, $role] = $this->users->newUserWithRole();
+ $page = $this->entities->page();
+ $this->permissions->disableEntityInheritedPermissions($page);
+ $this->permissions->addEntityPermission($page, [], null, $user);
+ $this->permissions->addEntityPermission($page, ['view'], $role);
+
+ $this->assertNotVisibleToUser($page, $user);
+ }
+
+ public function test_42_entity_role_override_allow_via_inherit()
+ {
+ [$user, $role] = $this->users->newUserWithRole();
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+ $this->permissions->disableEntityInheritedPermissions($chapter);
+ $this->permissions->addEntityPermission($chapter, ['view'], null, $user);
+ $this->permissions->addEntityPermission($page, [], $role);
+
+ $this->assertVisibleToUser($page, $user);
+ }
+
+ public function test_43_entity_role_override_deny_via_inherit()
+ {
+ [$user, $role] = $this->users->newUserWithRole();
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+ $this->permissions->disableEntityInheritedPermissions($chapter);
+ $this->permissions->addEntityPermission($chapter, [], null, $user);
+ $this->permissions->addEntityPermission($page, ['view'], $role);
+
+ $this->assertNotVisibleToUser($page, $user);
+ }
+
+ public function test_50_role_override_allow()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole();
+ $page = $this->entities->page();
+ $this->permissions->addEntityPermission($page, ['view'], null, $user);
+
+ $this->assertVisibleToUser($page, $user);
+ }
+
+ public function test_51_role_override_deny()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole([], ['page-view-all']);
+ $page = $this->entities->page();
+ $this->permissions->addEntityPermission($page, [], null, $user);
+
+ $this->assertNotVisibleToUser($page, $user);
+ }
+
+ public function test_60_inherited_role_override_allow()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole([], []);
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+ $this->permissions->addEntityPermission($chapter, ['view'], null, $user);
+
+ $this->assertVisibleToUser($page, $user);
+ }
+
+ public function test_61_inherited_role_override_deny()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole([], ['page-view-all']);
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+ $this->permissions->addEntityPermission($chapter, [], null, $user);
+
+ $this->assertNotVisibleToUser($page, $user);
+ }
+
+ public function test_61_inherited_role_override_deny_on_own()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole([], ['page-view-own']);
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+ $this->permissions->addEntityPermission($chapter, [], null, $user);
+ $this->permissions->changeEntityOwner($page, $user);
+
+ $this->assertNotVisibleToUser($page, $user);
+ }
+
+ public function test_70_all_override_allow()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole([], []);
+ $page = $this->entities->page();
+ $this->permissions->addEntityPermission($page, [], $roleA, null);
+ $this->permissions->addEntityPermission($page, ['view'], null, $user);
+
+ $this->assertVisibleToUser($page, $user);
+ }
+
+ public function test_71_all_override_deny()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole([], ['page-view-all']);
+ $page = $this->entities->page();
+ $this->permissions->addEntityPermission($page, ['view'], $roleA, null);
+ $this->permissions->addEntityPermission($page, [], null, $user);
+
+ $this->assertNotVisibleToUser($page, $user);
+ }
+
+ public function test_80_inherited_all_override_allow()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole([], []);
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+ $this->permissions->addEntityPermission($chapter, [], $roleA, null);
+ $this->permissions->addEntityPermission($chapter, ['view'], null, $user);
+
+ $this->assertVisibleToUser($page, $user);
+ }
+
+ public function test_81_inherited_all_override_deny()
+ {
+ [$user, $roleA] = $this->users->newUserWithRole([], ['page-view-all']);
+ $page = $this->entities->pageWithinChapter();
+ $chapter = $page->chapter;
+ $this->permissions->addEntityPermission($chapter, ['view'], $roleA, null);
+ $this->permissions->addEntityPermission($chapter, [], null, $user);
+
+ $this->assertNotVisibleToUser($page, $user);
+ }
+}
--- /dev/null
+<?php
+
+namespace Tests\Permissions\Scenarios;
+
+use BookStack\Auth\User;
+use BookStack\Entities\Models\Entity;
+use Tests\TestCase;
+
+// Cases defined in dev/docs/permission-scenario-testing.md
+
+class PermissionScenarioTestCase extends TestCase
+{
+ protected function assertVisibleToUser(Entity $entity, User $user)
+ {
+ $this->actingAs($user);
+ $funcView = userCan($entity->getMorphClass() . '-view', $entity);
+ $queryView = $entity->newQuery()->scopes(['visible'])->find($entity->id) !== null;
+
+ $id = $entity->getMorphClass() . ':' . $entity->id;
+ $msg = "Item [{$id}] should be visible but was not found via ";
+ $msg .= implode(' and ', array_filter([!$funcView ? 'userCan' : '', !$queryView ? 'query' : '']));
+
+ static::assertTrue($funcView && $queryView, $msg);
+ }
+
+ protected function assertNotVisibleToUser(Entity $entity, User $user)
+ {
+ $this->actingAs($user);
+ $funcView = userCan($entity->getMorphClass() . '-view', $entity);
+ $queryView = $entity->newQuery()->scopes(['visible'])->find($entity->id) !== null;
+
+ $id = $entity->getMorphClass() . ':' . $entity->id;
+ $msg = "Item [{$id}] should not be visible but was found via ";
+ $msg .= implode(' and ', array_filter([$funcView ? 'userCan' : '', $queryView ? 'query' : '']));
+
+ static::assertTrue(!$funcView && !$queryView, $msg);
+ }
+}
--- /dev/null
+<?php
+
+namespace Tests\Permissions\Scenarios;
+
+class RoleContentPermissionsTest extends PermissionScenarioTestCase
+{
+ public function test_01_allow()
+ {
+ [$user] = $this->users->newUserWithRole([], ['page-view-all']);
+ $page = $this->entities->page();
+
+ $this->assertVisibleToUser($page, $user);
+ }
+
+ public function test_02_deny()
+ {
+ [$user] = $this->users->newUserWithRole([], []);
+ $page = $this->entities->page();
+
+ $this->assertNotVisibleToUser($page, $user);
+ }
+
+ public function test_10_allow_on_own_with_own()
+ {
+ [$user] = $this->users->newUserWithRole([], ['page-view-own']);
+ $page = $this->entities->page();
+ $this->permissions->changeEntityOwner($page, $user);
+
+ $this->assertVisibleToUser($page, $user);
+ }
+
+ public function test_11_deny_on_other_with_own()
+ {
+ [$user] = $this->users->newUserWithRole([], ['page-view-own']);
+ $page = $this->entities->page();
+ $this->permissions->changeEntityOwner($page, $this->users->editor());
+
+ $this->assertNotVisibleToUser($page, $user);
+ }
+
+ public function test_20_multiple_role_conflicting_all()
+ {
+ [$user] = $this->users->newUserWithRole([], ['page-view-all']);
+ $this->users->attachNewRole($user, []);
+ $page = $this->entities->page();
+
+ $this->assertVisibleToUser($page, $user);
+ }
+
+ public function test_21_multiple_role_conflicting_own()
+ {
+ [$user] = $this->users->newUserWithRole([], ['page-view-own']);
+ $this->users->attachNewRole($user, []);
+ $page = $this->entities->page();
+ $this->permissions->changeEntityOwner($page, $user);
+
+ $this->assertVisibleToUser($page, $user);
+ }
+}
namespace Tests;
-use BookStack\Auth\Permissions\JointPermissionBuilder;
use BookStack\Auth\Permissions\RolePermission;
use BookStack\Auth\Role;
use BookStack\Auth\User;
foreach (RolePermission::all() as $perm) {
$publicRole->attachPermission($perm);
}
- $this->app->make(JointPermissionBuilder::class)->rebuildForRole($publicRole);
user()->clearPermissionCache();
$chapter = $this->entities->chapter();
{
$this->setSettings(['app-public' => 'true']);
$book = $this->entities->book();
- $this->entities->setPermissions($book);
+ $this->permissions->setEntityPermissions($book);
$resp = $this->get($book->getUrl());
$resp->assertSee('Book not found');
$pageB = $this->entities->page();
$this->createReference($pageB, $page);
- $this->entities->setPermissions($pageB);
+ $this->permissions->setEntityPermissions($pageB);
$this->asEditor()->get($page->getUrl('/references'))->assertDontSee($pageB->name);
$this->asAdmin()->get($page->getUrl('/references'))->assertSee($pageB->name);
public function test_recycle_bin_routes_permissions()
{
$page = $this->entities->page();
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->actingAs($editor)->delete($page->getUrl());
$deletion = Deletion::query()->firstOrFail();
$this->assertPermissionError($resp);
}
- $this->giveUserPermissions($editor, ['restrictions-manage-all']);
+ $this->permissions->grantUserRolePermissions($editor, ['restrictions-manage-all']);
foreach ($routes as $route) {
[$method, $url] = explode(':', $route);
$this->assertPermissionError($resp);
}
- $this->giveUserPermissions($editor, ['settings-manage']);
+ $this->permissions->grantUserRolePermissions($editor, ['settings-manage']);
foreach ($routes as $route) {
DB::beginTransaction();
{
$page = $this->entities->page();
$book = Book::query()->whereHas('pages')->whereHas('chapters')->withCount(['pages', 'chapters'])->first();
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->actingAs($editor)->delete($page->getUrl());
$this->actingAs($editor)->delete($book->getUrl());
{
$page = $this->entities->page();
$book = Book::query()->where('id', '!=', $page->book_id)->whereHas('pages')->whereHas('chapters')->with(['pages', 'chapters'])->firstOrFail();
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->actingAs($editor)->delete($page->getUrl());
$this->actingAs($editor)->delete($book->getUrl());
public function test_settings_manage_permission_required()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$resp = $this->actingAs($editor)->post('/settings/maintenance/regenerate-references');
$this->assertPermissionError($resp);
- $this->giveUserPermissions($editor, ['settings-manage']);
+ $this->permissions->grantUserRolePermissions($editor, ['settings-manage']);
$resp = $this->actingAs($editor)->post('/settings/maintenance/regenerate-references');
$this->assertNotPermissionError($resp);
public function test_send_test_email_endpoint_sends_email_and_redirects_user_and_shows_notification()
{
Notification::fake();
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
$sendReq = $this->actingAs($admin)->post('/settings/maintenance/send-test-email');
$sendReq->assertRedirect('/settings/maintenance#image-cleanup');
$exception = new \Exception('A random error occurred when testing an email');
$mockDispatcher->shouldReceive('sendNow')->andThrow($exception);
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
$sendReq = $this->actingAs($admin)->post('/settings/maintenance/send-test-email');
$sendReq->assertRedirect('/settings/maintenance#image-cleanup');
$this->assertSessionHas('error');
public function test_send_test_email_requires_settings_manage_permission()
{
Notification::fake();
- $user = $this->getViewer();
+ $user = $this->users->viewer();
$sendReq = $this->actingAs($user)->post('/settings/maintenance/send-test-email');
Notification::assertNothingSent();
- $this->giveUserPermissions($user, ['settings-manage']);
+ $this->permissions->grantUserRolePermissions($user, ['settings-manage']);
$sendReq = $this->actingAs($user)->post('/settings/maintenance/send-test-email');
Notification::assertSentTo($user, TestEmail::class);
}
namespace Tests;
-use BookStack\Auth\Permissions\JointPermissionBuilder;
-use BookStack\Auth\Permissions\PermissionsRepo;
-use BookStack\Auth\Permissions\RolePermission;
-use BookStack\Auth\Role;
-use BookStack\Auth\User;
use BookStack\Entities\Models\Entity;
use BookStack\Settings\SettingService;
use BookStack\Uploads\HttpFetcher;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Testing\Assert as PHPUnit;
+use Mockery;
use Monolog\Handler\TestHandler;
use Monolog\Logger;
use Psr\Http\Client\ClientInterface;
use Ssddanbrown\AssertHtml\TestsHtml;
use Tests\Helpers\EntityProvider;
+use Tests\Helpers\PermissionsProvider;
use Tests\Helpers\TestServiceProvider;
+use Tests\Helpers\UserRoleProvider;
abstract class TestCase extends BaseTestCase
{
use DatabaseTransactions;
use TestsHtml;
- protected ?User $admin = null;
- protected ?User $editor = null;
protected EntityProvider $entities;
+ protected UserRoleProvider $users;
+ protected PermissionsProvider $permissions;
protected function setUp(): void
{
$this->entities = new EntityProvider();
+ $this->users = new UserRoleProvider();
+ $this->permissions = new PermissionsProvider($this->users);
+
parent::setUp();
}
*/
public function asAdmin()
{
- return $this->actingAs($this->getAdmin());
- }
-
- /**
- * Get the current admin user.
- */
- public function getAdmin(): User
- {
- if (is_null($this->admin)) {
- $adminRole = Role::getSystemRole('admin');
- $this->admin = $adminRole->users->first();
- }
-
- return $this->admin;
+ return $this->actingAs($this->users->admin());
}
/**
*/
public function asEditor()
{
- return $this->actingAs($this->getEditor());
- }
-
- /**
- * Get a editor user.
- */
- protected function getEditor(): User
- {
- if ($this->editor === null) {
- $editorRole = Role::getRole('editor');
- $this->editor = $editorRole->users->first();
- }
-
- return $this->editor;
+ return $this->actingAs($this->users->editor());
}
/**
*/
public function asViewer()
{
- return $this->actingAs($this->getViewer());
- }
-
- /**
- * Get an instance of a user with 'viewer' permissions.
- */
- protected function getViewer(array $attributes = []): User
- {
- $user = Role::getRole('viewer')->users()->first();
- if (!empty($attributes)) {
- $user->forceFill($attributes)->save();
- }
-
- return $user;
- }
-
- /**
- * Get a user that's not a system user such as the guest user.
- */
- public function getNormalUser(): User
- {
- return User::query()->where('system_name', '=', null)->get()->last();
+ return $this->actingAs($this->users->viewer());
}
/**
}
}
- /**
- * Give the given user some permissions.
- */
- protected function giveUserPermissions(User $user, array $permissions = []): void
- {
- $newRole = $this->createNewRole($permissions);
- $user->attachRole($newRole);
- $user->load('roles');
- $user->clearPermissionCache();
- }
-
- /**
- * Completely remove the given permission name from the given user.
- */
- protected function removePermissionFromUser(User $user, string $permissionName)
- {
- $permissionBuilder = app()->make(JointPermissionBuilder::class);
-
- /** @var RolePermission $permission */
- $permission = RolePermission::query()->where('name', '=', $permissionName)->firstOrFail();
-
- $roles = $user->roles()->whereHas('permissions', function ($query) use ($permission) {
- $query->where('id', '=', $permission->id);
- })->get();
-
- /** @var Role $role */
- foreach ($roles as $role) {
- $role->detachPermission($permission);
- $permissionBuilder->rebuildForRole($role);
- }
-
- $user->clearPermissionCache();
- }
-
- /**
- * Create a new basic role for testing purposes.
- */
- protected function createNewRole(array $permissions = []): Role
- {
- $permissionRepo = app(PermissionsRepo::class);
- $roleData = Role::factory()->make()->toArray();
- $roleData['permissions'] = array_flip($permissions);
-
- return $permissionRepo->saveNewRole($roleData);
- }
-
/**
* Mock the HttpFetcher service and return the given data on fetch.
*/
';
file_put_contents($translationPath . '/entities.php', $customTranslations);
- $homeRequest = $this->actingAs($this->getViewer())->get('/');
+ $homeRequest = $this->actingAs($this->users->viewer())->get('/');
$this->withHtml($homeRequest)->assertElementContains('header nav', 'Sandwiches');
});
}
// Page has SoftDeletes trait by default, so we apply our custom scope and ensure
// it stacks on the global scope to filter out deleted items.
$query = Page::query()->scopes('visible')->toSql();
- $this->assertStringContainsString('joint_permissions', $query);
+ $this->assertStringContainsString('entity_permissions_collapsed', $query);
$this->assertStringContainsString('`deleted_at` is null', $query);
}
}
{
$page = $this->entities->page();
$this->asAdmin();
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
$fileName = 'upload_test_file.txt';
$expectedResp = [
public function test_attaching_link_to_page()
{
$page = $this->entities->page();
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
$this->asAdmin();
$linkReq = $this->call('POST', 'attachments/link', [
public function test_attachment_access_without_permission_shows_404()
{
- $admin = $this->getAdmin();
- $viewer = $this->getViewer();
+ $admin = $this->users->admin();
+ $viewer = $this->users->viewer();
$page = $this->entities->page(); /** @var Page $page */
$this->actingAs($admin);
$fileName = 'permission_test.txt';
$this->uploadFile($fileName, $page->id);
$attachment = Attachment::orderBy('id', 'desc')->take(1)->first();
- $this->entities->setPermissions($page, [], []);
+ $this->permissions->setEntityPermissions($page, [], []);
$this->actingAs($viewer);
$attachmentGet = $this->get($attachment->getUrl());
public function test_drawing_base64_upload()
{
$page = Page::first();
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->actingAs($editor);
$upload = $this->postJson('images/drawio', [
{
config()->set('services.drawio', 'http://cats.com?dog=tree');
$page = Page::first();
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$resp = $this->actingAs($editor)->get($page->getUrl('/edit'));
$resp->assertSee('drawio-url="http://cats.com?dog=tree"', false);
{
config()->set('services.drawio', true);
$page = Page::first();
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$resp = $this->actingAs($editor)->get($page->getUrl('/edit'));
$resp->assertSee('drawio-url="https://embed.diagrams.net/?embed=1&proto=json&spin=1&configure=1"', false);
public function test_image_upload()
{
$page = $this->entities->page();
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
$this->actingAs($admin);
$imgDetails = $this->uploadGalleryImage($page);
public function test_image_display_thumbnail_generation_does_not_increase_image_size()
{
$page = $this->entities->page();
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
$this->actingAs($admin);
$originalFile = $this->getTestImageFilePath('compressed.png');
public function test_image_display_thumbnail_generation_for_apng_images_uses_original_file()
{
$page = $this->entities->page();
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
$this->actingAs($admin);
$imgDetails = $this->uploadGalleryImage($page, 'animated.png');
public function test_image_edit()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->actingAs($editor);
$imgDetails = $this->uploadGalleryImage();
public function test_image_usage()
{
$page = $this->entities->page();
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->actingAs($editor);
$imgDetails = $this->uploadGalleryImage($page);
public function test_php_files_cannot_be_uploaded()
{
$page = $this->entities->page();
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
$this->actingAs($admin);
$fileName = 'bad.php';
public function test_php_like_files_cannot_be_uploaded()
{
$page = $this->entities->page();
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
$this->actingAs($admin);
$fileName = 'bad.phtml';
public function test_files_with_double_extensions_will_get_sanitized()
{
$page = $this->entities->page();
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
$this->actingAs($admin);
$fileName = 'bad.phtml.png';
$this->get($expectedUrl)->assertOk();
- $this->entities->setPermissions($page, [], []);
+ $this->permissions->setEntityPermissions($page, [], []);
$resp = $this->get($expectedUrl);
$resp->assertNotFound();
$this->get($expectedUrl)->assertOk();
- $this->entities->setPermissions($page, [], []);
+ $this->permissions->setEntityPermissions($page, [], []);
$resp = $this->get($expectedUrl);
$resp->assertNotFound();
$export = $this->get($pageB->getUrl('/export/html'));
$this->assertStringContainsString($encodedImageContent, $export->getContent());
- $this->entities->setPermissions($pageA, [], []);
+ $this->permissions->setEntityPermissions($pageA, [], []);
$export = $this->get($pageB->getUrl('/export/html'));
$this->assertStringNotContainsString($encodedImageContent, $export->getContent());
$imageName = 'first-image.png';
$relPath = $this->getTestImagePath('gallery', $imageName);
$this->deleteImage($relPath);
- $viewer = $this->getViewer();
+ $viewer = $this->users->viewer();
$this->uploadImage($imageName, $page->id);
$image = Image::first();
$resp = $this->actingAs($viewer)->get("/images/edit/{$image->id}");
$this->withHtml($resp)->assertElementNotExists('button#image-manager-delete[title="Delete"]');
- $this->giveUserPermissions($viewer, ['image-delete-all']);
+ $this->permissions->grantUserRolePermissions($viewer, ['image-delete-all']);
$resp = $this->actingAs($viewer)->get("/images/edit/{$image->id}");
$this->withHtml($resp)->assertElementExists('button#image-manager-delete[title="Delete"]');
public function test_user_image_upload()
{
- $editor = $this->getEditor();
- $admin = $this->getAdmin();
+ $editor = $this->users->editor();
+ $admin = $this->users->admin();
$this->actingAs($admin);
$file = $this->getTestProfileImage();
public function test_user_images_deleted_on_user_deletion()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->actingAs($editor);
$file = $this->getTestProfileImage();
public function test_deleted_unused_images()
{
$page = $this->entities->page();
- $admin = $this->getAdmin();
+ $admin = $this->users->admin();
$this->actingAs($admin);
$imageName = 'unused-image.png';
public function test_tokens_section_not_visible_without_access_api_permission()
{
- $user = $this->getViewer();
+ $user = $this->users->viewer();
$resp = $this->actingAs($user)->get($user->getEditUrl());
$resp->assertDontSeeText('API Tokens');
- $this->giveUserPermissions($user, ['access-api']);
+ $this->permissions->grantUserRolePermissions($user, ['access-api']);
$resp = $this->actingAs($user)->get($user->getEditUrl());
$resp->assertSeeText('API Tokens');
public function test_those_with_manage_users_can_view_other_user_tokens_but_not_create()
{
- $viewer = $this->getViewer();
- $editor = $this->getEditor();
- $this->giveUserPermissions($viewer, ['users-manage']);
+ $viewer = $this->users->viewer();
+ $editor = $this->users->editor();
+ $this->permissions->grantUserRolePermissions($viewer, ['users-manage']);
$resp = $this->actingAs($viewer)->get($editor->getEditUrl());
$resp->assertSeeText('API Tokens');
public function test_create_api_token()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$resp = $this->asAdmin()->get($editor->getEditUrl('/create-api-token'));
$resp->assertStatus(200);
public function test_create_with_no_expiry_sets_expiry_hundred_years_away()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->asAdmin()->post($editor->getEditUrl('/create-api-token'), ['name' => 'No expiry token', 'expires_at' => '']);
$token = ApiToken::query()->latest()->first();
public function test_created_token_displays_on_profile_page()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->asAdmin()->post($editor->getEditUrl('/create-api-token'), $this->testTokenData);
$token = ApiToken::query()->latest()->first();
public function test_secret_shown_once_after_creation()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$resp = $this->asAdmin()->followingRedirects()->post($editor->getEditUrl('/create-api-token'), $this->testTokenData);
$resp->assertSeeText('Token Secret');
public function test_token_update()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->asAdmin()->post($editor->getEditUrl('/create-api-token'), $this->testTokenData);
$token = ApiToken::query()->latest()->first();
$updateData = [
public function test_token_update_with_blank_expiry_sets_to_hundred_years_away()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->asAdmin()->post($editor->getEditUrl('/create-api-token'), $this->testTokenData);
$token = ApiToken::query()->latest()->first();
public function test_token_delete()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->asAdmin()->post($editor->getEditUrl('/create-api-token'), $this->testTokenData);
$token = ApiToken::query()->latest()->first();
public function test_user_manage_can_delete_token_without_api_permission_themselves()
{
- $viewer = $this->getViewer();
- $editor = $this->getEditor();
- $this->giveUserPermissions($editor, ['users-manage']);
+ $viewer = $this->users->viewer();
+ $editor = $this->users->editor();
+ $this->permissions->grantUserRolePermissions($editor, ['users-manage']);
$this->asAdmin()->post($viewer->getEditUrl('/create-api-token'), $this->testTokenData);
$token = ApiToken::query()->latest()->first();
public function test_user_updating()
{
- $user = $this->getNormalUser();
+ $user = $this->users->viewer();
$password = $user->password;
$resp = $this->asAdmin()->get('/settings/users/' . $user->id);
public function test_user_password_update()
{
- $user = $this->getNormalUser();
+ $user = $this->users->viewer();
$userProfilePage = '/settings/users/' . $user->id;
$this->asAdmin()->get($userProfilePage);
public function test_delete()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$resp = $this->asAdmin()->delete("settings/users/{$editor->id}");
$resp->assertRedirect('/settings/users');
$resp = $this->followRedirects($resp);
public function test_delete_offers_migrate_option()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$resp = $this->asAdmin()->get("settings/users/{$editor->id}/delete");
$resp->assertSee('Migrate Ownership');
$resp->assertSee('new_owner_id');
public function test_migrate_option_hidden_if_user_cannot_manage_users()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$resp = $this->asEditor()->get("settings/users/{$editor->id}/delete");
$resp->assertDontSee('Migrate Ownership');
$resp->assertDontSee('new_owner_id');
- $this->giveUserPermissions($editor, ['users-manage']);
+ $this->permissions->grantUserRolePermissions($editor, ['users-manage']);
$resp = $this->asEditor()->get("settings/users/{$editor->id}/delete");
$resp->assertSee('Migrate Ownership');
public function test_delete_removes_user_preferences()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
setting()->putUser($editor, 'dark-mode-enabled', 'true');
$this->assertDatabaseHas('settings', [
public function test_user_create_update_fails_if_locale_is_invalid()
{
- $user = $this->getEditor();
+ $user = $this->users->editor();
// Too long
$resp = $this->asAdmin()->put($user->getEditUrl(), ['language' => 'this_is_too_long']);
$resp->assertSessionHasErrors(['language' => 'The language may not be greater than 15 characters.']);
$resp->assertSessionHasErrors(['language' => 'The language may only contain letters, numbers, dashes and underscores.']);
}
-
- public function test_role_removal_on_user_edit_removes_all_role_assignments()
- {
- $user = $this->getEditor();
-
- $this->assertEquals(1, $user->roles()->count());
-
- // A roles[0] hidden fields is used to indicate the existence of role selection in the submission
- // of the user edit form. We check that field is used and emulate its submission.
- $resp = $this->asAdmin()->get("/settings/users/{$user->id}");
- $this->withHtml($resp)->assertElementExists('input[type="hidden"][name="roles[0]"][value="0"]');
-
- $resp = $this->asAdmin()->put("/settings/users/{$user->id}", [
- 'name' => $user->name,
- 'email' => $user->email,
- 'roles' => ['0' => '0'],
- ]);
- $resp->assertRedirect("/settings/users");
-
- $this->assertEquals(0, $user->roles()->count());
- }
-
- public function test_role_form_hidden_indicator_field_does_not_exist_where_roles_cannot_be_managed()
- {
- $user = $this->getEditor();
- $resp = $this->actingAs($user)->get("/settings/users/{$user->id}");
- $html = $this->withHtml($resp);
- $html->assertElementExists('input[name="email"]');
- $html->assertElementNotExists('input[type="hidden"][name="roles[0]"]');
- }
}
public function test_body_has_shortcuts_component_when_active()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->actingAs($editor);
$this->withHtml($this->get('/'))->assertElementNotExists('body[component="shortcuts"]');
public function test_update_sort_preference()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->actingAs($editor);
$updateRequest = $this->patch('/preferences/change-sort/books', [
public function test_update_sort_bad_entity_type_handled()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->actingAs($editor);
$updateRequest = $this->patch('/preferences/change-sort/dogs', [
public function test_update_expansion_preference()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$this->actingAs($editor);
$updateRequest = $this->patch('/preferences/change-expansion/home-details', ['expand' => 'true']);
public function test_toggle_dark_mode()
{
- $home = $this->actingAs($this->getEditor())->get('/');
+ $home = $this->actingAs($this->users->editor())->get('/');
$home->assertSee('Dark Mode');
$this->withHtml($home)->assertElementNotExists('.dark-mode');
$prefChange->assertRedirect();
$this->assertEquals(true, setting()->getForCurrentUser('dark-mode-enabled'));
- $home = $this->actingAs($this->getEditor())->get('/');
+ $home = $this->actingAs($this->users->editor())->get('/');
$this->withHtml($home)->assertElementExists('.dark-mode');
$home->assertDontSee('Dark Mode');
$home->assertSee('Light Mode');
public function test_books_view_type_preferences_when_list()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
setting()->putUser($editor, 'books_view_type', 'list');
$resp = $this->actingAs($editor)->get('/books');
public function test_books_view_type_preferences_when_grid()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
setting()->putUser($editor, 'books_view_type', 'grid');
$resp = $this->actingAs($editor)->get('/books');
public function test_shelf_view_type_change()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$shelf = $this->entities->shelf();
setting()->putUser($editor, 'bookshelf_view_type', 'list');
public function test_update_code_language_favourite()
{
- $editor = $this->getEditor();
+ $editor = $this->users->editor();
$page = $this->entities->page();
$this->actingAs($editor);
public function test_profile_has_search_links_in_created_entity_lists()
{
- $user = $this->getEditor();
- $resp = $this->actingAs($this->getAdmin())->get('/user/' . $user->slug);
+ $user = $this->users->editor();
+ $resp = $this->actingAs($this->users->admin())->get('/user/' . $user->slug);
$expectedLinks = [
'/search?term=%7Bcreated_by%3A' . $user->slug . '%7D+%7Btype%3Apage%7D',
{
public function test_select_search_matches_by_name()
{
- $viewer = $this->getViewer();
- $admin = $this->getAdmin();
+ $viewer = $this->users->viewer();
+ $admin = $this->users->admin();
$resp = $this->actingAs($admin)->get('/search/users/select?search=' . urlencode($viewer->name));
$resp->assertOk();
public function test_select_search_does_not_match_by_email()
{
- $viewer = $this->getViewer();
- $editor = $this->getEditor();
+ $viewer = $this->users->viewer();
+ $editor = $this->users->editor();
$resp = $this->actingAs($editor)->get('/search/users/select?search=' . urlencode($viewer->email));
$resp->assertDontSee($viewer->name);
public function test_select_requires_right_permission()
{
$permissions = ['users-manage', 'restrictions-manage-own', 'restrictions-manage-all'];
- $user = $this->getViewer();
+ $user = $this->users->viewer();
foreach ($permissions as $permission) {
$resp = $this->actingAs($user)->get('/search/users/select?search=a');
$this->assertPermissionError($resp);
- $this->giveUserPermissions($user, [$permission]);
+ $this->permissions->grantUserRolePermissions($user, [$permission]);
$resp = $this->actingAs($user)->get('/search/users/select?search=a');
$resp->assertOk();
$user->roles()->delete();
{
$this->setSettings(['app-public' => true]);
$defaultUser = User::getDefault();
- $this->giveUserPermissions($defaultUser, ['users-manage']);
+ $this->permissions->grantUserRolePermissions($defaultUser, ['users-manage']);
$resp = $this->get('/search/users/select?search=a');
$this->assertPermissionError($resp);