Merge branch 'zj-enable-deploy-keys-api' into 'master'

Enable/Disable Deploy keys for a project

Closes #20123 

## Does this MR meet the acceptance criteria?

- [X] [CHANGELOG](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CHANGELOG) entry added
- [X] [Documentation created/updated](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/doc_styleguide.md)
- [X] API support added
- Tests
  - [X] Added for this feature/bug
  - [X] All builds are passing

See merge request !5647
This commit is contained in:
Douwe Maan 2016-08-08 19:37:34 +00:00
commit 37005ed8bd
9 changed files with 326 additions and 163 deletions

View file

@ -6,6 +6,7 @@ v 8.11.0 (unreleased)
- Fix the title of the toggle dropdown button. !5515 (herminiotorres) - Fix the title of the toggle dropdown button. !5515 (herminiotorres)
- Improve diff performance by eliminating redundant checks for text blobs - Improve diff performance by eliminating redundant checks for text blobs
- Convert switch icon into icon font (ClemMakesApps) - Convert switch icon into icon font (ClemMakesApps)
- API: Endpoints for enabling and disabling deploy keys
- Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell) - Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell)
- Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell) - Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell)
- Ignore URLs starting with // in Markdown links !5677 (winniehell) - Ignore URLs starting with // in Markdown links !5677 (winniehell)

View file

@ -12,8 +12,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
end end
def new def new
redirect_to namespace_project_deploy_keys_path(@project.namespace, redirect_to namespace_project_deploy_keys_path(@project.namespace, @project)
@project)
end end
def create def create
@ -21,19 +20,16 @@ class Projects::DeployKeysController < Projects::ApplicationController
set_index_vars set_index_vars
if @key.valid? && @project.deploy_keys << @key if @key.valid? && @project.deploy_keys << @key
redirect_to namespace_project_deploy_keys_path(@project.namespace, redirect_to namespace_project_deploy_keys_path(@project.namespace, @project)
@project)
else else
render "index" render "index"
end end
end end
def enable def enable
@key = accessible_keys.find(params[:id]) Projects::EnableDeployKeyService.new(@project, current_user, params).execute
@project.deploy_keys << @key
redirect_to namespace_project_deploy_keys_path(@project.namespace, redirect_to namespace_project_deploy_keys_path(@project.namespace, @project)
@project)
end end
def disable def disable
@ -47,7 +43,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
def set_index_vars def set_index_vars
@enabled_keys ||= @project.deploy_keys @enabled_keys ||= @project.deploy_keys
@available_keys ||= accessible_keys - @enabled_keys @available_keys ||= current_user.accessible_deploy_keys - @enabled_keys
@available_project_keys ||= current_user.project_deploy_keys - @enabled_keys @available_project_keys ||= current_user.project_deploy_keys - @enabled_keys
@available_public_keys ||= DeployKey.are_public - @enabled_keys @available_public_keys ||= DeployKey.are_public - @enabled_keys
@ -56,10 +52,6 @@ class Projects::DeployKeysController < Projects::ApplicationController
@available_public_keys -= @available_project_keys @available_public_keys -= @available_project_keys
end end
def accessible_keys
@accessible_keys ||= current_user.accessible_deploy_keys
end
def deploy_key_params def deploy_key_params
params.require(:deploy_key).permit(:key, :title) params.require(:deploy_key).permit(:key, :title)
end end

View file

@ -0,0 +1,17 @@
module Projects
class EnableDeployKeyService < BaseService
def execute
key = accessible_keys.find_by(id: params[:key_id] || params[:id])
return unless key
project.deploy_keys << key
key
end
private
def accessible_keys
current_user.accessible_deploy_keys
end
end
end

View file

