From 2cf685c450189101995536c882aa650075618d0b Mon Sep 17 00:00:00 2001 From: Konstantin Haase Date: Mon, 23 Feb 2015 11:19:25 +0100 Subject: [PATCH 1/8] v3: add api endpoints for enabling/disabling a repo --- lib/travis/api/v3/routes.rb | 3 +++ lib/travis/api/v3/services/repository/disable.rb | 7 +++++++ lib/travis/api/v3/services/repository/enable.rb | 7 +++++++ spec/v3/service_index_spec.rb | 4 +++- 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 lib/travis/api/v3/services/repository/disable.rb create mode 100644 lib/travis/api/v3/services/repository/enable.rb diff --git a/lib/travis/api/v3/routes.rb b/lib/travis/api/v3/routes.rb index e911ac78..83343ad8 100644 --- a/lib/travis/api/v3/routes.rb +++ b/lib/travis/api/v3/routes.rb @@ -7,6 +7,9 @@ module Travis::API::V3 route '/repo/{repository.id}' get :find + post :enable, '/enable' + post :disable, '/disable' + resource :requests do route '/requests' get :find diff --git a/lib/travis/api/v3/services/repository/disable.rb b/lib/travis/api/v3/services/repository/disable.rb new file mode 100644 index 00000000..9e5103ac --- /dev/null +++ b/lib/travis/api/v3/services/repository/disable.rb @@ -0,0 +1,7 @@ +module Travis::API::V3 + class Services::Repository::Disable < Service + def run!(activate = false) + not_implemented + end + end +end diff --git a/lib/travis/api/v3/services/repository/enable.rb b/lib/travis/api/v3/services/repository/enable.rb new file mode 100644 index 00000000..5d85439b --- /dev/null +++ b/lib/travis/api/v3/services/repository/enable.rb @@ -0,0 +1,7 @@ +module Travis::API::V3 + class Services::Repository::Enable < Services::Repository::Disable + def run! + super(true) + end + end +end diff --git a/spec/v3/service_index_spec.rb b/spec/v3/service_index_spec.rb index ce53c2e4..325392e0 100644 --- a/spec/v3/service_index_spec.rb +++ b/spec/v3/service_index_spec.rb @@ -9,7 +9,9 @@ describe Travis::API::V3::ServiceIndex do describe "custom json entry point" do let(:expected_resources) {{ "repository" => { - "find" => [{"request-method"=>"GET", "uri-template"=>"#{path}repo/{repository.id}"}] }, + "find" => [{"request-method"=>"GET", "uri-template"=>"#{path}repo/{repository.id}"}], + "enable" => [{"request-method"=>"POST", "uri-template"=>"#{path}repo/{repository.id}/enable"}], + "disable" => [{"request-method"=>"POST", "uri-template"=>"#{path}repo/{repository.id}/disable"}] }, "repositories" => { "for_current_user" => [{"request-method"=>"GET", "uri-template"=>"#{path}repos"}] }, "build" => { From de5908dd43e53b4c094a9adb641fd36fb324d595 Mon Sep 17 00:00:00 2001 From: Konstantin Haase Date: Fri, 6 Mar 2015 15:35:47 +0100 Subject: [PATCH 2/8] v3: implement enable/disable --- lib/travis/api/v3/access_control/anonymous.rb | 4 + .../api/v3/access_control/application.rb | 10 +- lib/travis/api/v3/access_control/generic.rb | 4 + lib/travis/api/v3/access_control/user.rb | 4 + .../api/v3/extensions/encrypted_column.rb | 109 ++++++++++++++++++ lib/travis/api/v3/github.rb | 43 +++++++ lib/travis/api/v3/models/token.rb | 14 +++ lib/travis/api/v3/models/user.rb | 9 +- lib/travis/api/v3/service.rb | 5 + .../api/v3/services/repository/disable.rb | 9 +- 10 files changed, 207 insertions(+), 4 deletions(-) create mode 100644 lib/travis/api/v3/extensions/encrypted_column.rb create mode 100644 lib/travis/api/v3/github.rb create mode 100644 lib/travis/api/v3/models/token.rb diff --git a/lib/travis/api/v3/access_control/anonymous.rb b/lib/travis/api/v3/access_control/anonymous.rb index 98175a80..f2229bc5 100644 --- a/lib/travis/api/v3/access_control/anonymous.rb +++ b/lib/travis/api/v3/access_control/anonymous.rb @@ -8,5 +8,9 @@ module Travis::API::V3 def self.for_request(*) new end + + def admin_for(repository) + raise LoginRequired + end end end diff --git a/lib/travis/api/v3/access_control/application.rb b/lib/travis/api/v3/access_control/application.rb index 80e26d53..36102170 100644 --- a/lib/travis/api/v3/access_control/application.rb +++ b/lib/travis/api/v3/access_control/application.rb @@ -2,12 +2,13 @@ require 'travis/api/v3/access_control/generic' module Travis::API::V3 class AccessControl::Application < AccessControl::Generic - attr_reader :application_name, :config, :user + attr_reader :application_name, :config, :user, :user_control def initialize(application_name, user: nil) @application_name = application_name @config = Travis.config.applications[application_name] @user = user + @user_control = user ? AccessControl::User.new(user) : AccessControl::Generic.new raise ArgumentError, 'unknown application %p' % application_name unless config raise ArgumentError, 'cannot use %p without a user' % application_name if config.requires_user and not user end @@ -19,5 +20,12 @@ module Travis::API::V3 def full_access? config.full_access end + + def admin_for(repository) + return user_control.admin_for(repository) unless full_access? + admin = repository.users.where('permissions.admin = true'.freeze).order('users.synced_at DESC'.freeze).first + raise AdminAccessRequired, "no admin found for #{repository.slug}" unless admin + admin + end end end diff --git a/lib/travis/api/v3/access_control/generic.rb b/lib/travis/api/v3/access_control/generic.rb index d04b01d6..0253c18b 100644 --- a/lib/travis/api/v3/access_control/generic.rb +++ b/lib/travis/api/v3/access_control/generic.rb @@ -15,6 +15,10 @@ module Travis::API::V3 full_access? or dispatch(object) end + def admin_for(repository) + raise AdminAccessRequired, repository: repository + end + def user end diff --git a/lib/travis/api/v3/access_control/user.rb b/lib/travis/api/v3/access_control/user.rb index 5dbe81bd..38529f8a 100644 --- a/lib/travis/api/v3/access_control/user.rb +++ b/lib/travis/api/v3/access_control/user.rb @@ -15,6 +15,10 @@ module Travis::API::V3 true end + def admin_for(repository) + permission?(:admin, repository) ? user : super + end + protected def repository_writable?(repository) diff --git a/lib/travis/api/v3/extensions/encrypted_column.rb b/lib/travis/api/v3/extensions/encrypted_column.rb new file mode 100644 index 00000000..6818b887 --- /dev/null +++ b/lib/travis/api/v3/extensions/encrypted_column.rb @@ -0,0 +1,109 @@ +require 'securerandom' +require 'base64' + +module Travis::API::V3 + module Extensions + class EncryptedColumn + attr_reader :disable, :options + alias disabled? disable + + def initialize(options = {}) + @options = options || {} + @disable = self.options[:disable] + @key = self.options[:key] + end + + def enabled? + !disabled? + end + + def load(data) + return nil unless data + + data = data.to_s + + decrypt?(data) ? decrypt(data) : data + end + + def dump(data) + encrypt?(data) ? encrypt(data.to_s) : data + end + + def key + @key || config.key + end + + def iv + SecureRandom.hex(8) + end + + def prefix + '--ENCR--' + end + + def decrypt?(data) + data.present? && (!use_prefix? || prefix_used?(data)) + end + + def encrypt?(data) + data.present? && enabled? + end + + def prefix_used?(data) + data[0..7] == prefix + end + + def decrypt(data) + data = data[8..-1] if prefix_used?(data) + + data = decode data + + iv = data[-16..-1] + data = data[0..-17] + + aes = create_aes :decrypt, key.to_s, iv + + result = aes.update(data) + aes.final + end + + def encrypt(data) + iv = self.iv + + aes = create_aes :encrypt, key.to_s, iv + + encrypted = aes.update(data) + aes.final + + encrypted = "#{encrypted}#{iv}" + encrypted = encode encrypted + encrypted = "#{prefix}#{encrypted}" if use_prefix? + encrypted + end + + def use_prefix? + options.has_key?(:use_prefix) ? options[:use_prefix] : Travis::Features.feature_inactive?(:db_encryption_prefix) + end + + def create_aes(mode = :encrypt, key, iv) + aes = OpenSSL::Cipher::AES.new(256, :CBC) + + aes.send(mode) + aes.key = key + aes.iv = iv + + aes + end + + def config + Travis.config.encryption + end + + def decode(str) + Base64.strict_decode64 str + end + + def encode(str) + Base64.strict_encode64 str + end + end + end +end diff --git a/lib/travis/api/v3/github.rb b/lib/travis/api/v3/github.rb new file mode 100644 index 00000000..0766ea2c --- /dev/null +++ b/lib/travis/api/v3/github.rb @@ -0,0 +1,43 @@ +require 'gh' + + +module Travis::API::V3 + class GitHub + DEFAULT_OPTIONS = { + client_id: Travis.config.oauth2.try(:client_id), + client_secret: Travis.config.oauth2.try(:client_secret), + user_agent: "Travis-API/3 Travis-CI/0.0.1 GH/#{GH::VERSION}", + origin: Travis.config.host, + api_url: Travis.config.github.api_url, + ssl: Travis.config.ssl.merge(Travis.config.github.ssl || {}).to_hash.compact + } + private_constant :DEFAULT_OPTIONS + + attr_reader :gh, :user + + def initialize(user = nil, token = nil) + if user.respond_to? :github_oauth_token + raise ServerError, 'no GitHub token for user' if user.github_oauth_token.blank? + token = user.github_oauth_token + end + + @user = user + @gh = GH.with(token: token, **DEFAULT_OPTIONS) + end + + def set_hook(repository, flag) + config = { domain: Travis.config.service_hook_url || '' } + + if user + config[:user] = user.login + config[:token] = user.token + end + + gh.post("repos/#{repository.slug}/hooks", + name: 'travis'.freeze, + events: [:push, :pull_request, :issue_comment, :public, :member], + active: flag, + config: config) + end + end +end \ No newline at end of file diff --git a/lib/travis/api/v3/models/token.rb b/lib/travis/api/v3/models/token.rb new file mode 100644 index 00000000..90965d0b --- /dev/null +++ b/lib/travis/api/v3/models/token.rb @@ -0,0 +1,14 @@ +module Travis::API::V3 + class Models::Token < Model + belongs_to :user + validate :token, presence: true + serialize :token, Extensions::EncryptedColumn.new(disable: true) + before_validation :generate_token, on: :create + + protected + + def generate_token + self.token = SecureRandom.base64(15).tr('+/=lIO0', 'pqrsxyz') + end + end +end diff --git a/lib/travis/api/v3/models/user.rb b/lib/travis/api/v3/models/user.rb index c07f3a4b..06197e03 100644 --- a/lib/travis/api/v3/models/user.rb +++ b/lib/travis/api/v3/models/user.rb @@ -1,9 +1,14 @@ module Travis::API::V3 class Models::User < Model has_many :memberships, dependent: :destroy - has_many :organizations, through: :memberships has_many :permissions, dependent: :destroy - has_many :repositories, through: :permissions has_many :emails, dependent: :destroy + has_many :tokens, dependent: :destroy + has_many :repositories, through: :permissions + has_many :organizations, through: :memberships + + def token + tokens.first_or_create.token + end end end diff --git a/lib/travis/api/v3/service.rb b/lib/travis/api/v3/service.rb index 95aad833..affd37ff 100644 --- a/lib/travis/api/v3/service.rb +++ b/lib/travis/api/v3/service.rb @@ -13,12 +13,17 @@ module Travis::API::V3 @access_control = access_control @params = params @queries = {} + @github = {} end def query(type = self.class.result_type) @queries[type] ||= Queries[type].new(params, self.class.result_type) end + def github(user = nil) + @github[user] ||= GitHub.new(user) + end + def find(type = self.class.result_type, *args) not_found(true, type) unless object = query(type).find(*args) not_found(false, type) unless access_control.visible? object diff --git a/lib/travis/api/v3/services/repository/disable.rb b/lib/travis/api/v3/services/repository/disable.rb index 9e5103ac..9babfd52 100644 --- a/lib/travis/api/v3/services/repository/disable.rb +++ b/lib/travis/api/v3/services/repository/disable.rb @@ -1,7 +1,14 @@ module Travis::API::V3 class Services::Repository::Disable < Service def run!(activate = false) - not_implemented + raise LoginRequired unless access_control.logged_in? or access_control.full_access? + raise NotFound unless repository = find(:repository) + + admin = access_control.admin_for(repository) + github(admin).set_hook(repository, activate) + repository.update_attributes(active: active) + + repository end end end From 08505fead08f56e30baccd1d82e1030be425c7ff Mon Sep 17 00:00:00 2001 From: Konstantin Haase Date: Fri, 6 Mar 2015 15:36:31 +0100 Subject: [PATCH 3/8] v3: don't set user/token on github hook --- lib/travis/api/v3/github.rb | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/travis/api/v3/github.rb b/lib/travis/api/v3/github.rb index 0766ea2c..d47eae65 100644 --- a/lib/travis/api/v3/github.rb +++ b/lib/travis/api/v3/github.rb @@ -26,18 +26,11 @@ module Travis::API::V3 end def set_hook(repository, flag) - config = { domain: Travis.config.service_hook_url || '' } - - if user - config[:user] = user.login - config[:token] = user.token - end - gh.post("repos/#{repository.slug}/hooks", name: 'travis'.freeze, events: [:push, :pull_request, :issue_comment, :public, :member], active: flag, - config: config) + config: { domain: Travis.config.service_hook_url || '' }) end end end \ No newline at end of file From 05c20422cc0682026b233845c71b8e4e2c02e850 Mon Sep 17 00:00:00 2001 From: Konstantin Haase Date: Fri, 6 Mar 2015 16:07:45 +0100 Subject: [PATCH 4/8] v3: github_oauth_token is encrypted --- lib/travis/api/v3/models/user.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/travis/api/v3/models/user.rb b/lib/travis/api/v3/models/user.rb index 06197e03..874bc908 100644 --- a/lib/travis/api/v3/models/user.rb +++ b/lib/travis/api/v3/models/user.rb @@ -7,6 +7,9 @@ module Travis::API::V3 has_many :repositories, through: :permissions has_many :organizations, through: :memberships + + serialize :github_oauth_token, Extensions::EncryptedColumn.new(disable: true) + def token tokens.first_or_create.token end From deacd0fdfab6ddfbaa6c54e9892d9e8c0b5e33c6 Mon Sep 17 00:00:00 2001 From: Konstantin Haase Date: Fri, 6 Mar 2015 16:57:06 +0100 Subject: [PATCH 5/8] use correct variable --- lib/travis/api/v3/services/repository/disable.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/travis/api/v3/services/repository/disable.rb b/lib/travis/api/v3/services/repository/disable.rb index 9babfd52..08a191bb 100644 --- a/lib/travis/api/v3/services/repository/disable.rb +++ b/lib/travis/api/v3/services/repository/disable.rb @@ -5,8 +5,9 @@ module Travis::API::V3 raise NotFound unless repository = find(:repository) admin = access_control.admin_for(repository) + github(admin).set_hook(repository, activate) - repository.update_attributes(active: active) + repository.update_attributes(active: activate) repository end From 819b0574978ad756acb86f3f7a5d76d0e725a2df Mon Sep 17 00:00:00 2001 From: Konstantin Haase Date: Fri, 6 Mar 2015 17:02:30 +0100 Subject: [PATCH 6/8] v3: override existing travis hook --- lib/travis/api/v3/github.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/travis/api/v3/github.rb b/lib/travis/api/v3/github.rb index d47eae65..85d20eda 100644 --- a/lib/travis/api/v3/github.rb +++ b/lib/travis/api/v3/github.rb @@ -26,7 +26,13 @@ module Travis::API::V3 end def set_hook(repository, flag) - gh.post("repos/#{repository.slug}/hooks", + hooks_url = "repos/#{repository.slug}/hooks" + + if hook = gh.get(hooks_url).detect { |hook| hook['name'.freeze] == 'travis'.freeze } + gh.delete(hook['_links'.freeze]['self'.freeze]['href'.freeze]) + end + + gh.post(hooks_url, name: 'travis'.freeze, events: [:push, :pull_request, :issue_comment, :public, :member], active: flag, From e01e71e6db91dd6ef094a216eaf7ec4adda18742 Mon Sep 17 00:00:00 2001 From: Konstantin Haase Date: Fri, 6 Mar 2015 17:04:46 +0100 Subject: [PATCH 7/8] v3: fix gh call --- lib/travis/api/v3/github.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/travis/api/v3/github.rb b/lib/travis/api/v3/github.rb index 85d20eda..3fa60cfe 100644 --- a/lib/travis/api/v3/github.rb +++ b/lib/travis/api/v3/github.rb @@ -28,7 +28,7 @@ module Travis::API::V3 def set_hook(repository, flag) hooks_url = "repos/#{repository.slug}/hooks" - if hook = gh.get(hooks_url).detect { |hook| hook['name'.freeze] == 'travis'.freeze } + if hook = gh[hooks_url].detect { |hook| hook['name'.freeze] == 'travis'.freeze } gh.delete(hook['_links'.freeze]['self'.freeze]['href'.freeze]) end From 3687436bd825f1c2ba1c28ff2c9915a3e999236c Mon Sep 17 00:00:00 2001 From: Konstantin Haase Date: Fri, 6 Mar 2015 17:09:33 +0100 Subject: [PATCH 8/8] v3: update existing hook instead of removing it --- lib/travis/api/v3/github.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/travis/api/v3/github.rb b/lib/travis/api/v3/github.rb index 3fa60cfe..b1f501c2 100644 --- a/lib/travis/api/v3/github.rb +++ b/lib/travis/api/v3/github.rb @@ -27,16 +27,18 @@ module Travis::API::V3 def set_hook(repository, flag) hooks_url = "repos/#{repository.slug}/hooks" - - if hook = gh[hooks_url].detect { |hook| hook['name'.freeze] == 'travis'.freeze } - gh.delete(hook['_links'.freeze]['self'.freeze]['href'.freeze]) - end - - gh.post(hooks_url, + payload = { name: 'travis'.freeze, events: [:push, :pull_request, :issue_comment, :public, :member], active: flag, - config: { domain: Travis.config.service_hook_url || '' }) + config: { domain: Travis.config.service_hook_url || '' } + } + + if hook = gh[hooks_url].detect { |hook| hook['name'.freeze] == 'travis'.freeze } + gh.patch(hook['_links'.freeze]['self'.freeze]['href'.freeze], payload) + else + gh.post(hooks_url, payload) + end end end end \ No newline at end of file