diff --git a/config/authz/permission_groups/assignable_permissions/repositories/repository/read.yml b/config/authz/permission_groups/assignable_permissions/repositories/repository/read.yml new file mode 100644 index 0000000000000000000000000000000000000000..e280171a72fa42cd028186e63e846ad357d227d1 --- /dev/null +++ b/config/authz/permission_groups/assignable_permissions/repositories/repository/read.yml @@ -0,0 +1,9 @@ +--- +name: read_repository +description: Grants the ability to read repositories +feature_category: source_code_management +permissions: + - read_repository + - read_repository_health +boundaries: + - project diff --git a/config/authz/permission_groups/assignable_permissions/repositories/repository/update.yml b/config/authz/permission_groups/assignable_permissions/repositories/repository/update.yml new file mode 100644 index 0000000000000000000000000000000000000000..251c8c60645a4b9cad88b7d3c394188af4b746b6 --- /dev/null +++ b/config/authz/permission_groups/assignable_permissions/repositories/repository/update.yml @@ -0,0 +1,8 @@ +--- +name: update_repository +description: Grants the ability to update repositories +feature_category: source_code_management +permissions: + - update_repository +boundaries: + - project diff --git a/config/authz/permissions/repository/_metadata.yml b/config/authz/permissions/repository/_metadata.yml new file mode 100644 index 0000000000000000000000000000000000000000..8fe5b8de7a2906f1681460ec55e851b6b1d81ba1 --- /dev/null +++ b/config/authz/permissions/repository/_metadata.yml @@ -0,0 +1 @@ +feature_category: source_code_management diff --git a/config/authz/permissions/repository/read.yml b/config/authz/permissions/repository/read.yml new file mode 100644 index 0000000000000000000000000000000000000000..2245630f102e17054e08484b0341b8ffec2807a9 --- /dev/null +++ b/config/authz/permissions/repository/read.yml @@ -0,0 +1,6 @@ +--- +name: read_repository +description: Grants the ability to read repositories +feature_category: source_code_management +boundaries: + - project diff --git a/config/authz/permissions/repository/update.yml b/config/authz/permissions/repository/update.yml new file mode 100644 index 0000000000000000000000000000000000000000..c72ee163b2e3556462668c009f029f49b1fa6653 --- /dev/null +++ b/config/authz/permissions/repository/update.yml @@ -0,0 +1,6 @@ +--- +name: update_repository +description: Grants the ability to update repositories +feature_category: source_code_management +boundaries: + - project diff --git a/config/authz/permissions/repository_health/_metadata.yml b/config/authz/permissions/repository_health/_metadata.yml new file mode 100644 index 0000000000000000000000000000000000000000..8fe5b8de7a2906f1681460ec55e851b6b1d81ba1 --- /dev/null +++ b/config/authz/permissions/repository_health/_metadata.yml @@ -0,0 +1 @@ +feature_category: source_code_management diff --git a/config/authz/permissions/repository_health/read.yml b/config/authz/permissions/repository_health/read.yml new file mode 100644 index 0000000000000000000000000000000000000000..be50f0f9e4a29a0eaebf102b14029d6e0bb15056 --- /dev/null +++ b/config/authz/permissions/repository_health/read.yml @@ -0,0 +1,6 @@ +--- +name: read_repository_health +description: Grants the ability to read repository health information +feature_category: source_code_management +boundaries: + - project diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 07bd3348fa6670b8bdc81ed1eca0392e113fc845..411dcb1b98883e73d46e71e63bee0b55e420866a 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -113,6 +113,7 @@ def compare_cache_key(current_user, user_project, target_project, params) end end + route_setting :authorization, permissions: :read_repository, boundary_type: :project desc 'Get a project repository tree' do success Entities::TreeObject end @@ -151,6 +152,7 @@ def compare_cache_key(current_user, user_project, target_project, params) not_found!(e.message) end + route_setting :authorization, permissions: :read_repository, boundary_type: :project desc 'Get raw blob contents from the repository' params do requires :sha, type: String, @@ -164,6 +166,7 @@ def compare_cache_key(current_user, user_project, target_project, params) send_git_blob @repo, @blob end + route_setting :authorization, permissions: :read_repository, boundary_type: :project desc 'Get a blob from the repository' params do requires :sha, type: String, @@ -188,6 +191,7 @@ def compare_cache_key(current_user, user_project, target_project, params) } end + route_setting :authorization, permissions: :read_repository, boundary_type: :project desc 'Get an archive of the repository' params do optional :sha, type: String, @@ -215,6 +219,7 @@ def compare_cache_key(current_user, user_project, target_project, params) not_found!('File') end + route_setting :authorization, permissions: :read_repository, boundary_type: :project desc 'Compare two branches, tags, or commits' do success Entities::Compare end @@ -253,6 +258,7 @@ def compare_cache_key(current_user, user_project, target_project, params) end end + route_setting :authorization, permissions: :read_repository_health, boundary_type: :project desc 'Get repository health' do success Entities::RepositoryHealth end @@ -278,6 +284,7 @@ def compare_cache_key(current_user, user_project, target_project, params) present health, with: Entities::RepositoryHealth end + route_setting :authorization, permissions: :read_repository, boundary_type: :project desc 'Get repository contributors' do success Entities::Contributor end @@ -296,6 +303,7 @@ def compare_cache_key(current_user, user_project, target_project, params) not_found! end + route_setting :authorization, permissions: :read_repository, boundary_type: :project desc 'Get the common ancestor between commits' do success Entities::Commit end @@ -335,7 +343,7 @@ def compare_cache_key(current_user, user_project, target_project, params) use :release_params end route_setting :authentication, job_token_allowed: true - route_setting :authorization, job_token_policies: :read_releases, + route_setting :authorization, permissions: :read_repository, boundary_type: :project, job_token_policies: :read_releases, allow_public_access_for_enabled_project_features: :repository get ':id/repository/changelog' do check_rate_limit!(:project_repositories_changelog, scope: [current_user, user_project]) do @@ -354,6 +362,7 @@ def compare_cache_key(current_user, user_project, target_project, params) render_api_error!("Failed to generate the changelog: #{ex.message}", 422) end + route_setting :authorization, permissions: :update_repository, boundary_type: :project desc 'Generates a changelog section for a release and commits it in a changelog file' do detail 'This feature was introduced in GitLab 13.9' success code: 200 diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 29fcca11377d186d2210abb83ca28d84176a65b3..c2ed70d5c674c52b5dd0f3eb2a8d4a8b2b465f6c 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -166,6 +166,13 @@ expect(json_response).to be_an(Array) end end + + it_behaves_like 'authorizing granular token permissions', :read_repository do + let(:boundary_object) { project } + let(:request) do + get api(route, personal_access_token: pat) + end + end end describe "GET /projects/:id/repository/blobs/:sha" do @@ -239,6 +246,13 @@ let(:request) { get api(route, guest) } end end + + it_behaves_like 'authorizing granular token permissions', :read_repository do + let(:boundary_object) { project } + let(:request) do + get api(route, personal_access_token: pat) + end + end end describe "GET /projects/:id/repository/blobs/:sha/raw" do @@ -312,6 +326,13 @@ let(:request) { get api(route, guest) } end end + + it_behaves_like 'authorizing granular token permissions', :read_repository do + let(:boundary_object) { project } + let(:request) do + get api(route, personal_access_token: pat) + end + end end describe "GET /projects/:id/repository/archive(.:format)?:sha" do @@ -501,6 +522,13 @@ def expected_archive_request(repository, metadata, path, include_lfs_blobs, excl let(:request) { get api(route, guest) } end end + + it_behaves_like 'authorizing granular token permissions', :read_repository do + let(:boundary_object) { project } + let(:request) do + get api(route, personal_access_token: pat) + end + end end describe 'GET /projects/:id/repository/compare' do @@ -725,6 +753,13 @@ def commit_messages(response) let(:request) { get api(route, guest) } end end + + it_behaves_like 'authorizing granular token permissions', :read_repository do + let(:boundary_object) { project } + let(:request) do + get api(route, personal_access_token: pat), params: { from: 'master', to: 'feature' } + end + end end describe 'GET /projects/:id/repository/contributors' do @@ -839,6 +874,13 @@ def commit_messages(response) expect(first_link_url).to include('sort=asc') end end + + it_behaves_like 'authorizing granular token permissions', :read_repository do + let(:boundary_object) { project } + let(:request) do + get api(route, personal_access_token: pat) + end + end end describe 'GET :id/repository/health' do @@ -920,6 +962,24 @@ def commit_messages(response) let(:current_user) { guest } end end + + context 'when feature flag is disabled' do + before do + stub_feature_flags(project_repositories_health: false) + end + + it_behaves_like '404 response' do + let(:current_user) { user } + end + end + + it_behaves_like 'authorizing granular token permissions', :read_repository_health do + let(:params) { { generate: true } } + let(:boundary_object) { project } + let(:request) do + get api("/projects/#{project.id}/repository/health", personal_access_token: pat), params: params + end + end end describe 'GET :id/repository/merge_base' do @@ -993,6 +1053,13 @@ def commit_messages(response) expect(json_response['message']).to eq('Provide at least 2 refs') end end + + it_behaves_like 'authorizing granular token permissions', :read_repository do + let(:boundary_object) { project } + let(:request) do + get api("/projects/#{project.id}/repository/merge_base", personal_access_token: pat), params: { refs: refs } + end + end end describe 'GET /projects/:id/repository/changelog' do @@ -1200,6 +1267,29 @@ def commit_messages(response) let(:message) { 'Failed to generate the changelog: The commit start range is unspecified, and no previous tag could be found to use instead' } end end + + it_behaves_like 'authorizing granular token permissions', :read_repository do + let(:boundary_object) { project } + + before do + spy = instance_spy(::Repositories::ChangelogService, execute: 'Release notes') + + allow(::Repositories::ChangelogService) + .to receive(:new) + .and_return(spy) + end + + let(:request) do + get api("/projects/#{project.id}/repository/changelog", personal_access_token: pat), + params: { + version: '1.0.0', + from: 'foo', + to: 'bar', + date: '2020-01-01', + trailer: 'Foo' + } + end + end end describe 'POST /projects/:id/repository/changelog' do @@ -1400,5 +1490,33 @@ def commit_messages(response) expect(response).to have_gitlab_http_status(:too_many_requests) end + + it_behaves_like 'authorizing granular token permissions', :update_repository do + let(:boundary_object) { project } + + before do + spy = instance_spy(::Repositories::ChangelogService) + + allow(::Repositories::ChangelogService) + .to receive(:new) + .and_return(spy) + + allow(spy).to receive(:execute).with(commit_to_changelog: true) + end + + let(:request) do + post api("/projects/#{project.id}/repository/changelog", personal_access_token: pat), + params: { + version: '1.0.0', + from: 'foo', + to: 'bar', + date: '2020-01-01', + branch: 'kittens', + trailer: 'Foo', + file: 'FOO.md', + message: 'Commit message' + } + end + end end end