@ -159,3 +159,51 @@ Example response:
"id" : 13 "id" : 13
} }
``` ```
## Enable a deploy key
Enables a deploy key for a project so this can be used. Returns the enabled key, with a status code 201 when successful.
```bash
curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/deploy_keys/13/enable
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project |
| `key_id` | integer | yes | The ID of the deploy key |
Example response:
```json
{
"key" : "ssh-rsa AAAA...",
"id" : 12,
"title" : "My deploy key",
"created_at" : "2015-08-29T12:44:31.550Z"
}
```
## Disable a deploy key
Disable a deploy key for a project. Returns the disabled key.
```bash
curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/deploy_keys/13/disable
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project |
| `key_id` | integer | yes | The ID of the deploy key |
Example response:
```json
{
"key" : "ssh-rsa AAAA...",
"id" : 12,
"title" : "My deploy key",
"created_at" : "2015-08-29T12:44:31.550Z"
}
```

View file

@ -10,6 +10,9 @@ module API
present keys, with: Entities::SSHKey present keys, with: Entities::SSHKey
end end
params do
requires :id, type: String, desc: 'The ID of the project'
end
resource :projects do resource :projects do
before { authorize_admin_project } before { authorize_admin_project }
@ -17,52 +20,43 @@ module API
# Use "projects/:id/deploy_keys/..." instead. # Use "projects/:id/deploy_keys/..." instead.
# #
%w(keys deploy_keys).each do |path| %w(keys deploy_keys).each do |path|
# Get a specific project's deploy keys desc "Get a specific project's deploy keys" do
# success Entities::SSHKey
# Example Request: end
# GET /projects/:id/deploy_keys
get ":id/#{path}" do get ":id/#{path}" do
present user_project.deploy_keys, with: Entities::SSHKey present user_project.deploy_keys, with: Entities::SSHKey
end end
# Get single deploy key owned by currently authenticated user desc 'Get single deploy key' do
# success Entities::SSHKey
# Example Request: end
# GET /projects/:id/deploy_keys/:key_id params do
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
end
get ":id/#{path}/:key_id" do get ":id/#{path}/:key_id" do
key = user_project.deploy_keys.find params[:key_id] key = user_project.deploy_keys.find params[:key_id]
present key, with: Entities::SSHKey present key, with: Entities::SSHKey
end end
# Add new deploy key to currently authenticated user # TODO: for 9.0 we should check if params are there with the params block
# If deploy key already exists - it will be joined to project # grape provides, at this point we'd change behaviour so we can't
# but only if original one was accessible by same user # Behaviour now if you don't provide all required params: it renders a
# # validation error or two.
# Parameters: desc 'Add new deploy key to currently authenticated user' do
# key (required) - New deploy Key success Entities::SSHKey
# title (required) - New deploy Key's title end
# Example Request:
# POST /projects/:id/deploy_keys
post ":id/#{path}" do post ":id/#{path}" do
attrs = attributes_for_keys [:title, :key] attrs = attributes_for_keys [:title, :key]
attrs[:key].strip! if attrs[:key]
if attrs[:key].present?
attrs[:key].strip!
# check if key already exist in project
key = user_project.deploy_keys.find_by(key: attrs[:key]) key = user_project.deploy_keys.find_by(key: attrs[:key])
if key present key, with: Entities::SSHKey if key
present key, with: Entities::SSHKey
next
end
# Check for available deploy keys in other projects # Check for available deploy keys in other projects
key = current_user.accessible_deploy_keys.find_by(key: attrs[:key]) key = current_user.accessible_deploy_keys.find_by(key: attrs[:key])
if key if key
user_project.deploy_keys << key user_project.deploy_keys << key
present key, with: Entities::SSHKey present key, with: Entities::SSHKey
next
end
end end
key = DeployKey.new attrs key = DeployKey.new attrs
@ -74,12 +68,46 @@ module API
end end
end end
# Delete existing deploy key of currently authenticated user desc 'Enable a deploy key for a project' do
# detail 'This feature was added in GitLab 8.11'
# Example Request: success Entities::SSHKey
# DELETE /projects/:id/deploy_keys/:key_id end
params do
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
end
post ":id/#{path}/:key_id/enable" do
key = ::Projects::EnableDeployKeyService.new(user_project,
current_user, declared(params)).execute
if key
present key, with: Entities::SSHKey
else
not_found!('Deploy Key')
end
end
desc 'Disable a deploy key for a project' do
detail 'This feature was added in GitLab 8.11'
success Entities::SSHKey
end
params do
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
end
delete ":id/#{path}/:key_id/disable" do
key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id])
key.destroy
present key.deploy_key, with: Entities::SSHKey
end
desc 'Delete existing deploy key of currently authenticated user' do
success Key
end
params do
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
end
delete ":id/#{path}/:key_id" do delete ":id/#{path}/:key_id" do
key = user_project.deploy_keys.find params[:key_id] key = user_project.deploy_keys.find(params[:key_id])
key.destroy key.destroy
end end
end end

View file

@ -1,38 +0,0 @@
require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
let(:project) { create(:project, creator_id: user.id) }
let!(:deploy_keys_project) { create(:deploy_keys_project, project: project) }
let(:admin) { create(:admin) }
describe 'GET /deploy_keys' do
before { admin }
context 'when unauthenticated' do
it 'should return authentication error' do
get api('/deploy_keys')
expect(response.status).to eq(401)
end
end
context 'when authenticated as non-admin user' do
it 'should return a 403 error' do
get api('/deploy_keys', user)
expect(response.status).to eq(403)
end
end
context 'when authenticated as admin' do
it 'should return all deploy keys' do
get api('/deploy_keys', admin)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.first['id']).to eq(deploy_keys_project.deploy_key.id)
end
end
end
end

View file

@ -0,0 +1,160 @@
require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let(:project) { create(:project, creator_id: user.id) }
let(:deploy_key) { create(:deploy_key, public: true) }
let!(:deploy_keys_project) do
create(:deploy_keys_project, project: project, deploy_key: deploy_key)
end
describe 'GET /deploy_keys' do
context 'when unauthenticated' do
it 'should return authentication error' do
get api('/deploy_keys')
expect(response.status).to eq(401)
end
end
context 'when authenticated as non-admin user' do
it 'should return a 403 error' do
get api('/deploy_keys', user)
expect(response.status).to eq(403)
end
end
context 'when authenticated as admin' do
it 'should return all deploy keys' do
get api('/deploy_keys', admin)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.first['id']).to eq(deploy_keys_project.deploy_key.id)
end
end
end
describe 'GET /projects/:id/deploy_keys' do
before { deploy_key }
it 'should return array of ssh keys' do
get api("/projects/#{project.id}/deploy_keys", admin)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(deploy_key.title)
end
end
describe 'GET /projects/:id/deploy_keys/:key_id' do
it 'should return a single key' do
get api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin)
expect(response).to have_http_status(200)
expect(json_response['title']).to eq(deploy_key.title)
end
it 'should return 404 Not Found with invalid ID' do
get api("/projects/#{project.id}/deploy_keys/404", admin)
expect(response).to have_http_status(404)
end
end
describe 'POST /projects/:id/deploy_keys' do
it 'should not create an invalid ssh key' do
post api("/projects/#{project.id}/deploy_keys", admin), { title: 'invalid key' }
expect(response).to have_http_status(400)
expect(json_response['message']['key']).to eq([
'can\'t be blank',
'is too short (minimum is 0 characters)',
'is invalid'
])
end
it 'should not create a key without title' do
post api("/projects/#{project.id}/deploy_keys", admin), key: 'some key'
expect(response).to have_http_status(400)
expect(json_response['message']['title']).to eq([
'can\'t be blank',
'is too short (minimum is 0 characters)'
])
end
it 'should create new ssh key' do
key_attrs = attributes_for :another_key
expect do
post api("/projects/#{project.id}/deploy_keys", admin), key_attrs
end.to change{ project.deploy_keys.count }.by(1)
end
end
describe 'DELETE /projects/:id/deploy_keys/:key_id' do
before { deploy_key }
it 'should delete existing key' do
expect do
delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin)
end.to change{ project.deploy_keys.count }.by(-1)
end
it 'should return 404 Not Found with invalid ID' do
delete api("/projects/#{project.id}/deploy_keys/404", admin)
expect(response).to have_http_status(404)
end
end
describe 'POST /projects/:id/deploy_keys/:key_id/enable' do
let(:project2) { create(:empty_project) }
context 'when the user can admin the project' do
it 'enables the key' do
expect do
post api("/projects/#{project2.id}/deploy_keys/#{deploy_key.id}/enable", admin)
end.to change { project2.deploy_keys.count }.from(0).to(1)
expect(response).to have_http_status(201)
expect(json_response['id']).to eq(deploy_key.id)
end
end
context 'when authenticated as non-admin user' do
it 'should return a 404 error' do
post api("/projects/#{project2.id}/deploy_keys/#{deploy_key.id}/enable", user)
expect(response).to have_http_status(404)
end
end
end
describe 'DELETE /projects/:id/deploy_keys/:key_id/disable' do
context 'when the user can admin the project' do
it 'disables the key' do
expect do
delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}/disable", admin)
end.to change { project.deploy_keys.count }.from(1).to(0)
expect(response).to have_http_status(200)
expect(json_response['id']).to eq(deploy_key.id)
end
end
context 'when authenticated as non-admin user' do
it 'should return a 404 error' do
delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}/disable", user)
expect(response).to have_http_status(404)
end
end
end
end

