diff options
23 files changed, 386 insertions, 46 deletions
diff --git a/CHANGELOG b/CHANGELOG index 35d794d8bd2..cdf298caf5d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -77,6 +77,8 @@ v 7.10.0 (unreleased) v 7.9.3 - Contains no changes - Add icons to Add dropdown items. + - Allow admin to create public deploy keys that are accessible to any project. + v 7.9.2 - Contains no changes diff --git a/app/controllers/admin/deploy_keys_controller.rb b/app/controllers/admin/deploy_keys_controller.rb new file mode 100644 index 00000000000..e93603bef36 --- /dev/null +++ b/app/controllers/admin/deploy_keys_controller.rb @@ -0,0 +1,49 @@ +class Admin::DeployKeysController < Admin::ApplicationController + before_filter :deploy_keys, only: [:index] + before_filter :deploy_key, only: [:show, :destroy] + + def index + + end + + def show + + end + + def new + @deploy_key = deploy_keys.new + end + + def create + @deploy_key = deploy_keys.new(deploy_key_params) + + if @deploy_key.save + redirect_to admin_deploy_keys_path + else + render "new" + end + end + + def destroy + deploy_key.destroy + + respond_to do |format| + format.html { redirect_to admin_deploy_keys_path } + format.json { head :ok } + end + end + + protected + + def deploy_key + @deploy_key ||= deploy_keys.find(params[:id]) + end + + def deploy_keys + @deploy_keys ||= DeployKey.are_public + end + + def deploy_key_params + params.require(:deploy_key).permit(:key, :title) + end +end diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index 679a5d76ec0..6fba3ce299b 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -8,7 +8,14 @@ class Projects::DeployKeysController < Projects::ApplicationController def index @enabled_keys = @project.deploy_keys - @available_keys = available_keys - @enabled_keys + + @available_keys = accessible_keys - @enabled_keys + @available_project_keys = current_user.project_deploy_keys - @enabled_keys + @available_public_keys = DeployKey.are_public - @enabled_keys + + # Public keys that are already used by another accessible project are already + # in @available_project_keys. + @available_public_keys -= @available_project_keys end def show @@ -32,18 +39,9 @@ class Projects::DeployKeysController < Projects::ApplicationController end end - def destroy - @key = @project.deploy_keys.find(params[:id]) - @key.destroy - - respond_to do |format| - format.html { redirect_to namespace_project_deploy_keys_path(@project.namespace, @project) } - format.js { render nothing: true } - end - end - def enable - @project.deploy_keys << available_keys.find(params[:id]) + @key = accessible_keys.find(params[:id]) + @project.deploy_keys << @key redirect_to namespace_project_deploy_keys_path(@project.namespace, @project) @@ -52,14 +50,13 @@ class Projects::DeployKeysController < Projects::ApplicationController def disable @project.deploy_keys_projects.find_by(deploy_key_id: params[:id]).destroy - redirect_to namespace_project_deploy_keys_path(@project.namespace, - @project) + redirect_to :back end protected - def available_keys - @available_keys ||= current_user.accessible_deploy_keys + def accessible_keys + @accessible_keys ||= current_user.accessible_deploy_keys end def deploy_key_params diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index e3734023be3..ebbd2bfd77d 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -127,6 +127,14 @@ module ProjectsHelper html + count_html end + def project_for_deploy_key(deploy_key) + if deploy_key.projects.include?(@project) + @project + else + deploy_key.projects.find { |project| can?(current_user, :read_project, project) } + end + end + private def get_project_nav_tabs(project, current_user) diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb index 570f5e91c13..85d52d558cd 100644 --- a/app/models/deploy_key.rb +++ b/app/models/deploy_key.rb @@ -7,6 +7,7 @@ # created_at :datetime # updated_at :datetime # key :text +# public :boolean default(FALSE) # title :string(255) # type :string(255) # fingerprint :string(255) @@ -17,4 +18,21 @@ class DeployKey < Key has_many :projects, through: :deploy_keys_projects scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) } + scope :are_public, -> { where(public: true) } + + def private? + !public? + end + + def orphaned? + self.deploy_keys_projects.length == 0 + end + + def almost_orphaned? + self.deploy_keys_projects.length == 1 + end + + def destroyed_when_orphaned? + self.private? + end end diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb index 7e88903b9af..18db521741f 100644 --- a/app/models/deploy_keys_project.rb +++ b/app/models/deploy_keys_project.rb @@ -22,6 +22,8 @@ class DeployKeysProject < ActiveRecord::Base private def destroy_orphaned_deploy_key - self.deploy_key.destroy if self.deploy_key.deploy_keys_projects.length == 0 + return unless self.deploy_key.destroyed_when_orphaned? && self.deploy_key.orphaned? + + self.deploy_key.destroy end end diff --git a/app/models/user.rb b/app/models/user.rb index e2b6757bc4d..a40111e62dd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -414,8 +414,16 @@ class User < ActiveRecord::Base @ldap_identity ||= identities.find_by(["provider LIKE ?", "ldap%"]) end + def project_deploy_keys + DeployKey.in_projects(self.authorized_projects.pluck(:id)) + end + def accessible_deploy_keys - DeployKey.in_projects(self.authorized_projects.pluck(:id)).uniq + @accessible_deploy_keys ||= begin + key_ids = project_deploy_keys.pluck(:id) + key_ids.push(*DeployKey.are_public.pluck(:id)) + DeployKey.where(id: key_ids) + end end def created_by diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml new file mode 100644 index 00000000000..2ae83ab95f7 --- /dev/null +++ b/app/views/admin/deploy_keys/index.html.haml @@ -0,0 +1,27 @@ +.panel.panel-default + .panel-heading + Public deploy keys (#{@deploy_keys.count}) + .panel-head-actions + = link_to 'New Deploy Key', new_admin_deploy_key_path, class: "btn btn-new btn-sm" + - if @deploy_keys.any? + %table.table + %thead.panel-heading + %tr + %th Title + %th Fingerprint + %th Added at + %th + %tbody + - @deploy_keys.each do |deploy_key| + %tr + %td + = link_to admin_deploy_key_path(deploy_key) do + %strong= deploy_key.title + %td + %span + (#{deploy_key.fingerprint}) + %td + %span.cgray + added #{time_ago_with_tooltip(deploy_key.created_at)} + %td + = link_to 'Remove', admin_deploy_key_path(deploy_key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-sm btn-remove delete-key pull-right" diff --git a/app/views/admin/deploy_keys/new.html.haml b/app/views/admin/deploy_keys/new.html.haml new file mode 100644 index 00000000000..c00049424c5 --- /dev/null +++ b/app/views/admin/deploy_keys/new.html.haml @@ -0,0 +1,26 @@ +%h3.page-title New public deploy key +%hr + +%div + = form_for [:admin, @deploy_key], html: { class: 'deploy-key-form form-horizontal' } do |f| + -if @deploy_key.errors.any? + .alert.alert-danger + %ul + - @deploy_key.errors.full_messages.each do |msg| + %li= msg + + .form-group + = f.label :title, class: "control-label" + .col-sm-10= f.text_field :title, class: 'form-control' + .form-group + = f.label :key, class: "control-label" + .col-sm-10 + %p.light + Paste a machine public key here. Read more about how to generate it + = link_to "here", help_page_path("ssh", "README") + = f.text_area :key, class: "form-control thin_area", rows: 5 + + .form-actions + = f.submit 'Create', class: "btn-create btn" + = link_to "Cancel", admin_deploy_keys_path, class: "btn btn-cancel" + diff --git a/app/views/admin/deploy_keys/show.html.haml b/app/views/admin/deploy_keys/show.html.haml new file mode 100644 index 00000000000..cfa2adf92ee --- /dev/null +++ b/app/views/admin/deploy_keys/show.html.haml @@ -0,0 +1,34 @@ +.row + .col-md-4 + .panel.panel-default + .panel-heading + Deploy Key + %ul.well-list + %li + %span.light Title: + %strong= @deploy_key.title + %li + %span.light Created on: + %strong= @deploy_key.created_at.stamp("Aug 21, 2011") + + .panel.panel-default + .panel-heading Projects (#{@deploy_key.deploy_keys_projects.count}) + - if @deploy_key.deploy_keys_projects.any? + %ul.well-list + - @deploy_key.projects.each do |project| + %li + %span + %strong + = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project] + .pull-right + = link_to disable_namespace_project_deploy_key_path(project.namespace, project, @deploy_key), data: { confirm: "Are you sure?" }, method: :put, class: "btn-xs btn btn-remove", title: 'Remove deploy key from project' do + %i.fa.fa-times.fa-inverse + + .col-md-8 + %p + %span.light Fingerprint: + %strong= @deploy_key.fingerprint + %pre.well-pre + = @deploy_key.key + .pull-right + = link_to 'Remove', admin_deploy_key_path(@deploy_key), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key" diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml index 2f38d596c65..34efceb37d1 100644 --- a/app/views/layouts/nav/_admin.html.haml +++ b/app/views/layouts/nav/_admin.html.haml @@ -19,6 +19,11 @@ %i.fa.fa-group %span Groups + = nav_link(controller: :deploy_keys) do + = link_to admin_deploy_keys_path, title: 'Deploy Keys' do + %i.fa.fa-key + %span + Deploy Keys = nav_link(controller: :logs) do = link_to admin_logs_path, title: 'Logs' do %i.fa.fa-file-text diff --git a/app/views/projects/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml index a2faa9d5e25..c577dfa8d55 100644 --- a/app/views/projects/deploy_keys/_deploy_key.html.haml +++ b/app/views/projects/deploy_keys/_deploy_key.html.haml @@ -5,21 +5,32 @@ %i.fa.fa-plus Enable - else - - if deploy_key.projects.count > 1 + - if deploy_key.destroyed_when_orphaned? && deploy_key.almost_orphaned? + = link_to 'Remove', disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), data: { confirm: 'You are going to remove deploy key. Are you sure?'}, method: :put, class: "btn btn-remove delete-key btn-sm pull-right" + - else = link_to disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do %i.fa.fa-power-off Disable - - else - = link_to 'Remove', namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), data: { confirm: 'You are going to remove deploy key. Are you sure?'}, method: :delete, class: "btn btn-remove delete-key btn-sm pull-right" - - - key_project = deploy_key.projects.include?(@project) ? @project : deploy_key.projects.first - = link_to namespace_project_deploy_key_path(key_project.namespace, key_project, deploy_key) do + - if project = project_for_deploy_key(deploy_key) + = link_to namespace_project_deploy_key_path(project.namespace, project, deploy_key) do + %i.fa.fa-key + %strong= deploy_key.title + - else %i.fa.fa-key %strong= deploy_key.title + %p.light.prepend-top-10 - - deploy_key.projects.map(&:name_with_namespace).each do |project_name| - %span.label.label-gray.deploy-project-label= project_name + - if deploy_key.public? + %span.label.label-info.deploy-project-label + Public deploy key + + - deploy_key.projects.each do |project| + - if can?(current_user, :read_project, project) + %span.label.label-gray.deploy-project-label + = link_to namespace_project_path(project.namespace, project) do + = project.name_with_namespace + %small.pull-right Created #{time_ago_with_tooltip(deploy_key.created_at)} diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml index c02a18146eb..472a13a8524 100644 --- a/app/views/projects/deploy_keys/index.html.haml +++ b/app/views/projects/deploy_keys/index.html.haml @@ -22,11 +22,20 @@ .light-well .nothing-here-block Create a #{link_to 'new deploy key', new_namespace_project_deploy_key_path(@project.namespace, @project)} or add an existing one .col-md-6.available-keys - %h5 - %strong Deploy keys - from projects available to you - %ul.bordered-list - = render @available_keys - - if @available_keys.blank? - .light-well - .nothing-here-block Deploy keys from projects you have access to will be displayed here + - # If there are available public deploy keys but no available project deploy keys, only public deploy keys are shown. + - if @available_project_keys.any? || @available_public_keys.blank? + %h5 + %strong Deploy keys + from projects you have access to + %ul.bordered-list + = render @available_project_keys + - if @available_project_keys.blank? + .light-well + .nothing-here-block Deploy keys from projects you have access to will be displayed here + + - if @available_public_keys.any? + %h5 + %strong Public deploy keys + available to any project + %ul.bordered-list + = render @available_public_keys diff --git a/config/routes.rb b/config/routes.rb index c1b85b025b5..de21f418329 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -145,6 +145,8 @@ Gitlab::Application.routes.draw do end end + resources :deploy_keys, only: [:index, :show, :new, :create, :destroy] + resources :hooks, only: [:index, :create, :destroy] do get :test end @@ -393,7 +395,7 @@ Gitlab::Application.routes.draw do end end - resources :deploy_keys, constraints: { id: /\d+/ } do + resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :show, :new, :create] do member do put :enable put :disable diff --git a/db/migrate/20150327122227_add_public_to_key.rb b/db/migrate/20150327122227_add_public_to_key.rb new file mode 100644 index 00000000000..6ffbf4cda19 --- /dev/null +++ b/db/migrate/20150327122227_add_public_to_key.rb @@ -0,0 +1,5 @@ +class AddPublicToKey < ActiveRecord::Migration + def change + add_column :keys, :public, :boolean, default: false, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 3f481b67fd2..2974d3754f3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -132,6 +132,7 @@ ActiveRecord::Schema.define(version: 20150411180045) do t.string "title" t.string "type" t.string "fingerprint" + t.boolean "public", default: false, null: false end add_index "keys", ["created_at", "id"], name: "index_keys_on_created_at_and_id", using: :btree diff --git a/features/admin/deploy_keys.feature b/features/admin/deploy_keys.feature new file mode 100644 index 00000000000..9df47eb51fd --- /dev/null +++ b/features/admin/deploy_keys.feature @@ -0,0 +1,21 @@ +@admin +Feature: Admin Deploy Keys + Background: + Given I sign in as an admin + And there are public deploy keys in system + + Scenario: Deploy Keys list + When I visit admin deploy keys page + Then I should see all public deploy keys + + Scenario: Deploy Keys show + When I visit admin deploy keys page + And I click on first deploy key + Then I should see deploy key details + + Scenario: Deploy Keys new + When I visit admin deploy keys page + And I click 'New Deploy Key' + And I submit new deploy key + Then I should be on admin deploy keys page + And I should see newly created deploy key diff --git a/features/project/deploy_keys.feature b/features/project/deploy_keys.feature index 13e3b9bbd2e..a71f6124d9c 100644 --- a/features/project/deploy_keys.feature +++ b/features/project/deploy_keys.feature @@ -6,7 +6,17 @@ Feature: Project Deploy Keys Scenario: I should see deploy keys list Given project has deploy key When I visit project deploy keys page - Then I should see project deploy keys + Then I should see project deploy key + + Scenario: I should see project deploy keys + Given other project has deploy key + When I visit project deploy keys page + Then I should see other project deploy key + + Scenario: I should see public deploy keys + Given public deploy key exists + When I visit project deploy keys page + Then I should see public deploy key Scenario: I add new deploy key Given I visit project deploy keys page @@ -15,9 +25,16 @@ Feature: Project Deploy Keys Then I should be on deploy keys page And I should see newly created deploy key - Scenario: I attach deploy key to project + Scenario: I attach other project deploy key to project Given other project has deploy key And I visit project deploy keys page When I click attach deploy key Then I should be on deploy keys page And I should see newly created deploy key + + Scenario: I attach public deploy key to project + Given public deploy key exists + And I visit project deploy keys page + When I click attach deploy key + Then I should be on deploy keys page + And I should see newly created deploy key diff --git a/features/steps/admin/deploy_keys.rb b/features/steps/admin/deploy_keys.rb new file mode 100644 index 00000000000..fb0b611762e --- /dev/null +++ b/features/steps/admin/deploy_keys.rb @@ -0,0 +1,57 @@ +class Spinach::Features::AdminDeployKeys < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedAdmin + + step 'there are public deploy keys in system' do + create(:deploy_key, public: true) + create(:another_deploy_key, public: true) + end + + step 'I should see all public deploy keys' do + DeployKey.are_public.each do |p| + page.should have_content p.title + end + end + + step 'I click on first deploy key' do + click_link DeployKey.are_public.first.title + end + + step 'I should see deploy key details' do + deploy_key = DeployKey.are_public.first + current_path.should == admin_deploy_key_path(deploy_key) + page.should have_content(deploy_key.title) + page.should have_content(deploy_key.key) + end + + step 'I visit admin deploy key page' do + visit admin_deploy_key_path(deploy_key) + end + + step 'I visit admin deploy keys page' do + visit admin_deploy_keys_path + end + + step 'I click \'New Deploy Key\'' do + click_link 'New Deploy Key' + end + + step 'I submit new deploy key' do + fill_in "deploy_key_title", with: "laptop" + fill_in "deploy_key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop" + click_button "Create" + end + + step 'I should be on admin deploy keys page' do + current_path.should == admin_deploy_keys_path + end + + step 'I should see newly created deploy key' do + page.should have_content(deploy_key.title) + end + + def deploy_key + @deploy_key ||= DeployKey.are_public.first + end +end diff --git a/features/steps/project/deploy_keys.rb b/features/steps/project/deploy_keys.rb index 4bf5cb5fa40..50e14513a7a 100644 --- a/features/steps/project/deploy_keys.rb +++ b/features/steps/project/deploy_keys.rb @@ -7,12 +7,24 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps create(:deploy_keys_project, project: @project) end - step 'I should see project deploy keys' do + step 'I should see project deploy key' do within '.enabled-keys' do page.should have_content deploy_key.title end end + step 'I should see other project deploy key' do + within '.available-keys' do + page.should have_content other_deploy_key.title + end + end + + step 'I should see public deploy key' do + within '.available-keys' do + page.should have_content public_deploy_key.title + end + end + step 'I click \'New Deploy Key\'' do click_link 'New Deploy Key' end @@ -39,6 +51,10 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps create(:deploy_keys_project, project: @second_project) end + step 'public deploy key exists' do + create(:deploy_key, public: true) + end + step 'I click attach deploy key' do within '.available-keys' do click_link 'Enable' @@ -50,4 +66,12 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps def deploy_key @project.deploy_keys.last end + + def other_deploy_key + @second_project.deploy_keys.last + end + + def public_deploy_key + DeployKey.are_public.last + end end diff --git a/spec/factories.rb b/spec/factories.rb index fc103e5b133..b9bfd3cebb4 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -111,6 +111,9 @@ FactoryGirl.define do key do "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmTillFzNTrrGgwaCKaSj+QCz81E6jBc/s9av0+3b1Hwfxgkqjl4nAK/OD2NjgyrONDTDfR8cRN4eAAy6nY8GLkOyYBDyuc5nTMqs5z3yVuTwf3koGm/YQQCmo91psZ2BgDFTor8SVEE5Mm1D1k3JDMhDFxzzrOtRYFPci9lskTJaBjpqWZ4E9rDTD2q/QZntCqbC3wE9uSemRQB5f8kik7vD/AD8VQXuzKladrZKkzkONCPWsXDspUitjM8HkQdOf0PsYn1CMUC1xKYbCxkg5TkEosIwGv6CoEArUrdu/4+10LVslq494mAvEItywzrluCLCnwELfW+h/m8UHoVhZ" end + + factory :another_deploy_key, class: 'DeployKey' do + end end factory :invalid_key do diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb index f351aab9238..7032b777144 100644 --- a/spec/models/deploy_keys_project_spec.rb +++ b/spec/models/deploy_keys_project_spec.rb @@ -28,17 +28,32 @@ describe DeployKeysProject do let(:deploy_key) { subject.deploy_key } context "when the deploy key is only used by this project" do - it "destroys the deploy key" do - subject.destroy + context "when the deploy key is public" do + before do + deploy_key.update_attribute(:public, true) + end - expect { - deploy_key.reload - }.to raise_error(ActiveRecord::RecordNotFound) + it "doesn't destroy the deploy key" do + subject.destroy + + expect { + deploy_key.reload + }.not_to raise_error(ActiveRecord::RecordNotFound) + end + end + + context "when the deploy key is private" do + it "destroys the deploy key" do + subject.destroy + + expect { + deploy_key.reload + }.to raise_error(ActiveRecord::RecordNotFound) + end end end context "when the deploy key is used by more than one project" do - let!(:other_project) { create(:project) } before do diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index d9bd91f5990..042352311da 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -168,12 +168,11 @@ end # project_deploy_keys GET /:project_id/deploy_keys(.:format) deploy_keys#index # POST /:project_id/deploy_keys(.:format) deploy_keys#create # new_project_deploy_key GET /:project_id/deploy_keys/new(.:format) deploy_keys#new -# edit_project_deploy_key GET /:project_id/deploy_keys/:id/edit(.:format) deploy_keys#edit # project_deploy_key GET /:project_id/deploy_keys/:id(.:format) deploy_keys#show -# PUT /:project_id/deploy_keys/:id(.:format) deploy_keys#update # DELETE /:project_id/deploy_keys/:id(.:format) deploy_keys#destroy describe Projects::DeployKeysController, 'routing' do it_behaves_like 'RESTful project resources' do + let(:actions) { [:index, :show, :new, :create] } let(:controller) { 'deploy_keys' } end end |