Skip to content

Commit

Permalink
Merge branch 'master' into bsfy-181-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
murtaza-swati committed Dec 12, 2022
2 parents fd7f085 + 4d10c7f commit 701db1c
Show file tree
Hide file tree
Showing 17 changed files with 355 additions and 2 deletions.
45 changes: 45 additions & 0 deletions lib/travis/api/v3/models/custom_key.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
module Travis::API::V3
class Models::CustomKey < Model
belongs_to :owner, polymorphic: true

serialize :private_key, Travis::Model::EncryptedColumn.new

validates_each :private_key do |record, attr, private_key|
record.errors.add(attr, :missing_attr, message: 'missing_attr') if private_key.blank?
record.errors.add(attr, :invalid_pem, message: 'invalid_pem') unless record.valid_pem?
end

def save_key!(owner_type, owner_id, name, description, private_key, added_by)
self.owner_type = owner_type
self.owner_id = owner_id
self.private_key = private_key
self.name = name
self.description = description
self.added_by = added_by

if self.valid?
self.fingerprint = calculate_fingerprint(private_key)
self.public_key = OpenSSL::PKey::RSA.new(private_key).public_key.to_s

self.save!
end

self
end

def valid_pem?
private_key && OpenSSL::PKey::RSA.new(private_key)
true
rescue OpenSSL::PKey::RSAError
false
end

private

def calculate_fingerprint(source)
rsa_key = OpenSSL::PKey::RSA.new(source)
public_ssh_rsa = "\x00\x00\x00\x07ssh-rsa" + rsa_key.e.to_s(0) + rsa_key.n.to_s(0)
OpenSSL::Digest::MD5.new(public_ssh_rsa).hexdigest.scan(/../).join(':')
end
end
end
5 changes: 5 additions & 0 deletions lib/travis/api/v3/models/organization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ def build_priorities_enabled?
Travis::Features.owner_active?(:build_priorities_org, self)
end

def custom_keys
return @custom_keys if defined? @custom_keys
@custom_keys = Models::CustomKey.where(owner_type: 'Organization', owner_id: id)
end

alias members users
end
end
5 changes: 5 additions & 0 deletions lib/travis/api/v3/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,10 @@ def installation
def github?
vcs_type == 'GithubUser'
end

def custom_keys
return @custom_keys if defined? @custom_keys
@custom_keys = Models::CustomKey.where(owner_type: 'User', owner_id: id)
end
end
end
65 changes: 65 additions & 0 deletions lib/travis/api/v3/queries/custom_key.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
module Travis::API::V3
class Queries::CustomKey < Query
def create(params, current_user)
raise UnprocessableEntity, 'Key with this identifier already exists.' unless Travis::API::V3::Models::CustomKey.where(name: params['name'], owner_id: params['owner_id'], owner_type: params['owner_type']).count.zero?

if params['owner_type'] == 'User'
org_ids = User.find(params['owner_id']).organizations.map(&:id)

raise UnprocessableEntity, 'Key with this identifier already exists in one of your organizations.' unless Travis::API::V3::Models::CustomKey.where(name: params['name'], owner_id: org_ids, owner_type: 'Organization').count.zero?
elsif params['owner_type'] == 'Organization'
user_ids = Membership.where(organization_id: params['owner_id']).map(&:user_id)

raise UnprocessableEntity, 'Key with this identifier already exists for your user.' unless Travis::API::V3::Models::CustomKey.where(name: params['name'], owner_id: user_ids, owner_type: 'User').count.zero?
end

key = Travis::API::V3::Models::CustomKey.new.save_key!(
params['owner_type'],
params['owner_id'],
params['name'],
params['description'],
params['private_key'],
params['added_by']
)
handle_errors(key) unless key.valid?

Travis::API::V3::Models::Audit.create!(
owner: current_user,
change_source: 'travis-api',
source: key,
source_changes: {
action: 'create',
fingerprint: key.fingerprint
}
)

key
end

def delete(params, current_user)
key = Travis::API::V3::Models::CustomKey.find(params['id'])
Travis::API::V3::Models::Audit.create!(
owner: current_user,
change_source: 'travis-api',
source: key,
source_changes: {
action: 'delete',
name: key.name,
owner_type: key.owner_type,
owner_id: key.owner_id,
fingerprint: key.fingerprint
}
)

key.destroy
end

private

