Commit 3f2e821d authored by Tan Le's avatar Tan Le Committed by Mayra Cabrera

Extend ability to read audit events to more roles

This commit will extend the ability to query project and group audit
events via API to more roles.

Group-level audit events
- Group Owner and above: read all group-level audit events
- Developer to Maintainer: read their own group-level audit events

Project-level audit events
- Project Maintainer and above: read all project-level audit events
- Developer: read their own project-level audit events
parent 920f4430
...@@ -132,7 +132,8 @@ Example response: ...@@ -132,7 +132,8 @@ Example response:
The Group Audit Events API allows you to retrieve [group audit events](../administration/audit_events.md#group-events). The Group Audit Events API allows you to retrieve [group audit events](../administration/audit_events.md#group-events).
To retrieve group audit events using the API, you must [authenticate yourself](README.md#authentication) as an Administrator or an owner of the group. A user with a Owner role (or above) can retrieve group audit events of all users.
A user with a Developer or Maintainer role is limited to group audit events based on their individual actions.
### Retrieve all group audit events ### Retrieve all group audit events
...@@ -238,7 +239,8 @@ Example response: ...@@ -238,7 +239,8 @@ Example response:
The Project Audit Events API allows you to retrieve [project audit events](../administration/audit_events.md#project-events). The Project Audit Events API allows you to retrieve [project audit events](../administration/audit_events.md#project-events).
To retrieve project audit events using the API, you must [authenticate yourself](README.md#authentication) as a Maintainer or an Owner of the project. A user with a Maintainer role (or above) can retrieve project audit events of all users.
A user with a Developer role is limited to project audit events based on their individual actions.
### Retrieve all project audit events ### Retrieve all project audit events
......
...@@ -233,6 +233,7 @@ module EE ...@@ -233,6 +233,7 @@ module EE
enable :create_wiki enable :create_wiki
enable :admin_merge_request enable :admin_merge_request
enable :read_ci_minutes_quota enable :read_ci_minutes_quota
enable :read_group_audit_events
end end
rule { security_dashboard_enabled & developer }.enable :read_group_security_dashboard rule { security_dashboard_enabled & developer }.enable :read_group_security_dashboard
......
...@@ -195,6 +195,7 @@ module EE ...@@ -195,6 +195,7 @@ module EE
enable :update_vulnerability_feedback enable :update_vulnerability_feedback
enable :read_ci_minutes_quota enable :read_ci_minutes_quota
enable :admin_feature_flags_issue_links enable :admin_feature_flags_issue_links
enable :read_project_audit_events
end end
rule { can?(:developer_access) & iterations_available }.policy do rule { can?(:developer_access) & iterations_available }.policy do
......
---
title: Extend ability to read audit events to more roles
merge_request: 49106
author:
type: added
...@@ -54,7 +54,13 @@ module EE ...@@ -54,7 +54,13 @@ module EE
end end
def audit_log_finder_params def audit_log_finder_params
params.slice(:created_after, :created_before) params
.slice(:created_after, :created_before)
.then { |params| filter_by_author(params) }
end
def filter_by_author(params)
can?(current_user, :admin_group, user_group) ? params : params.merge(author_id: current_user.id)
end end
override :delete_group override :delete_group
...@@ -90,7 +96,7 @@ module EE ...@@ -90,7 +96,7 @@ module EE
segment ':id/audit_events' do segment ':id/audit_events' do
before do before do
authorize! :admin_group, user_group authorize! :read_group_audit_events, user_group
check_audit_events_available!(user_group) check_audit_events_available!(user_group)
increment_unique_values('a_compliance_audit_events_api', current_user.id) increment_unique_values('a_compliance_audit_events_api', current_user.id)
end end
......
...@@ -23,7 +23,7 @@ module EE ...@@ -23,7 +23,7 @@ module EE
end end
segment ':id/audit_events', feature_category: :audit_events do segment ':id/audit_events', feature_category: :audit_events do
before do before do
authorize! :admin_project, user_project authorize! :read_project_audit_events, user_project
check_audit_events_available!(user_project) check_audit_events_available!(user_project)
increment_unique_values('a_compliance_audit_events_api', current_user.id) increment_unique_values('a_compliance_audit_events_api', current_user.id)
end end
...@@ -97,7 +97,13 @@ module EE ...@@ -97,7 +97,13 @@ module EE
end end
def audit_log_finder_params def audit_log_finder_params
params.slice(:created_after, :created_before) params
.slice(:created_after, :created_before)
.then { |params| filter_by_author(params) }
end
def filter_by_author(params)
can?(current_user, :admin_project, user_project) ? params : params.merge(author_id: current_user.id)
end end
override :delete_project override :delete_project
......
...@@ -1159,6 +1159,27 @@ RSpec.describe GroupPolicy do ...@@ -1159,6 +1159,27 @@ RSpec.describe GroupPolicy do
end end
end end
describe ':read_group_audit_events' do
using RSpec::Parameterized::TableSyntax
let(:policy) { :read_group_audit_events }
where(:role, :allowed) do
:guest | false
:reporter | false
:developer | true
:maintainer | true
:owner | true
:admin | true
end
with_them do
let(:current_user) { public_send(role) }
it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) }
end
end
context 'when group is locked because storage usage limit exceeded' do context 'when group is locked because storage usage limit exceeded' do
let(:current_user) { owner } let(:current_user) { owner }
let(:policies) do let(:policies) do
......
...@@ -22,7 +22,7 @@ RSpec.describe ProjectPolicy do ...@@ -22,7 +22,7 @@ RSpec.describe ProjectPolicy do
let(:additional_developer_permissions) do let(:additional_developer_permissions) do
%i[ %i[
admin_vulnerability_feedback read_project_security_dashboard admin_vulnerability_feedback read_project_audit_events read_project_security_dashboard
read_vulnerability read_vulnerability_scanner create_vulnerability create_vulnerability_export admin_vulnerability read_vulnerability read_vulnerability_scanner create_vulnerability create_vulnerability_export admin_vulnerability
admin_vulnerability_issue_link read_merge_train admin_vulnerability_issue_link read_merge_train
] ]
......
...@@ -17,6 +17,46 @@ RSpec.describe API::Groups do ...@@ -17,6 +17,46 @@ RSpec.describe API::Groups do
group.ldap_group_links.create cn: 'ldap-group', group_access: Gitlab::Access::MAINTAINER, provider: 'ldap' group.ldap_group_links.create cn: 'ldap-group', group_access: Gitlab::Access::MAINTAINER, provider: 'ldap'
end end
shared_examples 'inaccessable by reporter role and lower' do
context 'for reporter' do
before do
reporter = create(:user)
group.add_reporter(reporter)
get api(path, reporter)
end
it 'returns 403 response' do
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'for guest' do
before do
guest = create(:user)
group.add_guest(guest)
get api(path, guest)
end
it 'returns 403 response' do
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'for anonymous' do
before do
anonymous = create(:user)
get api(path, anonymous)
end
it 'returns 403 response' do
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
describe "GET /groups" do describe "GET /groups" do
context "when authenticated as user" do context "when authenticated as user" do
it "returns ldap details" do it "returns ldap details" do
...@@ -507,9 +547,21 @@ RSpec.describe API::Groups do ...@@ -507,9 +547,21 @@ RSpec.describe API::Groups do
describe 'GET group/:id/audit_events' do describe 'GET group/:id/audit_events' do
let(:path) { "/groups/#{group.id}/audit_events" } let(:path) { "/groups/#{group.id}/audit_events" }
context 'when authenticated, as a user' do it_behaves_like 'inaccessable by reporter role and lower'
it_behaves_like '403 response' do
let(:request) { get api(path, create(:user)) } context 'when authenticated, as a member' do
before do
stub_licensed_features(audit_events: true)
group.add_developer(user)
end
it 'returns only events authored by current user' do
group_audit_event = create(:group_audit_event, entity_id: group.id, author_id: user.id)
create(:group_audit_event, entity_id: group.id, author_id: another_user.id)
get api(path, user)
expect_response_contain_exactly(group_audit_event.id)
end end
end end
...@@ -602,9 +654,33 @@ RSpec.describe API::Groups do ...@@ -602,9 +654,33 @@ RSpec.describe API::Groups do
let_it_be(:group_audit_event) { create(:group_audit_event, created_at: Date.new(2000, 1, 10), entity_id: group.id) } let_it_be(:group_audit_event) { create(:group_audit_event, created_at: Date.new(2000, 1, 10), entity_id: group.id) }
context 'when authenticated, as a user' do it_behaves_like 'inaccessable by reporter role and lower'
it_behaves_like '403 response' do
let(:request) { get api(path, create(:user)) } context 'when authenticated, as a member' do
let_it_be(:developer) { create(:user) }
before do
stub_licensed_features(audit_events: true)
group.add_developer(developer)
end
it 'returns 200 response' do
audit_event = create(:group_audit_event, entity_id: group.id, author_id: developer.id)
path = "/groups/#{group.id}/audit_events/#{audit_event.id}"
get api(path, developer)
expect(response).to have_gitlab_http_status(:ok)
end
context 'existing audit event of a different user' do
let_it_be(:audit_event) { create(:group_audit_event, entity_id: group.id, author_id: another_user.id) }
let(:path) { "/groups/#{group.id}/audit_events/#{audit_event.id}" }
it_behaves_like '404 response' do
let(:request) { get api(path, developer) }
end
end end
end end
......
...@@ -6,8 +6,49 @@ RSpec.describe API::Projects do ...@@ -6,8 +6,49 @@ RSpec.describe API::Projects do
include ExternalAuthorizationServiceHelpers include ExternalAuthorizationServiceHelpers
let(:user) { create(:user) } let(:user) { create(:user) }
let_it_be(:another_user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) } let(:project) { create(:project, namespace: user.namespace) }
shared_examples 'inaccessable by reporter role and lower' do
context 'for reporter' do
before do
reporter = create(:user)
project.add_reporter(reporter)
get api(path, reporter)
end
it 'returns 403 response' do
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'for guest' do
before do
guest = create(:user)
project.add_guest(guest)
get api(path, guest)
end
it 'returns 403 response' do
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'for anonymous' do
before do
anonymous = create(:user)
get api(path, anonymous)
end
it 'returns 403 response' do
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
describe 'GET /projects' do describe 'GET /projects' do
it 'does not break on license checks' do it 'does not break on license checks' do
enable_namespace_license_check! enable_namespace_license_check!
...@@ -515,13 +556,31 @@ RSpec.describe API::Projects do ...@@ -515,13 +556,31 @@ RSpec.describe API::Projects do
let_it_be(:project) { create(:project, :public, namespace: user.namespace) } let_it_be(:project) { create(:project, :public, namespace: user.namespace) }
let(:path) { "/projects/#{project.id}/audit_events" } let(:path) { "/projects/#{project.id}/audit_events" }
context 'when authenticated, as a user' do it_behaves_like 'inaccessable by reporter role and lower'
it_behaves_like '403 response' do
let(:request) { get api(path, create(:user)) } context 'when authenticated, as a member' do
let_it_be(:developer) { create(:user) }
before do
stub_licensed_features(audit_events: true)
project.add_developer(developer)
end
it 'returns only events authored by current user' do
project_audit_event_1 = create(:project_audit_event, entity_id: project.id, author_id: developer.id)
create(:project_audit_event, entity_id: project.id, author_id: 666)
get api(path, developer)
expect_response_contain_exactly(project_audit_event_1.id)
end end
end end
context 'when authenticated, as a project owner' do context 'when authenticated, as a project owner' do
before do
project.add_maintainer(user)
end
context 'audit events feature is not available' do context 'audit events feature is not available' do
before do before do
stub_licensed_features(audit_events: false) stub_licensed_features(audit_events: false)
...@@ -612,9 +671,46 @@ RSpec.describe API::Projects do ...@@ -612,9 +671,46 @@ RSpec.describe API::Projects do
let_it_be(:project_audit_event) { create(:project_audit_event, created_at: Date.new(2000, 1, 10), entity_id: project.id) } let_it_be(:project_audit_event) { create(:project_audit_event, created_at: Date.new(2000, 1, 10), entity_id: project.id) }
context 'when authenticated, as a user' do it_behaves_like 'inaccessable by reporter role and lower'
context 'when authenticated, as a guest' do
let_it_be(:guest) { create(:user) }
before do
stub_licensed_features(audit_events: true)
project.add_guest(guest)
end
it_behaves_like '403 response' do it_behaves_like '403 response' do
let(:request) { get api(path, create(:user)) } let(:request) { get api(path, guest) }
end
end
context 'when authenticated, as a member' do
let_it_be(:developer) { create(:user) }
before do
stub_licensed_features(audit_events: true)
project.add_developer(developer)
end
it 'returns 200 response' do
audit_event = create(:project_audit_event, entity_id: project.id, author_id: developer.id)
path = "/projects/#{project.id}/audit_events/#{audit_event.id}"
get api(path, developer)
expect(response).to have_gitlab_http_status(:ok)
end
context 'existing audit event of a different user' do
let_it_be(:audit_event) { create(:project_audit_event, entity_id: project.id, author_id: another_user.id) }
let(:path) { "/projects/#{project.id}/audit_events/#{audit_event.id}" }
it_behaves_like '404 response' do
let(:request) { get api(path, developer) }
end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment