Commit 204d3d33 authored by Ash McKenzie's avatar Ash McKenzie

Merge branch '213013_group_level_security_reports_api' into 'master'

Group-level vulnerability exports API

See merge request gitlab-org/gitlab!31889
parents d76c5e68 d9391e55
......@@ -42,7 +42,7 @@ POST /security/projects/:id/vulnerability_exports
curl --header POST "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/security/projects/1/vulnerability_exports
```
The created vulnerability export will be automatically deleted after 1 hour.
The created vulnerability export is automatically deleted after 1 hour.
Example response:
......@@ -51,6 +51,53 @@ Example response:
"id": 2,
"created_at": "2020-03-30T09:35:38.746Z",
"project_id": 1,
"group_id": null,
"format": "csv",
"status": "created",
"started_at": null,
"finished_at": null,
"_links": {
"self": "https://gitlab.example.com/api/v4/security/vulnerability_exports/2",
"download": "https://gitlab.example.com/api/v4/security/vulnerability_exports/2/download"
}
}
```
## Create a group-level vulnerability export
Creates a new vulnerability export for a group.
Vulnerability export permissions inherit permissions from their group. If a group is
private and a user isn't a member of the group to which the vulnerability
belongs, requests to that group return a `404 Not Found` status code.
Vulnerability exports can be only accessed by the export's author.
If an authenticated user doesn't have permission to
[create a new vulnerability](../user/permissions.md#group-members-permissions),
this request results in a `403` status code.
```plaintext
POST /security/groups/:id/vulnerability_exports
```
| Attribute | Type | Required | Description |
| ------------------- | ----------------- | ---------- | -----------------------------------------------------------------------------------------------------------------------------|
| `id` | integer or string | yes | The ID or [URL-encoded path](README.md#namespaced-path-encoding) of the group which the authenticated user is a member of |
```shell
curl --header POST "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/security/groups/1/vulnerability_exports
```
The created vulnerability export is automatically deleted after 1 hour.
Example response:
```json
{
"id": 2,
"created_at": "2020-03-30T09:35:38.746Z",
"project_id": null,
"group_id": 1,
"format": "csv",
"status": "created",
"started_at": null,
......@@ -83,6 +130,7 @@ Example response:
"id": 2,
"created_at": "2020-03-30T09:35:38.746Z",
"project_id": null,
"group_id": null,
"format": "csv",
"status": "created",
"started_at": null,
......@@ -119,6 +167,7 @@ Example response:
"id": 2,
"created_at": "2020-03-30T09:35:38.746Z",
"project_id": 1,
"group_id": null,
"format": "csv",
"status": "finished",
"started_at": "2020-03-30T09:36:54.469Z",
......
......@@ -202,6 +202,8 @@ module EE
rule { security_dashboard_enabled & developer }.enable :read_group_security_dashboard
rule { can?(:read_group_security_dashboard) }.enable :create_vulnerability_export
rule { admin | owner }.policy do
enable :read_group_compliance_dashboard
enable :read_group_credentials_inventory
......
---
title: Introduce a new API endpoint to generate group-level vulnerability exports
merge_request: 31889
author:
type: added
......@@ -38,7 +38,7 @@ module API
default: ::Vulnerabilities::Export.formats.each_key.first,
values: ::Vulnerabilities::Export.formats.keys
end
desc 'Generate an export of project vulnerability findings' do
desc 'Generate a project-level export' do
success EE::API::Entities::VulnerabilityExport
end
......@@ -53,6 +53,28 @@ module API
end
end
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
params do
requires :id, type: String, desc: 'The ID of a group'
optional :export_format, type: String, desc: 'The format of export to be generated',
default: ::Vulnerabilities::Export.formats.each_key.first,
values: ::Vulnerabilities::Export.formats.keys
end
desc 'Generate a group-level export' do
success EE::API::Entities::VulnerabilityExport
end
before do
not_found! unless Feature.enabled?(:first_class_vulnerabilities, user_group, default_enabled: true)
end
post ':id/vulnerability_exports' do
authorize! :create_vulnerability_export, user_group
process_create_request_for(user_group)
end
end
namespace do
before do
not_found! unless Feature.enabled?(:first_class_vulnerabilities, default_enabled: true)
......@@ -63,7 +85,7 @@ module API
default: ::Vulnerabilities::Export.formats.each_key.first,
values: ::Vulnerabilities::Export.formats.keys
end
desc 'Generate an instance level export' do
desc 'Generate an instance-level export' do
success EE::API::Entities::VulnerabilityExport
end
post 'vulnerability_exports' do
......@@ -73,7 +95,7 @@ module API
end
end
desc 'Get single project vulnerability export' do
desc 'Get a single vulnerability export' do
success EE::API::Entities::VulnerabilityExport
end
get 'vulnerability_exports/:id' do
......@@ -88,7 +110,7 @@ module API
with: EE::API::Entities::VulnerabilityExport
end
desc 'Download single project vulnerability export'
desc 'Download a single vulnerability export'
get 'vulnerability_exports/:id/download' do
authorize! :read_vulnerability_export, vulnerability_export
......
......@@ -9,6 +9,7 @@ module EE
expose :id
expose :created_at
expose :project_id
expose :group_id
expose :format
expose :status
expose :started_at
......
......@@ -4,6 +4,7 @@
"id",
"created_at",
"project_id",
"group_id",
"format",
"status",
"started_at",
......@@ -13,7 +14,8 @@
"properties" : {
"id": { "type": "integer" },
"created_at": { "type": "date" },
"project_id": { "type": "integer" },
"project_id": { "type": ["integer", "null"] },
"group_id": { "type": ["integer", "null"] },
"format": {
"type": "string",
"enum": ["csv"]
......
......@@ -14,6 +14,7 @@ describe ::EE::API::Entities::VulnerabilityExport do
expect(subject[:id]).to eq(vulnerability_export.id)
expect(subject[:created_at]).to eq(vulnerability_export.created_at)
expect(subject[:project_id]).to eq(vulnerability_export.project_id)
expect(subject[:group_id]).to eq(vulnerability_export.group_id)
expect(subject[:format]).to eq(vulnerability_export.format)
expect(subject[:status]).to eq(vulnerability_export.status)
expect(subject[:started_at]).to eq(vulnerability_export.started_at)
......
......@@ -630,7 +630,9 @@ describe GroupPolicy do
end
end
describe 'read_group_security_dashboard' do
describe 'read_group_security_dashboard & create_vulnerability_export' do
let(:abilities) { %i(read_group_security_dashboard create_vulnerability_export) }
before do
stub_licensed_features(security_dashboard: true)
end
......@@ -638,57 +640,57 @@ describe GroupPolicy do
context 'with admin' do
let(:current_user) { admin }
it { is_expected.to be_allowed(:read_group_security_dashboard) }
it { is_expected.to be_allowed(*abilities) }
end
context 'with owner' do
let(:current_user) { owner }
it { is_expected.to be_allowed(:read_group_security_dashboard) }
it { is_expected.to be_allowed(*abilities) }
end
context 'with maintainer' do
let(:current_user) { maintainer }
it { is_expected.to be_allowed(:read_group_security_dashboard) }
it { is_expected.to be_allowed(*abilities) }
end
context 'with developer' do
let(:current_user) { developer }
it { is_expected.to be_allowed(:read_group_security_dashboard) }
it { is_expected.to be_allowed(*abilities) }
context 'when security dashboard features is not available' do
before do
stub_licensed_features(security_dashboard: false)
end
it { is_expected.to be_disallowed(:read_group_security_dashboard) }
it { is_expected.to be_disallowed(*abilities) }
end
end
context 'with reporter' do
let(:current_user) { reporter }
it { is_expected.to be_disallowed(:read_group_security_dashboard) }
it { is_expected.to be_disallowed(*abilities) }
end
context 'with guest' do
let(:current_user) { guest }
it { is_expected.to be_disallowed(:read_group_security_dashboard) }
it { is_expected.to be_disallowed(*abilities) }
end
context 'with non member' do
let(:current_user) { create(:user) }
it { is_expected.to be_disallowed(:read_group_security_dashboard) }
it { is_expected.to be_disallowed(*abilities) }
end
context 'with anonymous' do
let(:current_user) { nil }
it { is_expected.to be_disallowed(:read_group_security_dashboard) }
it { is_expected.to be_disallowed(*abilities) }
end
end
......
......@@ -71,6 +71,70 @@ describe API::VulnerabilityExports do
end
end
describe 'POST /security/groups/:id/vulnerability_exports' do
let_it_be(:group) { create(:group) }
let(:format) { 'csv' }
let(:request_path) { "/security/groups/#{group.id}/vulnerability_exports" }
subject(:create_vulnerability_export) { post api(request_path, user), params: { export_format: format } }
context 'when the request does not fulfill the requirements' do
let(:format) { 'exif' }
it 'responds with bad_request' do
create_vulnerability_export
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response).to eq('error' => 'export_format does not have a valid value')
end
end
context 'when the request fulfills the requirements' do
context 'when the user is not authorized to take the action' do
it 'responds with 403 forbidden' do
create_vulnerability_export
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when the user is authorized to take the action' do
let(:mock_service_object) { instance_double(VulnerabilityExports::CreateService, execute: vulnerability_export) }
before do
allow(VulnerabilityExports::CreateService).to receive(:new).and_return(mock_service_object)
group.add_developer(user)
end
context 'when the export creation succeeds' do
let(:vulnerability_export) { create(:vulnerability_export) }
it 'returns information about new vulnerability export' do
create_vulnerability_export
expect(response).to have_gitlab_http_status(:created)
expect(response).to match_response_schema('public_api/v4/vulnerability_export', dir: 'ee')
end
end
context 'when the export creation fails' do
let(:errors) { instance_double(ActiveModel::Errors, any?: true, messages: ['foo']) }
let(:vulnerability_export) { instance_double(Vulnerabilities::Export, persisted?: false, errors: errors) }
it 'returns the error message' do
create_vulnerability_export
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response).to eq('message' => ['foo'])
end
end
end
end
it_behaves_like 'forbids access to vulnerability API endpoint in case of disabled features'
end
describe 'POST /security/vulnerability_exports' do
let(:format) { 'csv' }
let(:request_path) { "/security/vulnerability_exports" }
......
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