def handle_errors(key)
private_key = key.errors[:private_key]
raise UnprocessableEntity, 'This key is not a private key.' if private_key.include?('invalid_pem')
raise WrongParams if private_key.include?('missing_attr')
end
end
end
10 changes: 10 additions & 0 deletions lib/travis/api/v3/renderer/custom_key.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module Travis::API::V3
class Renderer::CustomKey < ModelRenderer
representation :standard, :id, :name, :description, :public_key, :fingerprint, :added_by_login, :created_at
representation :minimal, *representations[:standard]

def added_by_login
model.added_by.nil? ? '' : User.find(model.added_by).login
end
end
end
2 changes: 1 addition & 1 deletion lib/travis/api/v3/renderer/owner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class Renderer::Owner < ModelRenderer

representation(:minimal, :id, :login, :name, :vcs_type, :ro_mode)
representation(:standard, :id, :login, :name, :github_id, :vcs_id, :vcs_type, :avatar_url, :education,
:allow_migration, :allowance, :ro_mode)
:allow_migration, :allowance, :ro_mode, :custom_keys)
representation(:additional, :repositories, :installation)

def initialize(*)
Expand Down
2 changes: 1 addition & 1 deletion lib/travis/api/v3/renderer/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

module Travis::API::V3
class Renderer::User < Renderer::Owner
representation(:standard, :email, :is_syncing, :synced_at, :recently_signed_up, :secure_user_hash, :ro_mode, :confirmed_at)
representation(:standard, :email, :is_syncing, :synced_at, :recently_signed_up, :secure_user_hash, :ro_mode, :confirmed_at, :custom_keys)
representation(:additional, :emails)

def email
Expand Down
10 changes: 10 additions & 0 deletions lib/travis/api/v3/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,16 @@ module Routes
end
end

hidden_resource :custom_keys do
route '/custom_keys'
post :create
end

hidden_resource :custom_key do
route '/custom_key/{id}'
delete :delete
end

hidden_resource :beta_migration_requests do
route '/beta_migration_requests'

Expand Down
2 changes: 2 additions & 0 deletions lib/travis/api/v3/services.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ module Services
CreditsCalculator = Module.new { extend Services }
Cron = Module.new { extend Services }
Crons = Module.new { extend Services }
CustomKey = Module.new { extend Services }
CustomKeys = Module.new { extend Services }
EmailSubscription = Module.new { extend Services }
EnvVar = Module.new { extend Services }
EnvVars = Module.new { extend Services }
Expand Down
10 changes: 10 additions & 0 deletions lib/travis/api/v3/services/custom_key/delete.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module Travis::API::V3
class Services::CustomKey::Delete < Service
def run!
raise LoginRequired unless access_control.full_access_or_logged_in?

query(:custom_key).delete(params, access_control.user)
deleted
end
end
end
12 changes: 12 additions & 0 deletions lib/travis/api/v3/services/custom_keys/create.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module Travis::API::V3
class Services::CustomKeys::Create < Service
params :owner_id, :owner_type, :name, :description, :private_key, :added_by
result_type :custom_key

def run!
raise LoginRequired unless access_control.full_access_or_logged_in?

result query(:custom_key).create(params, access_control.user)
end
end
end
1 change: 1 addition & 0 deletions lib/travis/model/organization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class Organization < Travis::Model
has_many :users, :through => :memberships
has_many :repositories, :as => :owner
has_one :owner_group, as: :owner
has_many :custom_keys, as: :owner

def education?
Travis::Features.owner_active?(:educational_org, self)
Expand Down
1 change: 1 addition & 0 deletions lib/travis/model/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class User < Travis::Model
has_many :repositories, through: :permissions
has_many :emails, dependent: :destroy
has_one :owner_group, as: :owner
has_many :custom_keys, as: :owner

before_create :set_as_recent
after_create :create_a_token
Expand Down
23 changes: 23 additions & 0 deletions spec/v3/models/custom_key_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
describe Travis::API::V3::Models::CustomKey do
let(:user) { FactoryBot.create(:user) }
let(:owner_type) { 'User' }
let(:owner_id) { user.id }
let(:name) { 'TEST_KEY' }
let(:added_by) { user.id }
let(:private_key) { OpenSSL::PKey::RSA.new(TEST_PRIVATE_KEY).to_pem }

subject { Travis::API::V3::Models::CustomKey.new }