View file

@ -642,78 +642,6 @@ describe API::API, api: true do
end end
end end
describe :deploy_keys do
let(:deploy_keys_project) { create(:deploy_keys_project, project: project) }
let(:deploy_key) { deploy_keys_project.deploy_key }
describe 'GET /projects/:id/deploy_keys' do
before { deploy_key }
it 'should return array of ssh keys' do
get api("/projects/#{project.id}/deploy_keys", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(deploy_key.title)
end
end
describe 'GET /projects/:id/deploy_keys/:key_id' do
it 'should return a single key' do
get api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", user)
expect(response).to have_http_status(200)
expect(json_response['title']).to eq(deploy_key.title)
end
it 'should return 404 Not Found with invalid ID' do
get api("/projects/#{project.id}/deploy_keys/404", user)
expect(response).to have_http_status(404)
end
end
describe 'POST /projects/:id/deploy_keys' do
it 'should not create an invalid ssh key' do
post api("/projects/#{project.id}/deploy_keys", user), { title: 'invalid key' }
expect(response).to have_http_status(400)
expect(json_response['message']['key']).to eq([
'can\'t be blank',
'is too short (minimum is 0 characters)',
'is invalid'
])
end
it 'should not create a key without title' do
post api("/projects/#{project.id}/deploy_keys", user), key: 'some key'
expect(response).to have_http_status(400)
expect(json_response['message']['title']).to eq([
'can\'t be blank',
'is too short (minimum is 0 characters)'
])
end
it 'should create new ssh key' do
key_attrs = attributes_for :key
expect do
post api("/projects/#{project.id}/deploy_keys", user), key_attrs
end.to change{ project.deploy_keys.count }.by(1)
end
end
describe 'DELETE /projects/:id/deploy_keys/:key_id' do
before { deploy_key }
it 'should delete existing key' do
expect do
delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", user)
end.to change{ project.deploy_keys.count }.by(-1)
end
it 'should return 404 Not Found with invalid ID' do
delete api("/projects/#{project.id}/deploy_keys/404", user)
expect(response).to have_http_status(404)
end
end
end
describe :fork_admin do describe :fork_admin do
let(:project_fork_target) { create(:project) } let(:project_fork_target) { create(:project) }
let(:project_fork_source) { create(:project, :public) } let(:project_fork_source) { create(:project, :public) }

View file

@ -0,0 +1,27 @@
require 'spec_helper'
describe Projects::EnableDeployKeyService, services: true do
let(:deploy_key) { create(:deploy_key, public: true) }
let(:project) { create(:empty_project) }
let(:user) { project.creator}
let!(:params) { { key_id: deploy_key.id } }
it 'enables the key' do
expect do
service.execute
end.to change { project.deploy_keys.count }.from(0).to(1)
end
context 'trying to add an unaccessable key' do
let(:another_key) { create(:another_key) }
let!(:params) { { key_id: another_key.id } }
it 'returns nil if the key cannot be added' do
expect(service.execute).to be nil
end
end
def service
Projects::EnableDeployKeyService.new(project, user, params)
end
end