summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorick Peterse <yorickpeterse@gmail.com>2019-01-24 12:51:13 +0000
committerYorick Peterse <yorickpeterse@gmail.com>2019-01-24 12:51:16 +0000
commitab3e9447602afba0fa9b701de384a5e14cf6e90e (patch)
tree15cea6fd25004b27fc0f4ba133ac5de3fbd4ff47
parent0b7db931af324b7bf1fe4faab821268c29bb7118 (diff)
downloadgitlab-ce-ab3e9447602afba0fa9b701de384a5e14cf6e90e.tar.gz
Merge branch 'security-pipeline-trigger-tokens-exposure-11-5' into 'security-11-5'
[11.5] Do not expose trigger token when user should not see it See merge request gitlab/gitlabhq!2760 (cherry picked from commit 138126043d62c57b4fb1e057561b433347b36d03) bd70c84e Do not expose trigger token when user should not see it
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb2
-rw-r--r--app/controllers/projects/triggers_controller.rb7
-rw-r--r--app/models/ci/trigger.rb3
-rw-r--r--app/presenters/ci/trigger_presenter.rb19
-rw-r--r--app/views/projects/triggers/_trigger.html.haml2
-rw-r--r--changelogs/unreleased/security-pipeline-trigger-tokens-exposure.yml5
-rw-r--r--lib/api/entities.rb5
-rw-r--r--lib/api/helpers/presentable.rb29
-rw-r--r--lib/api/triggers.rb10
-rw-r--r--spec/presenters/ci/trigger_presenter_spec.rb51
-rw-r--r--spec/requests/api/triggers_spec.rb14
11 files changed, 130 insertions, 17 deletions
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index 75e590f3f33..f2f63e986bb 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -99,7 +99,9 @@ module Projects
def define_triggers_variables
@triggers = @project.triggers
+ .present(current_user: current_user)
@trigger = ::Ci::Trigger.new
+ .present(current_user: current_user)
end
def define_badges_variables
diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb
index f5fdfb8accc..c7b4ebb2b24 100644
--- a/app/controllers/projects/triggers_controller.rb
+++ b/app/controllers/projects/triggers_controller.rb
@@ -66,12 +66,11 @@ class Projects::TriggersController < Projects::ApplicationController
end
def trigger
- @trigger ||= project.triggers.find(params[:id]) || render_404
+ @trigger ||= project.triggers.find(params[:id])
+ .present(current_user: current_user)
end
def trigger_params
- params.require(:trigger).permit(
- :description
- )
+ params.require(:trigger).permit(:description)
end
end
diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb
index 55db42162ca..637148c4ce4 100644
--- a/app/models/ci/trigger.rb
+++ b/app/models/ci/trigger.rb
@@ -4,6 +4,7 @@ module Ci
class Trigger < ActiveRecord::Base
extend Gitlab::Ci::Model
include IgnorableColumn
+ include Presentable
ignore_column :deleted_at
@@ -29,7 +30,7 @@ module Ci
end
def short_token
- token[0...4]
+ token[0...4] if token.present?
end
def legacy?
diff --git a/app/presenters/ci/trigger_presenter.rb b/app/presenters/ci/trigger_presenter.rb
new file mode 100644
index 00000000000..605c8f328a4
--- /dev/null
+++ b/app/presenters/ci/trigger_presenter.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Ci
+ class TriggerPresenter < Gitlab::View::Presenter::Delegated
+ presents :trigger
+
+ def has_token_exposed?
+ can?(current_user, :admin_trigger, trigger)
+ end
+
+ def token
+ if has_token_exposed?
+ trigger.token
+ else
+ trigger.short_token
+ end
+ end
+ end
+end
diff --git a/app/views/projects/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml
index 7e4618e1a88..6f6f1e5e0c5 100644
--- a/app/views/projects/triggers/_trigger.html.haml
+++ b/app/views/projects/triggers/_trigger.html.haml
@@ -1,6 +1,6 @@
%tr
%td
- - if can?(current_user, :admin_trigger, trigger)
+ - if trigger.has_token_exposed?
%span= trigger.token
= clipboard_button(text: trigger.token, title: "Copy trigger token to clipboard")
- else
diff --git a/changelogs/unreleased/security-pipeline-trigger-tokens-exposure.yml b/changelogs/unreleased/security-pipeline-trigger-tokens-exposure.yml
new file mode 100644
index 00000000000..97d743eead1
--- /dev/null
+++ b/changelogs/unreleased/security-pipeline-trigger-tokens-exposure.yml
@@ -0,0 +1,5 @@
+---
+title: Expose CI/CD trigger token only to the trigger owner
+merge_request:
+author:
+type: security
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index d8bbcb4accd..018223cdd7f 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -1175,8 +1175,11 @@ module API
end
class Trigger < Grape::Entity
+ include ::API::Helpers::Presentable
+
expose :id
- expose :token, :description
+ expose :token
+ expose :description
expose :created_at, :updated_at, :last_used
expose :owner, using: Entities::UserBasic
end
diff --git a/lib/api/helpers/presentable.rb b/lib/api/helpers/presentable.rb
new file mode 100644
index 00000000000..973c2132efe
--- /dev/null
+++ b/lib/api/helpers/presentable.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ ##
+ # This module makes it possible to use `app/presenters` with
+ # Grape Entities. It instantiates model presenter and passes
+ # options defined in the API endpoint to the presenter itself.
+ #
+ # present object, with: Entities::Something,
+ # current_user: current_user,
+ # another_option: 'my options'
+ #
+ # Example above will make `current_user` and `another_option`
+ # values available in the subclass of `Gitlab::View::Presenter`
+ # thorough a separate method in the presenter.
+ #
+ # The model class needs to have `::Presentable` module mixed in
+ # if you want to use `API::Helpers::Presentable`.
+ #
+ module Presentable
+ extend ActiveSupport::Concern
+
+ def initialize(object, options = {})
+ super(object.present(options), options)
+ end
+ end
+ end
+end
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index f784c857883..f3a0d7b7b43 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -51,7 +51,7 @@ module API
triggers = user_project.triggers.includes(:trigger_requests)
- present paginate(triggers), with: Entities::Trigger
+ present paginate(triggers), with: Entities::Trigger, current_user: current_user
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -68,7 +68,7 @@ module API
trigger = user_project.triggers.find(params.delete(:trigger_id))
break not_found!('Trigger') unless trigger
- present trigger, with: Entities::Trigger
+ present trigger, with: Entities::Trigger, current_user: current_user
end
desc 'Create a trigger' do
@@ -85,7 +85,7 @@ module API
declared_params(include_missing: false).merge(owner: current_user))
if trigger.valid?
- present trigger, with: Entities::Trigger
+ present trigger, with: Entities::Trigger, current_user: current_user
else
render_validation_error!(trigger)
end
@@ -106,7 +106,7 @@ module API
break not_found!('Trigger') unless trigger
if trigger.update(declared_params(include_missing: false))
- present trigger, with: Entities::Trigger
+ present trigger, with: Entities::Trigger, current_user: current_user
else
render_validation_error!(trigger)
end
@@ -127,7 +127,7 @@ module API
if trigger.update(owner: current_user)
status :ok
- present trigger, with: Entities::Trigger
+ present trigger, with: Entities::Trigger, current_user: current_user
else
render_validation_error!(trigger)
end
diff --git a/spec/presenters/ci/trigger_presenter_spec.rb b/spec/presenters/ci/trigger_presenter_spec.rb
new file mode 100644
index 00000000000..231b539c188
--- /dev/null
+++ b/spec/presenters/ci/trigger_presenter_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe Ci::TriggerPresenter do
+ set(:user) { create(:user) }
+ set(:project) { create(:project) }
+
+ set(:trigger) do
+ create(:ci_trigger, token: '123456789abcd', project: project)
+ end
+
+ subject do
+ described_class.new(trigger, current_user: user)
+ end
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ context 'when user is not a trigger owner' do
+ describe '#token' do
+ it 'exposes only short token' do
+ expect(subject.token).not_to eq trigger.token
+ expect(subject.token).to eq '1234'
+ end
+ end
+
+ describe '#has_token_exposed?' do
+ it 'does not have token exposed' do
+ expect(subject).not_to have_token_exposed
+ end
+ end
+ end
+
+ context 'when user is a trigger owner and builds admin' do
+ before do
+ trigger.update(owner: user)
+ end
+
+ describe '#token' do
+ it 'exposes full token' do
+ expect(subject.token).to eq trigger.token
+ end
+ end
+
+ describe '#has_token_exposed?' do
+ it 'has token exposed' do
+ expect(subject).to have_token_exposed
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index 0ae6796d1e4..e7ab1a2a097 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -1,8 +1,9 @@
require 'spec_helper'
describe API::Triggers do
- let(:user) { create(:user) }
- let(:user2) { create(:user) }
+ set(:user) { create(:user) }
+ set(:user2) { create(:user) }
+
let!(:trigger_token) { 'secure_token' }
let!(:trigger_token_2) { 'secure_token_2' }
let!(:project) { create(:project, :repository, creator: user) }
@@ -132,14 +133,17 @@ describe API::Triggers do
end
describe 'GET /projects/:id/triggers' do
- context 'authenticated user with valid permissions' do
- it 'returns list of triggers' do
+ context 'authenticated user who can access triggers' do
+ it 'returns a list of triggers with tokens exposed correctly' do
get api("/projects/#{project.id}/triggers", user)
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
+
expect(json_response).to be_a(Array)
- expect(json_response[0]).to have_key('token')
+ expect(json_response.size).to eq 2
+ expect(json_response.dig(0, 'token')).to eq trigger_token
+ expect(json_response.dig(1, 'token')).to eq trigger_token_2[0..3]
end
end