commit
b2ba9383eb
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -3,3 +3,5 @@ config/travis.yml
|
|||
log/
|
||||
vendor
|
||||
config/skylight.yml
|
||||
.coverage
|
||||
.coverage/
|
||||
|
|
4
Gemfile
4
Gemfile
|
@ -10,6 +10,7 @@ gem 'travis-support', github: 'travis-ci/travis-support'
|
|||
gem 'travis-config', '~> 0.1.0'
|
||||
gem 'travis-sidekiqs', github: 'travis-ci/travis-sidekiqs', require: nil, ref: 'cde9741'
|
||||
gem 'travis-yaml', github: 'travis-ci/travis-yaml'
|
||||
gem 'mustermann', github: 'rkh/mustermann'
|
||||
gem 'sinatra'
|
||||
gem 'sinatra-contrib', require: nil #github: 'sinatra/sinatra-contrib', require: nil
|
||||
|
||||
|
@ -27,7 +28,8 @@ gem 'pry'
|
|||
gem 'metriks', '0.9.9.6'
|
||||
gem 'metriks-librato_metrics', github: 'eric/metriks-librato_metrics'
|
||||
gem 'micro_migrations'
|
||||
gem 'skylight'
|
||||
gem 'simplecov'
|
||||
gem 'skylight', '~> 0.6.0.beta.1'
|
||||
|
||||
group :test do
|
||||
gem 'rspec', '~> 2.13'
|
||||
|
|
70
Gemfile.lock
70
Gemfile.lock
|
@ -20,6 +20,13 @@ GIT
|
|||
rack-contrib (1.2.0)
|
||||
rack (>= 0.9.1)
|
||||
|
||||
GIT
|
||||
remote: git://github.com/rkh/mustermann.git
|
||||
revision: 01df7fe671838d6caadac278d9704b5cd3c7b999
|
||||
specs:
|
||||
mustermann (0.4.0)
|
||||
tool (~> 0.2)
|
||||
|
||||
GIT
|
||||
remote: git://github.com/rkh/yard-sinatra.git
|
||||
revision: 00774d355123617ff0faa7e0ebd54c4cdcfcdf93
|
||||
|
@ -43,7 +50,7 @@ GIT
|
|||
|
||||
GIT
|
||||
remote: git://github.com/travis-ci/travis-core.git
|
||||
revision: cbfb6da8a1c6f4cc80c40b260d4d2708e3acec03
|
||||
revision: a0aa1e2f79d45a4c59c1046a5435fe598eda2d2a
|
||||
specs:
|
||||
travis-core (0.0.1)
|
||||
actionmailer (~> 3.2.19)
|
||||
|
@ -89,7 +96,6 @@ PATH
|
|||
remote: .
|
||||
specs:
|
||||
travis-api (0.0.1)
|
||||
backports (~> 2.5)
|
||||
memcachier
|
||||
mustermann (~> 0.4)
|
||||
pg (~> 0.13.2)
|
||||
|
@ -106,12 +112,12 @@ PATH
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actionmailer (3.2.21)
|
||||
actionpack (= 3.2.21)
|
||||
actionmailer (3.2.19)
|
||||
actionpack (= 3.2.19)
|
||||
mail (~> 2.5.4)
|
||||
actionpack (3.2.21)
|
||||
activemodel (= 3.2.21)
|
||||
activesupport (= 3.2.21)
|
||||
actionpack (3.2.19)
|
||||
activemodel (= 3.2.19)
|
||||
activesupport (= 3.2.19)
|
||||
builder (~> 3.0.0)
|
||||
erubis (~> 2.7.0)
|
||||
journey (~> 1.0.4)
|
||||
|
@ -121,15 +127,15 @@ GEM
|
|||
sprockets (~> 2.2.1)
|
||||
active_model_serializers (0.9.0)
|
||||
activemodel (>= 3.2)
|
||||
activemodel (3.2.21)
|
||||
activesupport (= 3.2.21)
|
||||
activemodel (3.2.19)
|
||||
activesupport (= 3.2.19)
|
||||
builder (~> 3.0.0)
|
||||
activerecord (3.2.21)
|
||||
activemodel (= 3.2.21)
|
||||
activesupport (= 3.2.21)
|
||||
activerecord (3.2.19)
|
||||
activemodel (= 3.2.19)
|
||||
activesupport (= 3.2.19)
|
||||
arel (~> 3.0.2)
|
||||
tzinfo (~> 0.3.29)
|
||||
activesupport (3.2.21)
|
||||
activesupport (3.2.19)
|
||||
i18n (~> 0.6, >= 0.6.4)
|
||||
multi_json (~> 1.0)
|
||||
addressable (2.3.6)
|
||||
|
@ -140,7 +146,7 @@ GEM
|
|||
descendants_tracker (~> 0.0.4)
|
||||
ice_nine (~> 0.11.0)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
backports (2.8.2)
|
||||
backports (3.6.4)
|
||||
builder (3.0.4)
|
||||
bunny (0.8.0)
|
||||
celluloid (0.12.0)
|
||||
|
@ -172,10 +178,11 @@ GEM
|
|||
descendants_tracker (0.0.4)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
diff-lcs (1.2.5)
|
||||
docile (1.1.5)
|
||||
dotenv (0.7.0)
|
||||
equalizer (0.0.9)
|
||||
erubis (2.7.0)
|
||||
eventmachine (1.0.3)
|
||||
eventmachine (1.0.4)
|
||||
factory_girl (2.4.2)
|
||||
activesupport
|
||||
faraday (0.9.1)
|
||||
|
@ -184,7 +191,7 @@ GEM
|
|||
foreman (0.64.0)
|
||||
dotenv (~> 0.7.0)
|
||||
thor (>= 0.13.6)
|
||||
gh (0.13.2)
|
||||
gh (0.13.3)
|
||||
addressable
|
||||
backports
|
||||
faraday (~> 0.8)
|
||||
|
@ -195,10 +202,10 @@ GEM
|
|||
hike (1.2.3)
|
||||
hitimes (1.2.2)
|
||||
httpclient (2.3.4.1)
|
||||
i18n (0.7.0)
|
||||
ice_nine (0.11.1)
|
||||
i18n (0.6.11)
|
||||
ice_nine (0.11.0)
|
||||
journey (1.0.4)
|
||||
json (1.8.2)
|
||||
json (1.8.1)
|
||||
kgio (2.9.2)
|
||||
listen (1.0.3)
|
||||
rb-fsevent (>= 0.9.3)
|
||||
|
@ -222,8 +229,6 @@ GEM
|
|||
metaclass (~> 0.0.1)
|
||||
multi_json (1.10.1)
|
||||
multipart-post (2.0.0)
|
||||
mustermann (0.4.0)
|
||||
tool (~> 0.2)
|
||||
net-http-persistent (2.9.4)
|
||||
net-http-pipeline (1.0.1)
|
||||
pg (0.13.2)
|
||||
|
@ -244,11 +249,11 @@ GEM
|
|||
rack
|
||||
rack-ssl (1.3.4)
|
||||
rack
|
||||
rack-test (0.6.3)
|
||||
rack-test (0.6.2)
|
||||
rack (>= 1.0)
|
||||
railties (3.2.21)
|
||||
actionpack (= 3.2.21)
|
||||
activesupport (= 3.2.21)
|
||||
railties (3.2.19)
|
||||
actionpack (= 3.2.19)
|
||||
activesupport (= 3.2.19)
|
||||
rack-ssl (~> 1.3.2)
|
||||
rake (>= 0.8.7)
|
||||
rdoc (~> 3.4)
|
||||
|
@ -263,7 +268,7 @@ GEM
|
|||
rdoc (3.12.2)
|
||||
json (~> 1.4)
|
||||
redcarpet (2.3.0)
|
||||
redis (3.2.0)
|
||||
redis (3.1.0)
|
||||
redis-namespace (1.5.1)
|
||||
redis (~> 3.0, >= 3.0.4)
|
||||
rerun (0.8.2)
|
||||
|
@ -291,6 +296,11 @@ GEM
|
|||
simple_states (1.0.1)
|
||||
activesupport
|
||||
hashr (~> 0.0.10)
|
||||
simplecov (0.9.1)
|
||||
docile (~> 1.1.0)
|
||||
multi_json (~> 1.0)
|
||||
simplecov-html (~> 0.8.0)
|
||||
simplecov-html (0.8.0)
|
||||
sinatra (1.4.5)
|
||||
rack (~> 1.4)
|
||||
rack-protection (~> 1.4)
|
||||
|
@ -305,7 +315,7 @@ GEM
|
|||
skylight (0.6.0.beta.1)
|
||||
activesupport (>= 3.0.0)
|
||||
slop (3.6.0)
|
||||
sprockets (2.2.3)
|
||||
sprockets (2.2.2)
|
||||
hike (~> 1.2)
|
||||
multi_json (~> 1.0)
|
||||
rack (~> 1.0)
|
||||
|
@ -335,7 +345,7 @@ GEM
|
|||
raindrops (~> 0.7)
|
||||
useragent (0.10.0)
|
||||
uuidtools (2.1.5)
|
||||
virtus (1.0.4)
|
||||
virtus (1.0.3)
|
||||
axiom-types (~> 0.1)
|
||||
coercible (~> 1.0)
|
||||
descendants_tracker (~> 0.0, >= 0.0.3)
|
||||
|
@ -357,6 +367,7 @@ DEPENDENCIES
|
|||
metriks-librato_metrics!
|
||||
micro_migrations
|
||||
mocha (~> 0.12)
|
||||
mustermann!
|
||||
pry
|
||||
rack-attack
|
||||
rack-cache!
|
||||
|
@ -367,9 +378,10 @@ DEPENDENCIES
|
|||
rspec (~> 2.13)
|
||||
s3!
|
||||
sentry-raven!
|
||||
simplecov
|
||||
sinatra
|
||||
sinatra-contrib
|
||||
skylight
|
||||
skylight (~> 0.6.0.beta.1)
|
||||
travis-api!
|
||||
travis-config (~> 0.1.0)
|
||||
travis-core!
|
||||
|
|
|
@ -3,7 +3,6 @@ require 'travis'
|
|||
require 'travis/model'
|
||||
require 'travis/support/amqp'
|
||||
require 'travis/states_cache'
|
||||
require 'backports'
|
||||
require 'rack'
|
||||
require 'rack/protection'
|
||||
require 'rack/contrib'
|
||||
|
@ -22,6 +21,7 @@ require 'travis/support/log_subscriber/active_record_metrics'
|
|||
require 'fileutils'
|
||||
require 'travis/api/instruments'
|
||||
require 'travis/api/v2/http'
|
||||
require 'travis/api/v3'
|
||||
require 'travis/api/app/stack_instrumentation'
|
||||
|
||||
# Rack class implementing the HTTP API.
|
||||
|
@ -115,11 +115,17 @@ module Travis::Api
|
|||
env['travis.global_prefix'] = env['SCRIPT_NAME']
|
||||
end
|
||||
|
||||
use Travis::Api::App::Middleware::ScopeCheck
|
||||
|
||||
use Travis::Api::App::Middleware::Logging
|
||||
use Travis::Api::App::Middleware::Metriks
|
||||
use Travis::Api::App::Middleware::Rewrite
|
||||
use Travis::Api::App::Middleware::ScopeCheck
|
||||
use Travis::Api::App::Middleware::UserAgentTracker
|
||||
use Travis::Api::App::Middleware::Metriks
|
||||
|
||||
# if this is a v3 API request, ignore everything after
|
||||
use Travis::API::V3::OptIn
|
||||
|
||||
# rewrite should come after V3 hook
|
||||
use Travis::Api::App::Middleware::Rewrite
|
||||
|
||||
SettingsEndpoint.subclass :env_vars
|
||||
if Travis.config.endpoints.ssh_key
|
||||
|
@ -195,8 +201,8 @@ module Travis::Api
|
|||
end
|
||||
|
||||
def self.load_endpoints
|
||||
Backports.require_relative_dir 'app/middleware'
|
||||
Backports.require_relative_dir 'app/endpoint'
|
||||
Dir.glob("#{__dir__}/app/middleware/*.rb").each { |f| require f[%r[(?<=lib/).+(?=\.rb$)]] }
|
||||
Dir.glob("#{__dir__}/app/endpoint/*.rb").each { |f| require f[%r[(?<=lib/).+(?=\.rb$)]] }
|
||||
end
|
||||
|
||||
def self.setup_endpoints
|
||||
|
|
|
@ -17,7 +17,7 @@ class Travis::Api::App
|
|||
# Landing point. Redirects web browsers to [API documentation](#/docs/).
|
||||
get '/' do
|
||||
pass if settings.disable_root_endpoint?
|
||||
redirect to('/docs/') if request.preferred_type('application/json', 'text/html') == 'text/html'
|
||||
redirect to('/docs/') if request.preferred_type('application/json', 'application/json-home', 'text/html') == 'text/html'
|
||||
{ 'hello' => 'world' }
|
||||
end
|
||||
|
||||
|
|
|
@ -3,6 +3,6 @@ require 'travis/api/app'
|
|||
class Travis::Api::App
|
||||
# Namespace for Sinatra extensions.
|
||||
module Extensions
|
||||
Backports.require_relative_dir 'extensions'
|
||||
Dir.glob("#{__dir__}/extensions/*.rb").each { |f| require f[%r[(?<=lib/).+(?=\.rb$)]] }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,6 @@ require 'travis/api/app'
|
|||
class Travis::Api::App
|
||||
# Namespace for helpers.
|
||||
module Helpers
|
||||
Backports.require_relative_dir 'helpers'
|
||||
Dir.glob("#{__dir__}/helpers/*.rb").each { |f| require f[%r[(?<=lib/).+(?=\.rb$)]] }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require 'travis/api/app'
|
||||
require 'travis/api/app/helpers/accept'
|
||||
|
||||
class Travis::Api::App
|
||||
module Helpers
|
||||
|
|
27
lib/travis/api/v3.rb
Normal file
27
lib/travis/api/v3.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
module Travis
|
||||
module API
|
||||
module V3
|
||||
V3 = self
|
||||
|
||||
def load_dir(dir, recursive: true)
|
||||
Dir.glob("#{dir}/*.rb").each { |f| require f[%r[(?<=lib/).+(?=\.rb$)]] }
|
||||
Dir.glob("#{dir}/*").each { |dir| load_dir(dir) } if recursive
|
||||
end
|
||||
|
||||
def response(payload, headers = {}, content_type: 'application/json'.freeze, status: 200)
|
||||
payload = JSON.pretty_generate(payload) unless payload.is_a? String
|
||||
headers = { 'Content-Type'.freeze => content_type, 'Content-Length'.freeze => payload.bytesize.to_s }.merge!(headers)
|
||||
[status, headers, [payload] ]
|
||||
end
|
||||
|
||||
extend self
|
||||
load_dir("#{__dir__}/v3")
|
||||
|
||||
ClientError = Error .create(status: 400)
|
||||
NotFound = ClientError .create(:resource, status: 404, template: '%s not found (or insufficient access)')
|
||||
EnitityMissing = NotFound .create(type: 'not_found')
|
||||
WrongCredentials = ClientError .create('access denied', status: 403)
|
||||
WrongParams = ClientError .create('wrong parameters')
|
||||
end
|
||||
end
|
||||
end
|
15
lib/travis/api/v3/access_control.rb
Normal file
15
lib/travis/api/v3/access_control.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
module Travis::API::V3
|
||||
module AccessControl
|
||||
REGISTER = {}
|
||||
|
||||
def self.new(env)
|
||||
type, payload = env['HTTP_AUTHORIZATION'.freeze].to_s.split(" ", 2)
|
||||
payload &&= payload.unpack(?m.freeze).first if type == 'basic'.freeze
|
||||
payload &&= type == 'token'.freeze ? payload.gsub(/^"(.+)"$/, '\1'.freeze) : payload.split(?:.freeze)
|
||||
modes = REGISTER.fetch(type, [])
|
||||
access_control = modes.inject(nil) { |current, mode| current || mode.for_request(type, payload, env) }
|
||||
raise WrongCredentials unless access_control
|
||||
access_control
|
||||
end
|
||||
end
|
||||
end
|
12
lib/travis/api/v3/access_control/anonymous.rb
Normal file
12
lib/travis/api/v3/access_control/anonymous.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
require 'travis/api/v3/access_control/generic'
|
||||
|
||||
module Travis::API::V3
|
||||
class AccessControl::Anonymous < AccessControl::Generic
|
||||
# use when Authorization header is not set
|
||||
auth_type(nil)
|
||||
|
||||
def self.for_request(*)
|
||||
new
|
||||
end
|
||||
end
|
||||
end
|
25
lib/travis/api/v3/access_control/application.rb
Normal file
25
lib/travis/api/v3/access_control/application.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
require 'travis/api/v3/access_control/generic'
|
||||
|
||||
module Travis::API::V3
|
||||
class AccessControl::Application < AccessControl::Generic
|
||||
attr_reader :application_name, :config, :user
|
||||
|
||||
def initialize(application_name, user: nil)
|
||||
@application_name = application_name
|
||||
@config = Travis.config.applications[application_name]
|
||||
@user = user
|
||||
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
|
||||
|
||||
protected
|
||||
|
||||
def logged_in?
|
||||
!!user
|
||||
end
|
||||
|
||||
def full_access?
|
||||
config.full_access
|
||||
end
|
||||
end
|
||||
end
|
48
lib/travis/api/v3/access_control/generic.rb
Normal file
48
lib/travis/api/v3/access_control/generic.rb
Normal file
|
@ -0,0 +1,48 @@
|
|||
module Travis::API::V3
|
||||
class AccessControl::Generic
|
||||
def self.for_request(type, payload, env)
|
||||
end
|
||||
|
||||
def self.auth_type(*list)
|
||||
list.each { |e| (AccessControl::REGISTER[e] ||= []) << self }
|
||||
end
|
||||
|
||||
def visible?(object)
|
||||
full_access? or dispatch(object)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def repository_visible?(repository)
|
||||
return true if unrestricted_api? and not repository.private?
|
||||
private_repository_visible?(repository)
|
||||
end
|
||||
|
||||
def private_repository_visible?(repository)
|
||||
false
|
||||
end
|
||||
|
||||
def full_access?
|
||||
false
|
||||
end
|
||||
|
||||
def logged_in?
|
||||
false
|
||||
end
|
||||
|
||||
def public_api?
|
||||
!Travis.config.private_api
|
||||
end
|
||||
|
||||
def unrestricted_api?
|
||||
full_access? or logged_in? or public_api?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dispatch(object, method = caller_locations.first.base_label)
|
||||
method = object.class.name.underscore + ?_.freeze + method
|
||||
send(method, object) if respond_to?(method, true)
|
||||
end
|
||||
end
|
||||
end
|
27
lib/travis/api/v3/access_control/legacy_token.rb
Normal file
27
lib/travis/api/v3/access_control/legacy_token.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
require 'travis/api/app/access_token'
|
||||
require 'travis/api/v3/access_control/user'
|
||||
|
||||
module Travis::API::V3
|
||||
# Using v2 API tokens to access v3 API.
|
||||
# Allows us to later introduce a new way of storing tokens with more capabilities without API users having to know.
|
||||
class AccessControl::LegacyToken < AccessControl::User
|
||||
auth_type('token', 'basic')
|
||||
|
||||
def self.for_request(type, payload, env)
|
||||
payload = paylad.first if payload.is_a? Array
|
||||
token = Travis::Api::App::AccessToken.find_by_token(payload)
|
||||
new(token) if token
|
||||
end
|
||||
|
||||
def initialize(token)
|
||||
@token = token
|
||||
super(token.user)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def permission?(action, id)
|
||||
super if @token.scopes.include? :private
|
||||
end
|
||||
end
|
||||
end
|
19
lib/travis/api/v3/access_control/scoped.rb
Normal file
19
lib/travis/api/v3/access_control/scoped.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
require 'travis/api/v3/access_control/generic'
|
||||
|
||||
module Travis::API::V3
|
||||
class AccessControl::Scoped < AccessControl::Generic
|
||||
attr_accessor :unscoped, :owner_name, :name
|
||||
|
||||
def initialize(scope, unscoped)
|
||||
@owner_name, @name = scope.split(?/.freeze, 2)
|
||||
@unscoped = unscoped
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def private_repository_visible?(repository)
|
||||
return false if name and repository.name != name
|
||||
unscoped.visible?(repository) if repository.owner_name == owner_name
|
||||
end
|
||||
end
|
||||
end
|
58
lib/travis/api/v3/access_control/signature.rb
Normal file
58
lib/travis/api/v3/access_control/signature.rb
Normal file
|
@ -0,0 +1,58 @@
|
|||
require 'travis/api/v3/access_control/generic'
|
||||
require 'travis/api/app/access_token'
|
||||
require 'digest/sha1'
|
||||
require 'openssl'
|
||||
|
||||
module Travis::API::V3
|
||||
# Support signed requests to not expose the secret to an untrusted environment.
|
||||
class AccessControl::Signature < AccessControl::Generic
|
||||
auth_type('signature')
|
||||
|
||||
def self.for_request(type, payload, env)
|
||||
*args, signature = payload
|
||||
options = Hash[args.map { |a| a.split(?=.freeze, 2) }]
|
||||
challenge = ""
|
||||
|
||||
if github_id = options[?u.freeze]
|
||||
return unless user = ::User.find_by_github_id(github_id)
|
||||
end
|
||||
|
||||
if application = options[?a.freeze]
|
||||
return unless Travis.config.applications and app_config = Travis.config.applications[application]
|
||||
end
|
||||
|
||||
if c = options[?c.freeze]
|
||||
challenge << env['REQUEST_METHOD'.freeze] << "\n".freeze if c.include?(?m.freeze)
|
||||
challenge << env['SCRIPT_NAME'.freeze] << env['PATH_INFO'.freeze] << "\n" if c.include?(?p.freeze)
|
||||
end
|
||||
|
||||
challenge << app_config[:secret] if app_config and user
|
||||
challenge << args.join(?:.freeze)
|
||||
|
||||
if app_config
|
||||
control = AccessControl::Application.new(application, user: user)
|
||||
secrets = user ? secrets_for(user) : [app_config[:secret]]
|
||||
else
|
||||
control = AccessControl::User.new(user)
|
||||
secrets = secrets_for(user)
|
||||
end
|
||||
|
||||
if scope = options[?s.freeze]
|
||||
control &&= AccessControl::Scoped.new(scope, control)
|
||||
end
|
||||
|
||||
control if secrets.any? { |secret| signed(challenge, secret) == signature }
|
||||
end
|
||||
|
||||
def self.secrets_for(user)
|
||||
[
|
||||
Travis::Api::App::AccessToken.new(user: user, app_id: 1), # generated from github token
|
||||
Travis::Api::App::AccessToken.new(user: user, app_id: 0) # used by web
|
||||
]
|
||||
end
|
||||
|
||||
def self.signed(challenge, secret)
|
||||
OpenSSL::HMAC.hexdigest('sha256'.freeze, secret, challenge)
|
||||
end
|
||||
end
|
||||
end
|
24
lib/travis/api/v3/access_control/user.rb
Normal file
24
lib/travis/api/v3/access_control/user.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
require 'travis/api/v3/access_control/generic'
|
||||
|
||||
module Travis::API::V3
|
||||
class AccessControl::User < AccessControl::Generic
|
||||
attr_reader :user, :permissions
|
||||
|
||||
def initialize(user)
|
||||
@user = user
|
||||
@permissions = user.permissions.where(user_id: user.id)
|
||||
super()
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def private_repository_visible?(repository)
|
||||
permission?(:pull, repository)
|
||||
end
|
||||
|
||||
def permission?(type, id)
|
||||
id = id.id if id.is_a? ::Repository
|
||||
permissions.where(type => true, :repository_id => id).any?
|
||||
end
|
||||
end
|
||||
end
|
38
lib/travis/api/v3/error.rb
Normal file
38
lib/travis/api/v3/error.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
require 'rack/utils'
|
||||
|
||||
module Travis::API::V3
|
||||
class Error < StandardError
|
||||
def self.create(default_message = nil, **options)
|
||||
options[:default_message] = default_message if default_message
|
||||
Class.new(self) { options.each { |key, value| define_singleton_method(key) { value } } }
|
||||
end
|
||||
|
||||
def self.status
|
||||
500
|
||||
end
|
||||
|
||||
def self.type
|
||||
@type ||= name[/[^:]+$/].underscore
|
||||
end
|
||||
|
||||
def self.template
|
||||
'%s'.freeze
|
||||
end
|
||||
|
||||
def self.default_message
|
||||
@default_message ||= Rack::Utils::HTTP_STATUS_CODES.fetch(status, 'unknown error'.freeze).downcase
|
||||
end
|
||||
|
||||
attr_accessor :status, :type, :payload
|
||||
|
||||
def initialize(message = self.class.default_message, status: self.class.status, type: self.class.type, **payload)
|
||||
if message.is_a? Symbol
|
||||
payload[:resource_type] ||= message
|
||||
message = self.class.template % message
|
||||
end
|
||||
|
||||
self.status, self.type, self.payload = status, type, payload
|
||||
super(message)
|
||||
end
|
||||
end
|
||||
end
|
64
lib/travis/api/v3/opt_in.rb
Normal file
64
lib/travis/api/v3/opt_in.rb
Normal file
|
@ -0,0 +1,64 @@
|
|||
module Travis::API::V3
|
||||
class OptIn
|
||||
attr_reader :legacy_stack, :prefix, :router, :accept, :version_header
|
||||
|
||||
def initialize(legacy_stack, prefix: '/v3', router: Router.new, accept: 'application/vnd.travis-ci.3+', version_header: 'Travis-API-Version')
|
||||
@legacy_stack = legacy_stack
|
||||
@prefix = prefix
|
||||
@router = router
|
||||
@accept = accept
|
||||
@version_header = "HTTP_#{version_header.upcase.gsub(/\W/, '_')}"
|
||||
end
|
||||
|
||||
def call(env)
|
||||
return redirect(env) if redirect?(env)
|
||||
|
||||
if matched = matching_env(env)
|
||||
result = @router.call(matched)
|
||||
result, missing = nil, result if cascade?(*result)
|
||||
end
|
||||
|
||||
result = result || legacy_stack.call(env)
|
||||
pick(result, missing)
|
||||
end
|
||||
|
||||
def pick(result, missing)
|
||||
return result if missing.nil?
|
||||
return result if result[0] != 404
|
||||
missing
|
||||
end
|
||||
|
||||
def redirect?(env)
|
||||
env['PATH_INFO'.freeze] == prefix
|
||||
end
|
||||
|
||||
def redirect(env)
|
||||
[307, {'Location'.freeze => env['SCRIPT_NAME'.freeze] + prefix + ?/.freeze, 'Conent-Type'.freeze => 'text/plain'.freeze}, []]
|
||||
end
|
||||
|
||||
def cascade?(status, headers, body)
|
||||
status % 100 == 4 and headers['X-Cascade'.freeze] == 'pass'.freeze
|
||||
end
|
||||
|
||||
def matching_env(env)
|
||||
for_v3 = from_prefix(env) || from_accept(env) || from_version_header(env)
|
||||
for_v3 == true ? env : for_v3
|
||||
end
|
||||
|
||||
def from_prefix(env)
|
||||
return unless prefix and env['PATH_INFO'.freeze].start_with?(prefix + ?/.freeze)
|
||||
env.merge({
|
||||
'SCRIPT_NAME'.freeze => env['SCRIPT_NAME'.freeze] + prefix,
|
||||
'PATH_INFO'.freeze => env['PATH_INFO'.freeze][prefix.size..-1]
|
||||
})
|
||||
end
|
||||
|
||||
def from_accept(env)
|
||||
env['HTTP_ACCEPT'.freeze].include?(accept) if accept and env.include?('HTTP_ACCEPT'.freeze)
|
||||
end
|
||||
|
||||
def from_version_header(env)
|
||||
env[version_header] == '3'.freeze if version_header and env.include?(version_header)
|
||||
end
|
||||
end
|
||||
end
|
14
lib/travis/api/v3/renderer.rb
Normal file
14
lib/travis/api/v3/renderer.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
module Travis::API::V3
|
||||
module Renderer
|
||||
extend self
|
||||
|
||||
def [](key)
|
||||
return key if key.respond_to? :render
|
||||
const_get(key.to_s.camelize)
|
||||
end
|
||||
|
||||
def format_date(date)
|
||||
date && date.strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
end
|
||||
end
|
||||
end
|
14
lib/travis/api/v3/renderer/error.rb
Normal file
14
lib/travis/api/v3/renderer/error.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
module Travis::API::V3
|
||||
module Renderer::Error
|
||||
extend self
|
||||
|
||||
def render(error)
|
||||
{
|
||||
:@type => 'error'.freeze,
|
||||
:error_type => error.type,
|
||||
:error_message => error.message,
|
||||
**error.payload
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
33
lib/travis/api/v3/renderer/repository.rb
Normal file
33
lib/travis/api/v3/renderer/repository.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
module Travis::API::V3
|
||||
module Renderer::Repository
|
||||
DIRECT_ATTRIBUTES = %i[id name slug description github_language private]
|
||||
extend self
|
||||
|
||||
def render(repository)
|
||||
{ :@type => 'repository'.freeze, **direct_attributes(repository), **nested_resources(repository) }
|
||||
end
|
||||
|
||||
def direct_attributes(repository)
|
||||
DIRECT_ATTRIBUTES.map { |a| [a, repository.public_send(a)] }.to_h
|
||||
end
|
||||
|
||||
def nested_resources(repository)
|
||||
{
|
||||
owner: {
|
||||
:@type => repository.owner_type.downcase,
|
||||
:id => repository.owner_id,
|
||||
:login => repository.owner_name
|
||||
},
|
||||
last_build: {
|
||||
:@type => 'build'.freeze,
|
||||
:id => repository.last_build_id,
|
||||
:number => repository.last_build_number,
|
||||
:state => repository.last_build_state.to_s,
|
||||
:duration => repository.last_build_duration,
|
||||
:started_at => Renderer.format_date(repository.last_build_started_at),
|
||||
:finished_at => Renderer.format_date(repository.last_build_finished_at),
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
28
lib/travis/api/v3/result.rb
Normal file
28
lib/travis/api/v3/result.rb
Normal file
|
@ -0,0 +1,28 @@
|
|||
module Travis::API::V3
|
||||
class Result
|
||||
attr_accessor :type, :resource
|
||||
|
||||
def initialize(type, resource = [])
|
||||
@type, @resource = type, resource
|
||||
end
|
||||
|
||||
def respond_to_missing?(method, *)
|
||||
super or method.to_sym == type.to_sym
|
||||
end
|
||||
|
||||
def <<(value)
|
||||
resource << value
|
||||
self
|
||||
end
|
||||
|
||||
def render
|
||||
Renderer[type].render(resource)
|
||||
end
|
||||
|
||||
def method_missing(method, *args)
|
||||
return super unless method.to_sym == type.to_sym
|
||||
raise ArgumentError, 'wrong number of arguments (1 for 0)'.freeze if args.any?
|
||||
resource
|
||||
end
|
||||
end
|
||||
end
|
49
lib/travis/api/v3/router.rb
Normal file
49
lib/travis/api/v3/router.rb
Normal file
|
@ -0,0 +1,49 @@
|
|||
module Travis::API::V3
|
||||
class Router
|
||||
include Travis::API::V3
|
||||
attr_accessor :routes
|
||||
|
||||
def initialize(routes = Routes)
|
||||
@routes = routes
|
||||
routes.draw_routes
|
||||
end
|
||||
|
||||
def call(env)
|
||||
return service_index(env) if env['PATH_INFO'.freeze] == ?/.freeze
|
||||
access_control = AccessControl.new(env)
|
||||
factory, params = routes.factory_for(env['REQUEST_METHOD'.freeze], env['PATH_INFO'.freeze])
|
||||
env_params = params(env)
|
||||
|
||||
raise NotFound unless factory
|
||||
|
||||
service = factory.new(access_control, env_params.merge(params))
|
||||
result = service.run
|
||||
render(result, env_params)
|
||||
rescue Error => error
|
||||
result = Result.new(:error, error)
|
||||
V3.response(result.render, 'X-Cascade'.freeze => 'pass'.freeze, status: error.status)
|
||||
end
|
||||
|
||||
def render(result, env_params)
|
||||
V3.response(result.render)
|
||||
end
|
||||
|
||||
def service_index(env)
|
||||
ServiceIndex.for(env, routes).render(env)
|
||||
end
|
||||
|
||||
def params(env)
|
||||
request = Rack::Request.new(env)
|
||||
params = request.params
|
||||
media_type = request.media_type
|
||||
|
||||
if media_type == 'application/json'.freeze or media_type == 'text/json'.freeze
|
||||
request.body.rewind
|
||||
json_params = env['travis.input.json'.freeze] ||= JSON.load(request.body)
|
||||
params.merge! json_params if json_params.is_a? Hash
|
||||
end
|
||||
|
||||
params
|
||||
end
|
||||
end
|
||||
end
|
11
lib/travis/api/v3/routes.rb
Normal file
11
lib/travis/api/v3/routes.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
module Travis::API::V3
|
||||
module Routes
|
||||
require 'travis/api/v3/routes/dsl'
|
||||
extend DSL
|
||||
|
||||
resource :repository do
|
||||
route '/repo/:id'
|
||||
get :find_repository
|
||||
end
|
||||
end
|
||||
end
|
63
lib/travis/api/v3/routes/dsl.rb
Normal file
63
lib/travis/api/v3/routes/dsl.rb
Normal file
|
@ -0,0 +1,63 @@
|
|||
require 'travis/api/v3/routes/resource'
|
||||
|
||||
module Travis::API::V3
|
||||
module Routes::DSL
|
||||
def routes
|
||||
@routes ||= {}
|
||||
end
|
||||
|
||||
def resources
|
||||
@resources ||= []
|
||||
end
|
||||
|
||||
def current_resource
|
||||
@current_resource ||= nil
|
||||
end
|
||||
|
||||
def resource(type, &block)
|
||||
resource = Routes::Resource.new(type)
|
||||
with_resource(resource, &block)
|
||||
resources << resource
|
||||
end
|
||||
|
||||
def with_resource(resource)
|
||||
resource_was, @current_resource = current_resource, resource
|
||||
yield
|
||||
ensure
|
||||
@current_resource = resource_was
|
||||
end
|
||||
|
||||
def route(value)
|
||||
current_resource.route = value
|
||||
end
|
||||
|
||||
def get(*args)
|
||||
current_resource.add_service('GET'.freeze, *args)
|
||||
end
|
||||
|
||||
def post(*args)
|
||||
current_resource.add_service('POST'.freeze, *args)
|
||||
end
|
||||
|
||||
def draw_routes
|
||||
resources.each do |resource|
|
||||
prefix = resource.route
|
||||
resource.services.each do |(request_method, sub_route), service|
|
||||
route = sub_route ? prefix + sub_route : prefix
|
||||
routes[route] ||= {}
|
||||
routes[route][request_method] = Services[service]
|
||||
end
|
||||
end
|
||||
self.routes.replace(routes)
|
||||
end
|
||||
|
||||
def factory_for(request_method, path)
|
||||
routes.each do |route, method_map|
|
||||
next unless params = route.params(path)
|
||||
raise MethodNotAllowed unless factory = method_map[request_method]
|
||||
return [factory, params]
|
||||
end
|
||||
nil # nothing matched
|
||||
end
|
||||
end
|
||||
end
|
21
lib/travis/api/v3/routes/resource.rb
Normal file
21
lib/travis/api/v3/routes/resource.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
require 'mustermann'
|
||||
|
||||
module Travis::API::V3
|
||||
class Routes::Resource
|
||||
attr_accessor :identifier, :route, :services
|
||||
|
||||
def initialize(identifier)
|
||||
@identifier = identifier
|
||||
@services = {}
|
||||
end
|
||||
|
||||
def add_service(request_method, service, sub_route = nil)
|
||||
sub_route &&= Mustermann.new(sub_route)
|
||||
services[[request_method, sub_route]] = service
|
||||
end
|
||||
|
||||
def route=(value)
|
||||
@route = value ? Mustermann.new(value) : value
|
||||
end
|
||||
end
|
||||
end
|
33
lib/travis/api/v3/service.rb
Normal file
33
lib/travis/api/v3/service.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
module Travis::API::V3
|
||||
class Service
|
||||
def self.required_params
|
||||
@required_params ||= []
|
||||
end
|
||||
|
||||
def self.params(*list, optional: false)
|
||||
@params ||= []
|
||||
list.each do |param|
|
||||
param = param.to_s
|
||||
define_method(param) { params[param] }
|
||||
required_params << param unless optional
|
||||
@params << param
|
||||
end
|
||||
@params
|
||||
end
|
||||
|
||||
attr_accessor :access_control, :params
|
||||
|
||||
def initialize(access_control, params)
|
||||
@access_control = access_control
|
||||
@params = params
|
||||
end
|
||||
|
||||
def required_params?
|
||||
required_params.all? { |param| params.include? param }
|
||||
end
|
||||
|
||||
def required_params
|
||||
self.class.required_params
|
||||
end
|
||||
end
|
||||
end
|
90
lib/travis/api/v3/service_index.rb
Normal file
90
lib/travis/api/v3/service_index.rb
Normal file
|
@ -0,0 +1,90 @@
|
|||
module Travis::API::V3
|
||||
class ServiceIndex
|
||||
ALLOW_POST = ['application/json', 'application/x-www-form-urlencoded', 'multipart/form-data']
|
||||
@index_cache = {}
|
||||
|
||||
def self.for(env, routes)
|
||||
access_factory = AccessControl.new(env).class
|
||||
prefix = env['SCRIPT_NAME'.freeze]
|
||||
@index_cache[[access_factory, routes, prefix]] ||= new(access_factory, routes, prefix)
|
||||
end
|
||||
|
||||
attr_reader :access_factory, :routes, :json_home_response, :json_response, :prefix
|
||||
|
||||
def initialize(access_factory, routes, prefix)
|
||||
@prefix = prefix || ''
|
||||
@access_factory, @routes = access_factory, routes
|
||||
@json_response = V3.response(render_json, content_type: 'application/json'.freeze)
|
||||
@json_home_response = V3.response(render_json_home, content_type: 'application/json-home'.freeze)
|
||||
end
|
||||
|
||||
def render(env)
|
||||
json_home?(env) ? json_home_response : json_response
|
||||
end
|
||||
|
||||
def render_json
|
||||
resources = { }
|
||||
routes.resources.each do |resource|
|
||||
resources[resource.identifier] ||= {}
|
||||
resource.services.each do |(request_method, sub_route), service|
|
||||
service &&= service.to_s.sub(/_#{resource.identifier}$/, ''.freeze)
|
||||
list = resources[resource.identifier][service] ||= []
|
||||
pattern = sub_route ? resource.route + sub_route : resource.route
|
||||
pattern.to_templates.each do |template|
|
||||
list << { 'request-method'.freeze => request_method, 'uri-template'.freeze => prefix + template }
|
||||
end
|
||||
end
|
||||
end
|
||||
{ resources: resources }
|
||||
end
|
||||
|
||||
def render_json_home
|
||||
relations = {}
|
||||
|
||||
routes.resources.each do |resource|
|
||||
resource.services.each do |(request_method, sub_route), service|
|
||||
service &&= service.to_s.sub(/_#{resource.identifier}$/, ''.freeze)
|
||||
pattern = sub_route ? resource.route + sub_route : resource.route
|
||||
relation = "http://schema.travis-ci.com/rel/#{resource.identifier}/#{service}"
|
||||
pattern.to_templates.each do |template|
|
||||
relations[relation] ||= {}
|
||||
relations[relation][template] ||= { allow: [], vars: template.scan(/{\+?([^}]+)}/).flatten }
|
||||
relations[relation][template][:allow] << request_method
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
nested_relations = {}
|
||||
relations.delete_if do |relation, request_map|
|
||||
next if request_map.size < 2
|
||||
common_vars = request_map.values.map { |e| e[:vars] }.inject(:&)
|
||||
request_map.each do |template, payload|
|
||||
special_vars = payload[:vars] - common_vars
|
||||
schema = special_vars.any? ? "#{relation}/by_#{special_vars.join(?_)}" : relation
|
||||
nested_relations[schema] = { template => payload }
|
||||
end
|
||||
end
|
||||
relations.merge! nested_relations
|
||||
|
||||
resources = relations.map do |relation, payload|
|
||||
template, payload = payload.first
|
||||
hints = { 'allow' => payload[:allow] }
|
||||
hints['accept-post'] = ALLOW_POST if payload[:allow].include? 'POST'
|
||||
hints['accept-patch'] = ALLOW_POST if payload[:allow].include? 'PATCH'
|
||||
hints['accept-put'] = ALLOW_POST if payload[:allow].include? 'PUT'
|
||||
hints['representations'] = ['application/json', 'application/vnd.travis-ci.3+json']
|
||||
[relation, {
|
||||
'href-template' => prefix + template,
|
||||
'href-vars' => Hash[payload[:vars].map { |var| [var, "http://schema.travis-ci.com/param/#{var}"] }],
|
||||
'hints' => hints
|
||||
}]
|
||||
end
|
||||
|
||||
{ resources: Hash[resources] }
|
||||
end
|
||||
|
||||
def json_home?(env)
|
||||
env['HTTP_ACCEPT'.freeze] == 'application/json-home'.freeze
|
||||
end
|
||||
end
|
||||
end
|
8
lib/travis/api/v3/services.rb
Normal file
8
lib/travis/api/v3/services.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
module Travis::API::V3
|
||||
module Services
|
||||
def self.[](key)
|
||||
return key if key.respond_to? :new
|
||||
const_get(key.to_s.camelize)
|
||||
end
|
||||
end
|
||||
end
|
22
lib/travis/api/v3/services/find_repository.rb
Normal file
22
lib/travis/api/v3/services/find_repository.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
module Travis::API::V3
|
||||
class Services::FindRepository < Service
|
||||
params :id, :github_id, :slug, optional: true
|
||||
|
||||
def run
|
||||
raise NotFound, :repository unless repository and access_control.visible? repository
|
||||
Result.new(:repository, repository)
|
||||
end
|
||||
|
||||
def repository
|
||||
raise EntityMissing, :repository if defined?(@repository) and @repository.nil?
|
||||
@repository ||= find_repository
|
||||
end
|
||||
|
||||
def find_repository
|
||||
return ::Repository.find_by_id(id) if id
|
||||
return ::Repository.find_by_github_id(github_id) if github_id
|
||||
return ::Repository.by_slug(slug).first if slug
|
||||
raise WrongParams
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,7 @@
|
|||
ENV['RACK_ENV'] = ENV['RAILS_ENV'] = ENV['ENV'] = 'test'
|
||||
|
||||
require 'support/coverage'
|
||||
|
||||
require 'rspec'
|
||||
require 'database_cleaner'
|
||||
require 'sinatra/test_helpers'
|
||||
|
|
7
spec/support/coverage.rb
Normal file
7
spec/support/coverage.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
require 'simplecov'
|
||||
|
||||
SimpleCov.start do
|
||||
coverage_dir '.coverage'
|
||||
add_filter "/spec/"
|
||||
add_group "v3", "lib/travis/api/v3"
|
||||
end
|
14
spec/v3/result_spec.rb
Normal file
14
spec/v3/result_spec.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Travis::API::V3::Result do
|
||||
subject(:result) { described_class.new(:example) }
|
||||
|
||||
example { expect(result.type) .to be == :example }
|
||||
example { expect(result.resource) .to be == [] }
|
||||
example { expect(result.example) .to be == [] }
|
||||
|
||||
example do
|
||||
result << 42
|
||||
expect(result.example).to include(42)
|
||||
end
|
||||
end
|
44
spec/v3/service_index_spec.rb
Normal file
44
spec/v3/service_index_spec.rb
Normal file
|
@ -0,0 +1,44 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Travis::API::V3::ServiceIndex do
|
||||
let(:headers) {{ }}
|
||||
let(:path) { '/' }
|
||||
let(:json) { JSON.load(response.body) }
|
||||
let(:response) { get(path, {}, headers) }
|
||||
|
||||
describe "custom json entry point" do
|
||||
let(:expected_resources) {{
|
||||
"repository" => {
|
||||
"find" => [{"request-method"=>"GET", "uri-template"=>"#{path}repo/{id}"}]
|
||||
}
|
||||
}}
|
||||
|
||||
describe 'with /v3 prefix' do
|
||||
let(:path) { '/v3/' }
|
||||
specify(:resources) { expect(json['resources']).to be == expected_resources }
|
||||
end
|
||||
|
||||
describe 'with Accept header' do
|
||||
let(:headers) { { 'HTTP_ACCEPT' => 'application/vnd.travis-ci.3+json' } }
|
||||
specify(:resources) { expect(json['resources']).to be == expected_resources }
|
||||
end
|
||||
|
||||
describe 'with Travis-API-Version header' do
|
||||
let(:headers) { { 'HTTP_TRAVIS_API_VERSION' => '3' } }
|
||||
specify(:resources) { expect(json['resources']).to be == expected_resources }
|
||||
end
|
||||
end
|
||||
|
||||
describe "json-home document" do
|
||||
describe 'with /v3 prefix' do
|
||||
let(:headers) { { 'HTTP_ACCEPT' => 'application/json-home' } }
|
||||
let(:path) { '/v3/' }
|
||||
specify(:resources) { expect(json['resources']).to include("http://schema.travis-ci.com/rel/repository/find") }
|
||||
end
|
||||
|
||||
describe 'with Travis-API-Version header' do
|
||||
let(:headers) { { 'HTTP_ACCEPT' => 'application/json-home', 'HTTP_TRAVIS_API_VERSION' => '3' } }
|
||||
specify(:resources) { expect(json['resources']).to include("http://schema.travis-ci.com/rel/repository/find") }
|
||||
end
|
||||
end
|
||||
end
|
211
spec/v3/services/find_repository_spec.rb
Normal file
211
spec/v3/services/find_repository_spec.rb
Normal file
|
@ -0,0 +1,211 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Travis::API::V3::Services::FindRepository do
|
||||
let(:repo) { Repository.by_slug('svenfuchs/minimal').first }
|
||||
|
||||
describe "public repository" do
|
||||
before { get("/v3/repo/#{repo.id}") }
|
||||
example { expect(last_response).to be_ok }
|
||||
example { expect(JSON.load(body)).to be == {
|
||||
"@type" => "repository",
|
||||
"id" => repo.id,
|
||||
"name" => "minimal",
|
||||
"slug" => "svenfuchs/minimal",
|
||||
"description" => nil,
|
||||
"github_language" => nil,
|
||||
"private" => false,
|
||||
"owner" => {
|
||||
"@type" => "user",
|
||||
"id" => repo.owner_id,
|
||||
"login" => "svenfuchs" },
|
||||
"last_build" => {
|
||||
"@type" => "build",
|
||||
"id" => repo.last_build_id,
|
||||
"number" => "2",
|
||||
"state" => "passed",
|
||||
"duration" => nil,
|
||||
"started_at" => "2010-11-12T12:30:00Z",
|
||||
"finished_at" => "2010-11-12T12:30:20Z"}
|
||||
}}
|
||||
end
|
||||
|
||||
describe "missing repository" do
|
||||
before { get("/v3/repo/999999999999999") }
|
||||
example { expect(last_response).to be_not_found }
|
||||
example { expect(JSON.load(body)).to be == {
|
||||
"@type" => "error",
|
||||
"error_type" => "not_found",
|
||||
"error_message" => "repository not found (or insufficient access)",
|
||||
"resource_type" => "repository"
|
||||
}}
|
||||
end
|
||||
|
||||
describe "public repository, private API" do
|
||||
before { Travis.config.private_api = true }
|
||||
before { get("/v3/repo/#{repo.id}") }
|
||||
after { Travis.config.private_api = true }
|
||||
example { expect(last_response).to be_not_found }
|
||||
example { expect(JSON.load(body)).to be == {
|
||||
"@type" => "error",
|
||||
"error_type" => "not_found",
|
||||
"error_message" => "repository not found (or insufficient access)",
|
||||
"resource_type" => "repository"
|
||||
}}
|
||||
end
|
||||
|
||||
describe "private repository, not authenticated" do
|
||||
before { repo.update_attribute(:private, true) }
|
||||
before { get("/v3/repo/#{repo.id}") }
|
||||
before { repo.update_attribute(:private, false) }
|
||||
example { expect(last_response).to be_not_found }
|
||||
example { expect(JSON.load(body)).to be == {
|
||||
"@type" => "error",
|
||||
"error_type" => "not_found",
|
||||
"error_message" => "repository not found (or insufficient access)",
|
||||
"resource_type" => "repository"
|
||||
}}
|
||||
end
|
||||
|
||||
describe "private repository, private API, authenticated as user with access" do
|
||||
let(:token) { Travis::Api::App::AccessToken.create(user: repo.owner, app_id: 1) }
|
||||
let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}" }}
|
||||
before { Permission.create(repository: repo, user: repo.owner, pull: true) }
|
||||
before { repo.update_attribute(:private, true) }
|
||||
before { get("/v3/repo/#{repo.id}", {}, headers) }
|
||||
before { repo.update_attribute(:private, false) }
|
||||
example { expect(last_response).to be_ok }
|
||||
example { expect(JSON.load(body)).to be == {
|
||||
"@type" => "repository",
|
||||
"id" => repo.id,
|
||||
"name" => "minimal",
|
||||
"slug" => "svenfuchs/minimal",
|
||||
"description" => nil,
|
||||
"github_language" => nil,
|
||||
"private" => true,
|
||||
"owner" => {
|
||||
"@type" => "user",
|
||||
"id" => repo.owner_id,
|
||||
"login" => "svenfuchs" },
|
||||
"last_build" => {
|
||||
"@type" => "build",
|
||||
"id" => repo.last_build_id,
|
||||
"number" => "2",
|
||||
"state" => "passed",
|
||||
"duration" => nil,
|
||||
"started_at" => "2010-11-12T12:30:00Z",
|
||||
"finished_at" => "2010-11-12T12:30:20Z"}
|
||||
}}
|
||||
end
|
||||
|
||||
describe "private repository, private API, authenticated as user without access" do
|
||||
let(:token) { Travis::Api::App::AccessToken.create(user: User.find(2), app_id: 1) }
|
||||
let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}" }}
|
||||
before { repo.update_attribute(:private, true) }
|
||||
before { get("/v3/repo/#{repo.id}", {}, headers) }
|
||||
before { repo.update_attribute(:private, false) }
|
||||
example { expect(last_response).to be_not_found }
|
||||
example { expect(JSON.load(body)).to be == {
|
||||
"@type" => "error",
|
||||
"error_type" => "not_found",
|
||||
"error_message" => "repository not found (or insufficient access)",
|
||||
"resource_type" => "repository"
|
||||
}}
|
||||
end
|
||||
|
||||
describe "private repository, authenticated as internal application with full access" do
|
||||
let(:app_name) { 'travis-example' }
|
||||
let(:app_secret) { '12345678' }
|
||||
let(:sign_opts) { "a=#{app_name}" }
|
||||
let(:signature) { OpenSSL::HMAC.hexdigest('sha256', app_secret, sign_opts) }
|
||||
let(:headers) {{ 'HTTP_AUTHORIZATION' => "signature #{sign_opts}:#{signature}" }}
|
||||
before { Travis.config.applications = { app_name => { full_access: true, secret: app_secret }}}
|
||||
|
||||
|
||||
before { repo.update_attribute(:private, true) }
|
||||
before { get("/v3/repo/#{repo.id}", {}, headers) }
|
||||
before { repo.update_attribute(:private, false) }
|
||||
|
||||
|
||||
example { expect(last_response).to be_ok }
|
||||
example { expect(JSON.load(body)).to be == {
|
||||
"@type" => "repository",
|
||||
"id" => repo.id,
|
||||
"name" => "minimal",
|
||||
"slug" => "svenfuchs/minimal",
|
||||
"description" => nil,
|
||||
"github_language" => nil,
|
||||
"private" => true,
|
||||
"owner" => {
|
||||
"@type" => "user",
|
||||
"id" => repo.owner_id,
|
||||
"login" => "svenfuchs" },
|
||||
"last_build" => {
|
||||
"@type" => "build",
|
||||
"id" => repo.last_build_id,
|
||||
"number" => "2",
|
||||
"state" => "passed",
|
||||
"duration" => nil,
|
||||
"started_at" => "2010-11-12T12:30:00Z",
|
||||
"finished_at" => "2010-11-12T12:30:20Z"}
|
||||
}}
|
||||
end
|
||||
|
||||
describe "private repository, authenticated as internal application with full access, but scoped to a different org" do
|
||||
let(:app_name) { 'travis-example' }
|
||||
let(:app_secret) { '12345678' }
|
||||
let(:sign_opts) { "a=#{app_name}:s=travis-pro" }
|
||||
let(:signature) { OpenSSL::HMAC.hexdigest('sha256', app_secret, sign_opts) }
|
||||
let(:headers) {{ 'HTTP_AUTHORIZATION' => "signature #{sign_opts}:#{signature}" }}
|
||||
before { Travis.config.applications = { app_name => { full_access: true, secret: app_secret }}}
|
||||
|
||||
before { repo.update_attribute(:private, true) }
|
||||
before { get("/v3/repo/#{repo.id}", {}, headers) }
|
||||
before { repo.update_attribute(:private, false) }
|
||||
|
||||
example { expect(last_response).to be_not_found }
|
||||
example { expect(JSON.load(body)).to be == {
|
||||
"@type" => "error",
|
||||
"error_type" => "not_found",
|
||||
"error_message" => "repository not found (or insufficient access)",
|
||||
"resource_type" => "repository"
|
||||
}}
|
||||
end
|
||||
|
||||
describe "private repository, authenticated as internal application with full access, scoped to the right org" do
|
||||
let(:app_name) { 'travis-example' }
|
||||
let(:app_secret) { '12345678' }
|
||||
let(:sign_opts) { "a=#{app_name}:s=#{repo.owner_name}" }
|
||||
let(:signature) { OpenSSL::HMAC.hexdigest('sha256', app_secret, sign_opts) }
|
||||
let(:headers) {{ 'HTTP_AUTHORIZATION' => "signature #{sign_opts}:#{signature}" }}
|
||||
before { Travis.config.applications = { app_name => { full_access: true, secret: app_secret }}}
|
||||
|
||||
|
||||
before { repo.update_attribute(:private, true) }
|
||||
before { get("/v3/repo/#{repo.id}", {}, headers) }
|
||||
before { repo.update_attribute(:private, false) }
|
||||
|
||||
|
||||
example { expect(last_response).to be_ok }
|
||||
example { expect(JSON.load(body)).to be == {
|
||||
"@type" => "repository",
|
||||
"id" => repo.id,
|
||||
"name" => "minimal",
|
||||
"slug" => "svenfuchs/minimal",
|
||||
"description" => nil,
|
||||
"github_language" => nil,
|
||||
"private" => true,
|
||||
"owner" => {
|
||||
"@type" => "user",
|
||||
"id" => repo.owner_id,
|
||||
"login" => "svenfuchs" },
|
||||
"last_build" => {
|
||||
"@type" => "build",
|
||||
"id" => repo.last_build_id,
|
||||
"number" => "2",
|
||||
"state" => "passed",
|
||||
"duration" => nil,
|
||||
"started_at" => "2010-11-12T12:30:00Z",
|
||||
"finished_at" => "2010-11-12T12:30:20Z"}
|
||||
}}
|
||||
end
|
||||
end
|
|
@ -73,6 +73,7 @@ Gem::Specification.new do |s|
|
|||
"config/database.yml",
|
||||
"config/puma-config.rb",
|
||||
"config/unicorn.rb",
|
||||
"lib/conditional_skylight.rb",
|
||||
"lib/tasks/build_update_branch.rake",
|
||||
"lib/tasks/build_update_pull_request_data.rake",
|
||||
"lib/tasks/encrypt_all_data.rake",
|
||||
|
@ -160,6 +161,29 @@ Gem::Specification.new do |s|
|
|||
"lib/travis/api/v2/http/ssl_key.rb",
|
||||
"lib/travis/api/v2/http/user.rb",
|
||||
"lib/travis/api/v2/http/validation_error.rb",
|
||||
"lib/travis/api/v3.rb",
|
||||
"lib/travis/api/v3/access_control.rb",
|
||||
"lib/travis/api/v3/access_control/anonymous.rb",
|
||||
"lib/travis/api/v3/access_control/application.rb",
|
||||
"lib/travis/api/v3/access_control/generic.rb",
|
||||
"lib/travis/api/v3/access_control/legacy_token.rb",
|
||||
"lib/travis/api/v3/access_control/scoped.rb",
|
||||
"lib/travis/api/v3/access_control/signature.rb",
|
||||
"lib/travis/api/v3/access_control/user.rb",
|
||||
"lib/travis/api/v3/error.rb",
|
||||
"lib/travis/api/v3/opt_in.rb",
|
||||
"lib/travis/api/v3/renderer.rb",
|
||||
"lib/travis/api/v3/renderer/error.rb",
|
||||
"lib/travis/api/v3/renderer/repository.rb",
|
||||
"lib/travis/api/v3/result.rb",
|
||||
"lib/travis/api/v3/router.rb",
|
||||
"lib/travis/api/v3/routes.rb",
|
||||
"lib/travis/api/v3/routes/dsl.rb",
|
||||
"lib/travis/api/v3/routes/resource.rb",
|
||||
"lib/travis/api/v3/service.rb",
|
||||
"lib/travis/api/v3/service_index.rb",
|
||||
"lib/travis/api/v3/services.rb",
|
||||
"lib/travis/api/v3/services/find_repository.rb",
|
||||
"lib/travis/private_key.rb",
|
||||
"public/favicon.ico",
|
||||
"public/images/result/canceled.png",
|
||||
|
@ -202,6 +226,7 @@ Gem::Specification.new do |s|
|
|||
"spec/integration/v2_spec.backup.rb",
|
||||
"spec/integration/version_spec.rb",
|
||||
"spec/spec_helper.rb",
|
||||
"spec/support/coverage.rb",
|
||||
"spec/support/formats.rb",
|
||||
"spec/support/matchers.rb",
|
||||
"spec/unit/access_token_spec.rb",
|
||||
|
@ -253,6 +278,9 @@ Gem::Specification.new do |s|
|
|||
"spec/unit/middleware/user_agent_tracker_spec.rb",
|
||||
"spec/unit/responders/json_spec.rb",
|
||||
"spec/unit/responders/service_spec.rb",
|
||||
"spec/v3/result_spec.rb",
|
||||
"spec/v3/service_index_spec.rb",
|
||||
"spec/v3/services/find_repository_spec.rb",
|
||||
"tmp/.gitkeep",
|
||||
"travis-api.gemspec"
|
||||
]
|
||||
|
@ -260,7 +288,6 @@ Gem::Specification.new do |s|
|
|||
s.add_dependency 'travis-support'
|
||||
s.add_dependency 'travis-core'
|
||||
|
||||
s.add_dependency 'backports', '~> 2.5'
|
||||
s.add_dependency 'pg', '~> 0.13.2'
|
||||
s.add_dependency 'thin', '~> 1.4'
|
||||
s.add_dependency 'sinatra', '~> 1.3'
|
||||
|
|
Loading…
Reference in New Issue
Block a user