it 'must save valid private key' do
key = subject.save_key!(owner_type, owner_id, name, '', private_key, added_by)

expect(key.name).to eq(name)
expect(key.fingerprint).to eq('57:78:65:c2:c9:c8:c9:f7:dd:2b:35:39:40:27:d2:40')
end

it 'must not save invalid private key' do
key = subject.save_key!(owner_type, owner_id, name, '', 'INVALID', added_by)

expect(key.errors.messages[:private_key]).to eq(['invalid_pem'])
end
end
99 changes: 99 additions & 0 deletions spec/v3/queries/custom_key_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
describe Travis::API::V3::Queries::CustomKey do
let(:private_key) { OpenSSL::PKey::RSA.new(TEST_PRIVATE_KEY).to_pem }

context 'owner type is user' do
let(:user) { FactoryBot.create(:user) }
let(:params) do
{
'owner_id' => user.id,
'owner_type' => 'User',
'added_by' => user.id,
'name' => 'TEST_KEY',
'private_key' => private_key,
'description' => ''
}
end

context 'key with identifier does not exist in user or organization' do
it 'creates custom key' do
expect(described_class.new({}, 'CustomKey').create(params).fingerprint).to eq('57:78:65:c2:c9:c8:c9:f7:dd:2b:35:39:40:27:d2:40')
end
end

context 'key with identifier exists for this user' do
before do
Travis::API::V3::Models::CustomKey.new.save_key!(
params['owner_type'],
params['owner_id'],
params['name'],
params['description'],
params['private_key'],
params['added_by']
)
end

it 'returns error' do
expect { described_class.new({}, 'CustomKey').create(params) }.to raise_error(Travis::API::V3::UnprocessableEntity)
end
end

context 'key with identifier exists for users organization' do
let(:org) { FactoryBot.create(:org) }
let!(:membership) { org.memberships.create(user: user, role: 'admin', build_permission: true) }

before do
Travis::API::V3::Models::CustomKey.new.save_key!(
'Organization',
org.id,
params['name'],
params['description'],
params['private_key'],
params['added_by']
)
end

it 'returns error' do
expect { described_class.new({}, 'CustomKey').create(params) }.to raise_error(Travis::API::V3::UnprocessableEntity)
end
end
end

context 'owner type is organization' do
let(:org) { FactoryBot.create(:org) }
let(:user) { FactoryBot.create(:user) }
let!(:membership) { org.memberships.create(user: user, role: 'admin', build_permission: true) }
let(:params) do
{
'owner_id' => org.id,
'owner_type' => 'Organization',
'added_by' => user.id,
'name' => 'TEST_KEY',
'private_key' => private_key,
'description' => ''
}
end

context 'key with identifier does not exist in user or organization' do
it 'creates custom key' do
expect(described_class.new({}, 'CustomKey').create(params).fingerprint).to eq('57:78:65:c2:c9:c8:c9:f7:dd:2b:35:39:40:27:d2:40')
end
end

context 'key with identifier exists for this user' do
before do
Travis::API::V3::Models::CustomKey.new.save_key!(
'User',
user.id,
params['name'],
params['description'],
params['private_key'],
params['added_by']
)
end

it 'returns error' do
expect { described_class.new({}, 'CustomKey').create(params) }.to raise_error(Travis::API::V3::UnprocessableEntity)
end
end
end
end
15 changes: 15 additions & 0 deletions spec/v3/services/custom_key/delete_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
describe Travis::API::V3::Services::CustomKey::Delete, set_app: true do
let(:user) { FactoryBot.create(:user) }
let(:private_key) { OpenSSL::PKey::RSA.new(TEST_PRIVATE_KEY).to_pem }
let(:custom_key) { Travis::API::V3::Models::CustomKey.new.save_key!('User', user.id, 'TEST_KEY', '', private_key, user.id) }
let(:token) { Travis::Api::App::AccessToken.create(user: user, app_id: 1) }
let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}" }}
let(:parsed_body) { JSON.load(body) }

describe "deleting a custom key by id" do
before { delete("/v3/custom_key/#{custom_key.id}", {}, headers) }
example { expect(last_response.status).to eq 204 }
example { expect(Travis::API::V3::Models::CustomKey.where(id: custom_key.id)).to be_empty }
example { expect(parsed_body).to be_nil }
end
end
Loading

0 comments on commit 701db1c

Please sign in to comment.