From bba8376af9ff83bd6f79151841902b8d2a4fb97e Mon Sep 17 00:00:00 2001 From: ulferts Date: Thu, 17 Nov 2022 12:14:46 +0100 Subject: [PATCH 1/6] add spec on anonymous user capability --- .../capabilities/scopes/default_spec.rb | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/spec/models/capabilities/scopes/default_spec.rb b/spec/models/capabilities/scopes/default_spec.rb index 7f7489bef3..074cfed7b1 100644 --- a/spec/models/capabilities/scopes/default_spec.rb +++ b/spec/models/capabilities/scopes/default_spec.rb @@ -35,6 +35,7 @@ describe Capabilities::Scopes::Default, type: :model do let(:permissions) { %i[] } let(:global_permissions) { %i[] } let(:non_member_permissions) { %i[] } + let(:anonymous_permissions) { %i[] } let(:project_public) { false } let(:project_active) { true } let!(:project) { create(:project, public: project_public, active: project_active) } @@ -63,6 +64,10 @@ describe Capabilities::Scopes::Default, type: :model do create(:non_member, permissions: non_member_permissions) end + let(:anonymous_role) do + create(:anonymous_role, + permissions: anonymous_permissions) + end let(:own_role) { create(:role, permissions: []) } let(:own_member) do create(:member, @@ -180,6 +185,28 @@ describe Capabilities::Scopes::Default, type: :model do end end + context 'with the anonymous role with an action permission' do + let(:anonymous_permissions) { %i[view_members] } + let!(:user) { create(:anonymous) } + let(:members) { [anonymous_role] } + + context 'with the project being private' do + it_behaves_like 'is empty' + end + + context 'with the project being public' do + let(:project_public) { true } + + it_behaves_like 'consists of contract actions' do + let(:expected) do + [ + ['memberships/read', user.id, project.id] + ] + end + end + end + end + context 'with a member without a permission and with the non member having a permission' do let(:non_member_permissions) { %i[view_members] } let(:members) { [member, non_member_role] } From ba8bd5ff9c6bb18fa16831f2cf0937dfa80790f9 Mon Sep 17 00:00:00 2001 From: Dmitrii Date: Thu, 17 Nov 2022 21:20:55 +0100 Subject: [PATCH 2/6] addjust sql to get actions from anonymous user --- app/models/capabilities/scopes/default.rb | 29 +++++++++++++++++++ app/models/principals/scopes/not_builtin.rb | 5 ++++ app/models/principals/scopes/status.rb | 4 +++ .../files-tab/op-files-tab.component.ts | 8 +---- .../storages/storage/storage.component.ts | 2 -- 5 files changed, 39 insertions(+), 9 deletions(-) diff --git a/app/models/capabilities/scopes/default.rb b/app/models/capabilities/scopes/default.rb index ad3a7c4f97..76689cb2c2 100644 --- a/app/models/capabilities/scopes/default.rb +++ b/app/models/capabilities/scopes/default.rb @@ -40,10 +40,13 @@ module Capabilities::Scopes UNION #{default_sql_by_non_member} UNION + #{default_sql_by_non_member_with_anonymous} + UNION #{default_sql_by_admin} ) capabilities SQL + # binding.pry select('capabilities.*') .from(capabilities_sql) end @@ -117,6 +120,32 @@ module Capabilities::Scopes WHERE enabled_modules.project_id IS NOT NULL OR "actions".module IS NULL SQL end + + def default_sql_by_non_member_with_anonymous + <<~SQL.squish + SELECT DISTINCT + actions.id "action", + users.id principal_id, + projects.id context_id + FROM (#{Action.default.to_sql}) actions + JOIN "role_permissions" ON "role_permissions"."permission" = "actions"."permission" + JOIN "roles" ON "roles".id = "role_permissions".role_id AND roles.builtin = #{Role::BUILTIN_ANONYMOUS} + JOIN (#{Principal.visible.not_builtin_without_anonymous.not_locked_without_anonymous.to_sql}) users + ON 1 = 1 + JOIN "projects" + ON "projects".active = true + AND ("projects".public = true OR EXISTS (SELECT 1 + FROM members + WHERE members.project_id = projects.id + AND members.user_id = users.id + LIMIT 1)) + LEFT OUTER JOIN enabled_modules + ON enabled_modules.project_id = projects.id + AND actions.module = enabled_modules.name + + WHERE enabled_modules.project_id IS NOT NULL OR "actions".module IS NULL + SQL + end end end end diff --git a/app/models/principals/scopes/not_builtin.rb b/app/models/principals/scopes/not_builtin.rb index aeafd8f22c..bed6a86e38 100644 --- a/app/models/principals/scopes/not_builtin.rb +++ b/app/models/principals/scopes/not_builtin.rb @@ -42,6 +42,11 @@ module Principals::Scopes AnonymousUser.name, DeletedUser.name]) end + + def not_builtin_without_anonymous + where.not(type: [SystemUser.name, + DeletedUser.name]) + end end end end diff --git a/app/models/principals/scopes/status.rb b/app/models/principals/scopes/status.rb index f04f9e083d..bcf2545174 100644 --- a/app/models/principals/scopes/status.rb +++ b/app/models/principals/scopes/status.rb @@ -45,6 +45,10 @@ module Principals::Scopes not_builtin.where.not(status: val) end end + + def self.not_locked_without_anonymous + not_builtin_without_anonymous.where.not(status: :locked) + end end end end diff --git a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/files-tab/op-files-tab.component.ts b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/files-tab/op-files-tab.component.ts index 61c05d5431..0cefff0e1e 100644 --- a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/files-tab/op-files-tab.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/files-tab/op-files-tab.component.ts @@ -33,7 +33,6 @@ import { } from '@angular/core'; import { combineLatest, - merge, Observable, } from 'rxjs'; import { @@ -86,12 +85,7 @@ export class WorkPackageFilesTabComponent implements OnInit { return; } - // ToDo: Needs to be fixed after capabilities are available for anonymous user. - // https://community.openproject.org/projects/openproject/work_packages/44850/activity - const canViewFileLinks = merge( - this.currentUserService.isLoggedIn$.pipe(map((isLoggedIn) => !isLoggedIn)), - this.currentUserService.hasCapabilities$('file_links/view', project.id), - ); + const canViewFileLinks = this.currentUserService.hasCapabilities$('file_links/view', project.id); this.storages$ = this .storagesResourceService diff --git a/frontend/src/app/shared/components/storages/storage/storage.component.ts b/frontend/src/app/shared/components/storages/storage/storage.component.ts index c195c21b67..e4de7699c1 100644 --- a/frontend/src/app/shared/components/storages/storage/storage.component.ts +++ b/frontend/src/app/shared/components/storages/storage/storage.component.ts @@ -201,8 +201,6 @@ export class StorageComponent extends UntilDestroyedMixin implements OnInit { } private instantiateStorageInformation(fileLinks:IFileLink[]):StorageInformationBox[] { - // ToDo: Replace with anonymous user capabilities check. - // https://community.openproject.org/projects/openproject/work_packages/44850/activity if (!this.isLoggedIn) { return []; } From c7d37bd391b9ce89bed1aa5555d0ab1be9188b51 Mon Sep 17 00:00:00 2001 From: ulferts Date: Tue, 22 Nov 2022 17:26:19 +0100 Subject: [PATCH 3/6] add spec to ensure users don't receive anonymous role's permissions --- spec/models/capabilities/scopes/default_spec.rb | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/spec/models/capabilities/scopes/default_spec.rb b/spec/models/capabilities/scopes/default_spec.rb index 074cfed7b1..01263ced50 100644 --- a/spec/models/capabilities/scopes/default_spec.rb +++ b/spec/models/capabilities/scopes/default_spec.rb @@ -185,7 +185,15 @@ describe Capabilities::Scopes::Default, type: :model do end end - context 'with the anonymous role with an action permission' do + context 'with the anonymous role having the action permission in a public project' do + let(:anonymous_permissions) { %i[view_members] } + let(:project_public) { true } + let(:members) { [anonymous_role] } + + it_behaves_like 'is empty' + end + + context 'with the anonymous user with an action permission' do let(:anonymous_permissions) { %i[view_members] } let!(:user) { create(:anonymous) } let(:members) { [anonymous_role] } @@ -194,6 +202,12 @@ describe Capabilities::Scopes::Default, type: :model do it_behaves_like 'is empty' end + context 'with the anonymous role not having the permission' do + let(:anonymous_permissions) { %i[] } + + it_behaves_like 'is empty' + end + context 'with the project being public' do let(:project_public) { true } From f240da39b50eb124db302c88fa89fa147e46a647 Mon Sep 17 00:00:00 2001 From: ulferts Date: Thu, 17 Nov 2022 12:14:46 +0100 Subject: [PATCH 4/6] add spec on anonymous user capability --- .../capabilities/scopes/default_spec.rb | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/spec/models/capabilities/scopes/default_spec.rb b/spec/models/capabilities/scopes/default_spec.rb index 7f7489bef3..074cfed7b1 100644 --- a/spec/models/capabilities/scopes/default_spec.rb +++ b/spec/models/capabilities/scopes/default_spec.rb @@ -35,6 +35,7 @@ describe Capabilities::Scopes::Default, type: :model do let(:permissions) { %i[] } let(:global_permissions) { %i[] } let(:non_member_permissions) { %i[] } + let(:anonymous_permissions) { %i[] } let(:project_public) { false } let(:project_active) { true } let!(:project) { create(:project, public: project_public, active: project_active) } @@ -63,6 +64,10 @@ describe Capabilities::Scopes::Default, type: :model do create(:non_member, permissions: non_member_permissions) end + let(:anonymous_role) do + create(:anonymous_role, + permissions: anonymous_permissions) + end let(:own_role) { create(:role, permissions: []) } let(:own_member) do create(:member, @@ -180,6 +185,28 @@ describe Capabilities::Scopes::Default, type: :model do end end + context 'with the anonymous role with an action permission' do + let(:anonymous_permissions) { %i[view_members] } + let!(:user) { create(:anonymous) } + let(:members) { [anonymous_role] } + + context 'with the project being private' do + it_behaves_like 'is empty' + end + + context 'with the project being public' do + let(:project_public) { true } + + it_behaves_like 'consists of contract actions' do + let(:expected) do + [ + ['memberships/read', user.id, project.id] + ] + end + end + end + end + context 'with a member without a permission and with the non member having a permission' do let(:non_member_permissions) { %i[view_members] } let(:members) { [member, non_member_role] } From 904091a406af4b32a355130d7b585c63f8a48039 Mon Sep 17 00:00:00 2001 From: Dmitrii Date: Thu, 17 Nov 2022 21:20:55 +0100 Subject: [PATCH 5/6] addjust sql to get actions from anonymous user --- app/models/capabilities/scopes/default.rb | 29 +++++++++++++++++++ app/models/principals/scopes/not_builtin.rb | 5 ++++ app/models/principals/scopes/status.rb | 4 +++ .../files-tab/op-files-tab.component.ts | 8 +---- .../storages/storage/storage.component.ts | 2 -- 5 files changed, 39 insertions(+), 9 deletions(-) diff --git a/app/models/capabilities/scopes/default.rb b/app/models/capabilities/scopes/default.rb index ad3a7c4f97..76689cb2c2 100644 --- a/app/models/capabilities/scopes/default.rb +++ b/app/models/capabilities/scopes/default.rb @@ -40,10 +40,13 @@ module Capabilities::Scopes UNION #{default_sql_by_non_member} UNION + #{default_sql_by_non_member_with_anonymous} + UNION #{default_sql_by_admin} ) capabilities SQL + # binding.pry select('capabilities.*') .from(capabilities_sql) end @@ -117,6 +120,32 @@ module Capabilities::Scopes WHERE enabled_modules.project_id IS NOT NULL OR "actions".module IS NULL SQL end + + def default_sql_by_non_member_with_anonymous + <<~SQL.squish + SELECT DISTINCT + actions.id "action", + users.id principal_id, + projects.id context_id + FROM (#{Action.default.to_sql}) actions + JOIN "role_permissions" ON "role_permissions"."permission" = "actions"."permission" + JOIN "roles" ON "roles".id = "role_permissions".role_id AND roles.builtin = #{Role::BUILTIN_ANONYMOUS} + JOIN (#{Principal.visible.not_builtin_without_anonymous.not_locked_without_anonymous.to_sql}) users + ON 1 = 1 + JOIN "projects" + ON "projects".active = true + AND ("projects".public = true OR EXISTS (SELECT 1 + FROM members + WHERE members.project_id = projects.id + AND members.user_id = users.id + LIMIT 1)) + LEFT OUTER JOIN enabled_modules + ON enabled_modules.project_id = projects.id + AND actions.module = enabled_modules.name + + WHERE enabled_modules.project_id IS NOT NULL OR "actions".module IS NULL + SQL + end end end end diff --git a/app/models/principals/scopes/not_builtin.rb b/app/models/principals/scopes/not_builtin.rb index aeafd8f22c..bed6a86e38 100644 --- a/app/models/principals/scopes/not_builtin.rb +++ b/app/models/principals/scopes/not_builtin.rb @@ -42,6 +42,11 @@ module Principals::Scopes AnonymousUser.name, DeletedUser.name]) end + + def not_builtin_without_anonymous + where.not(type: [SystemUser.name, + DeletedUser.name]) + end end end end diff --git a/app/models/principals/scopes/status.rb b/app/models/principals/scopes/status.rb index f04f9e083d..bcf2545174 100644 --- a/app/models/principals/scopes/status.rb +++ b/app/models/principals/scopes/status.rb @@ -45,6 +45,10 @@ module Principals::Scopes not_builtin.where.not(status: val) end end + + def self.not_locked_without_anonymous + not_builtin_without_anonymous.where.not(status: :locked) + end end end end diff --git a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/files-tab/op-files-tab.component.ts b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/files-tab/op-files-tab.component.ts index e917a9005b..ca41bc49ae 100644 --- a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/files-tab/op-files-tab.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/files-tab/op-files-tab.component.ts @@ -33,7 +33,6 @@ import { } from '@angular/core'; import { combineLatest, - merge, Observable, } from 'rxjs'; import { @@ -86,12 +85,7 @@ export class WorkPackageFilesTabComponent implements OnInit { return; } - // ToDo: Needs to be fixed after capabilities are available for anonymous user. - // https://community.openproject.org/projects/openproject/work_packages/44850/activity - const canViewFileLinks = merge( - this.currentUserService.isLoggedIn$.pipe(map((isLoggedIn) => !isLoggedIn)), - this.currentUserService.hasCapabilities$('file_links/view', project.id), - ); + const canViewFileLinks = this.currentUserService.hasCapabilities$('file_links/view', project.id); this.storages$ = this .storagesResourceService diff --git a/frontend/src/app/shared/components/storages/storage/storage.component.ts b/frontend/src/app/shared/components/storages/storage/storage.component.ts index c195c21b67..e4de7699c1 100644 --- a/frontend/src/app/shared/components/storages/storage/storage.component.ts +++ b/frontend/src/app/shared/components/storages/storage/storage.component.ts @@ -201,8 +201,6 @@ export class StorageComponent extends UntilDestroyedMixin implements OnInit { } private instantiateStorageInformation(fileLinks:IFileLink[]):StorageInformationBox[] { - // ToDo: Replace with anonymous user capabilities check. - // https://community.openproject.org/projects/openproject/work_packages/44850/activity if (!this.isLoggedIn) { return []; } From 5ac1cec8cd5bb2ad55627671f24b2de19b6d3a87 Mon Sep 17 00:00:00 2001 From: Dmitrii Date: Wed, 23 Nov 2022 12:53:32 +0100 Subject: [PATCH 6/6] fix sql for anonymous user --- app/models/capabilities/scopes/default.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/models/capabilities/scopes/default.rb b/app/models/capabilities/scopes/default.rb index 76689cb2c2..2346c85f5d 100644 --- a/app/models/capabilities/scopes/default.rb +++ b/app/models/capabilities/scopes/default.rb @@ -46,7 +46,6 @@ module Capabilities::Scopes ) capabilities SQL - # binding.pry select('capabilities.*') .from(capabilities_sql) end @@ -130,15 +129,10 @@ module Capabilities::Scopes FROM (#{Action.default.to_sql}) actions JOIN "role_permissions" ON "role_permissions"."permission" = "actions"."permission" JOIN "roles" ON "roles".id = "role_permissions".role_id AND roles.builtin = #{Role::BUILTIN_ANONYMOUS} - JOIN (#{Principal.visible.not_builtin_without_anonymous.not_locked_without_anonymous.to_sql}) users - ON 1 = 1 + JOIN users ON users.type = '#{AnonymousUser.name}' JOIN "projects" ON "projects".active = true - AND ("projects".public = true OR EXISTS (SELECT 1 - FROM members - WHERE members.project_id = projects.id - AND members.user_id = users.id - LIMIT 1)) + AND "projects".public = true LEFT OUTER JOIN enabled_modules ON enabled_modules.project_id = projects.id AND actions.module = enabled_modules.name