diff --git a/lib/api/users.rb b/lib/api/users.rb index c61ec6fade38beaca29a10703d234b11584b4614..93df9413119217d0cd9cd8e3bdaf85593d2d9164 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -346,6 +346,30 @@ def reorder_users(users) end # rubocop: enable CodeReuse/ActiveRecord + desc 'Get the project-level Deploy keys that a specified user can access to.' do + success Entities::DeployKey + end + params do + requires :user_id, type: String, desc: 'The ID or username of the user' + use :pagination + end + get ':user_id/project_deploy_keys', requirements: API::USER_REQUIREMENTS, feature_category: :continuous_delivery do + user = find_user(params[:user_id]) + not_found!('User') unless user && can?(current_user, :read_user, user) + + project_ids = Project.visible_to_user_and_access_level(current_user, Gitlab::Access::MAINTAINER) + + unless current_user == user + # Restrict to only common projects of both current_user and user. + project_ids = project_ids.visible_to_user_and_access_level(user, Gitlab::Access::MAINTAINER) + end + + forbidden!('No common authorized project found') unless project_ids.present? + + keys = DeployKey.in_projects(project_ids) + present paginate(keys), with: Entities::DeployKey + end + desc 'Add an SSH key to a specified user. Available only for admins.' do success Entities::SSHKey end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 2c5a734a0e182ec351c1de301a23baf15485353c..d4dc7375e9edb45e21b4adc305c30b8e79f69a29 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -1694,6 +1694,111 @@ def update_password(user, admin, password = User.random_password) end end + describe 'GET /users/:id/project_deploy_keys' do + let(:project) { create(:project) } + + before do + project.add_maintainer(user) + + deploy_key = create(:deploy_key, user: user) + create(:deploy_keys_project, project: project, deploy_key_id: deploy_key.id) + end + + it 'returns 404 for non-existing user' do + get api("/users/#{non_existing_record_id}/project_deploy_keys") + + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['message']).to eq('404 User Not Found') + end + + it 'returns array of project deploy keys with pagination' do + get api("/users/#{user.id}/project_deploy_keys", user) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.first['title']).to eq(user.deploy_keys.first.title) + end + + it 'forbids when a developer fetches maintainer keys' do + dev_user = create(:user) + project.add_developer(dev_user) + + get api("/users/#{user.id}/project_deploy_keys", dev_user) + + expect(response).to have_gitlab_http_status(:forbidden) + expect(json_response['message']).to eq('403 Forbidden - No common authorized project found') + end + + context 'with multiple projects' do + let(:second_project) { create(:project) } + let(:second_user) { create(:user) } + + before do + second_project.add_maintainer(second_user) + + deploy_key = create(:deploy_key, user: second_user) + create(:deploy_keys_project, project: second_project, deploy_key_id: deploy_key.id) + end + + context 'when no common projects for user and current_user' do + it 'forbids' do + get api("/users/#{user.id}/project_deploy_keys", second_user) + + expect(response).to have_gitlab_http_status(:forbidden) + expect(json_response['message']).to eq('403 Forbidden - No common authorized project found') + end + end + + context 'when there are common projects for user and current_user' do + before do + project.add_maintainer(second_user) + end + + it 'lists only common project keys' do + expect(second_user.project_deploy_keys).to contain_exactly( + project.deploy_keys.first, second_project.deploy_keys.first) + + get api("/users/#{second_user.id}/project_deploy_keys", user) + + expect(json_response.count).to eq(1) + expect(json_response.first['key']).to eq(project.deploy_keys.first.key) + end + + it 'lists only project_deploy_keys and not user deploy_keys' do + third_user = create(:user) + + project.add_maintainer(third_user) + second_project.add_maintainer(third_user) + + create(:deploy_key, user: second_user) + create(:deploy_key, user: third_user) + + get api("/users/#{second_user.id}/project_deploy_keys", third_user) + + expect(json_response.count).to eq(2) + expect([json_response.first['key'], json_response.second['key']]).to contain_exactly( + project.deploy_keys.first.key, second_project.deploy_keys.first.key) + end + + it 'avoids N+1 queries' do + second_project.add_maintainer(user) + + control_count = ActiveRecord::QueryRecorder.new do + get api("/users/#{second_user.id}/project_deploy_keys", user) + end.count + + deploy_key = create(:deploy_key, user: second_user) + create(:deploy_keys_project, project: second_project, deploy_key_id: deploy_key.id) + + expect do + get api("/users/#{second_user.id}/project_deploy_keys", user) + end.not_to exceed_query_limit(control_count) + end + end + end + end + describe 'GET /user/:id/keys' do it 'returns 404 for non-existing user' do get api("/users/#{non_existing_record_id}/keys")