rack-auth-travis 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 66e2120bb880c54b9c8fc6de7f4c71ba04e0eb8d
4
+ data.tar.gz: 93fc97948dcc29135dd0373c90087b6d94e9ca33
5
+ SHA512:
6
+ metadata.gz: 5945ea2ec8ede2f9541208be47db7b521c3e3969da17e5d8c69aae8a453117fc909018963519929167c5ddac4283fb67e08a6ced1b06e02406beeeba581a00ad
7
+ data.tar.gz: 505976820d8c6c7edf49a485e263cb57aad7b550099e5777764c81a990c7cbc02a7428e7e632e36f992599d4e71d0687ec6a66b2d826bb53727132c711deb64b
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ /.bundle/
4
+ /.config/
5
+ /.yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /example/public/sha256.js
9
+ /lib/bundler/man/
10
+ /pkg/
11
+ /rdoc/
12
+ /spec/reports/
13
+ /test/tmp/
14
+ /test/version_tmp/
15
+ /tmp/
16
+ Gemfile.lock
17
+ rack-output.html
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,8 @@
1
+ AllCops:
2
+ Includes:
3
+ - Gemfile
4
+ - Rakefile
5
+ - Guardfile
6
+
7
+ Documentation:
8
+ Enabled: false
@@ -0,0 +1 @@
1
+ 2.0.0-p247
@@ -0,0 +1,5 @@
1
+ if ENV['COVERAGE']
2
+ SimpleCov.start do
3
+ add_filter '/spec/'
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
5
+ - jruby-19mode
6
+ - rbx-19mode
7
+ env:
8
+ global:
9
+ - COVERAGE=1
10
+ notifications:
11
+ email: github+rack-auth-travis@modcloth.com
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # vim:fileencoding=utf-8
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
@@ -0,0 +1,7 @@
1
+ # vim:fileencoding=utf-8
2
+
3
+ guard :rspec, cli: '--format doc' do
4
+ watch(/^spec\/.+_spec\.rb$/)
5
+ watch(/^lib\/(.+)\.rb$/) { |m| "spec/lib/#{m[1]}_spec.rb" }
6
+ watch('spec/spec_helper.rb') { 'spec' }
7
+ end
@@ -0,0 +1,22 @@
1
+ # Copyright © 2013 ModCloth, Inc.
2
+
3
+ ## MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,39 @@
1
+ # Rack::Auth::Travis
2
+
3
+ It's a Rack auth thing for Travis webhook requests!
4
+
5
+ ## Install
6
+
7
+ Depend on it!
8
+
9
+ ``` ruby
10
+ # Gemfile
11
+ gem 'rack-auth-travis'
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ Add it to your rack!
17
+
18
+ Take a look at the [example](./example) or do something like this:
19
+
20
+ ``` ruby
21
+ # config.ru
22
+ require './my_fancy_app'
23
+ require 'rack-auth-travis'
24
+
25
+ use Rack::Auth::Travis
26
+ run MyFancyApp.new
27
+ ```
28
+
29
+ When `use`'d without arguments, `Rack::Auth::Travis` will add an `ENV`-based
30
+ authenticator that will look for the following variables and use their values to
31
+ authenticate the `Authorization` header:
32
+
33
+ - `TRAVIS_AUTH_<uppercased-slug>` - generated from the "repo slug" with
34
+ non-alphanumeric characters replaced with underscores, e.g.
35
+ `octocat/Knife-Spoon` becomes `TRAVIS_AUTH_OCTOCAT_KNIFE_SPOON`
36
+ - `TRAVIS_AUTH_DEFAULT`
37
+
38
+ If one of these `ENV` variables exists, its value will be hashed with the "repo
39
+ slug" and compared to the `Authorization` header sent from Travis.
@@ -0,0 +1,17 @@
1
+ # vim:fileencoding=utf-8
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec) do |t|
7
+ t.rspec_opts = '-f doc'
8
+ end
9
+
10
+ load './example/Rakefile'
11
+
12
+ desc 'Run rubocop'
13
+ task :rubocop do
14
+ sh('rubocop --format simple') { |ok, _| ok || abort }
15
+ end
16
+
17
+ task default: [:rubocop, :spec]
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+ # vim:fileencoding=utf-8
3
+ require 'json'
4
+ require 'rack-auth-travis'
5
+
6
+ if ARGV.include?('-h') || ARGV.include?('--help')
7
+ $stdout.puts <<-EOUSAGE.gsub(/^ {4}/, '')
8
+ Usage: #{File.basename($PROGRAM_NAME)} < repo-slug-token-map.json
9
+
10
+ Convert a JSON mapping of repo slugs to auth tokens into environment
11
+ variables usable by the Rack::Auth::Travis ENV-based authenticator.
12
+ EOUSAGE
13
+ exit 0
14
+ end
15
+
16
+ JSON.parse($stdin.read).each do |k, v|
17
+ $stdout.puts "#{Rack::Auth::Travis.repo_env_key(k)}=#{v}"
18
+ end
19
+ exit 0
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+ # vim:fileencoding=utf-8
3
+ require 'json'
4
+ require 'rack-auth-travis'
5
+
6
+ def usage
7
+ $stdout.puts <<-EOUSAGE.gsub(/^ {4}/, '')
8
+ Usage: #{File.basename($PROGRAM_NAME)} <repo-slug> <token>
9
+
10
+ Get the sha256 hash of a "repo slug" and Travis token just like the one
11
+ expected in the 'Authorization' header of a webhook request.
12
+ EOUSAGE
13
+ end
14
+
15
+ if ARGV.include?('-h') || ARGV.include?('--help') || !ARGV[0] || !ARGV[1]
16
+ usage
17
+ exit 0
18
+ end
19
+
20
+ owner_name, name = ARGV[0].split('/', 2)
21
+ $stdout.puts Rack::Auth::Travis.authz(owner_name, name, ARGV[1])
22
+ exit 0
@@ -0,0 +1,9 @@
1
+ # vim:fileencoding=utf-8
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'puma'
6
+ gem 'rack'
7
+ gem 'rack-auth-travis', path: '../'
8
+ gem 'rake'
9
+ gem 'sinatra'
@@ -0,0 +1,48 @@
1
+ # vim:fileencoding=utf-8
2
+
3
+ desc 'POST a valid payload to the example app (or anywhere else)'
4
+ task :payload do
5
+ require 'rack-auth-travis'
6
+ require 'net/http'
7
+ require_relative 'spec/support'
8
+
9
+ owner_name = ENV['OWNER_NAME'] || 'octocat'
10
+ name = ENV['NAME'] || 'Knife-Spoon'
11
+ ENV['TRAVIS_AUTH_DEFAULT'] ||= 'a' * 64
12
+ token = ENV['TRAVIS_AUTH_DEFAULT']
13
+ repo_env_key = Rack::Auth::Travis.repo_env_key("#{owner_name}/#{name}")
14
+ ENV[repo_env_key] ||= token
15
+ host = ENV['HOST'] || 'localhost'
16
+ port = Integer(ENV['PORT'] || 9292)
17
+
18
+ http = Net::HTTP.new(host, port)
19
+ req = Net::HTTP::Post.new('/')
20
+ req['Accept'] = 'application/json'
21
+ req['Content-Type'] = 'application/json'
22
+ req['Authorization'] = Rack::Auth::Travis.authz(owner_name, name, token)
23
+ req.body = JSON.pretty_generate(Support.valid_payload(owner_name, name))
24
+
25
+ $stdout.puts <<-EOF.gsub(/^ {4}/, '')
26
+ OWNER_NAME=#{owner_name}
27
+ NAME=#{name}
28
+ TRAVIS_AUTH_DEFAULT=#{token}
29
+ #{repo_env_key}=#{token}
30
+ HOST=#{host}
31
+ PORT=#{port}
32
+
33
+ Authorization: #{req['Authorization']}
34
+
35
+ EOF
36
+ http.set_debug_output($stdout)
37
+ reqbody = http.request(req).body
38
+ File.open('rack-output.html', 'w') { |f| f.puts reqbody }
39
+ $stdout.puts reqbody
40
+ end
41
+
42
+ desc 'Run example application'
43
+ task :example do
44
+ Dir.chdir(File.expand_path('../', __FILE__)) do
45
+ ENV['BUNDLE_GEMFILE'] = nil
46
+ exec 'bundle exec rackup -I$PWD/../lib'
47
+ end
48
+ end
@@ -0,0 +1,32 @@
1
+ # vim:fileencoding=utf-8
2
+
3
+ require 'open-uri'
4
+ require 'rack/lint'
5
+ require 'rack/urlmap'
6
+ require 'rack-auth-travis'
7
+ require './echoplex'
8
+
9
+ ENV['TRAVIS_AUTH_DEFAULT'] ||= 'a' * 20
10
+ ENV['TRAVIS_REPO_OWNER_NAME'] ||= 'foo'
11
+ ENV['TRAVIS_REPO_NAME'] ||= 'bar'
12
+
13
+ sha256js = File.expand_path('../public/sha256.js', __FILE__)
14
+
15
+ if !File.exist?(sha256js)
16
+ File.open(sha256js, 'w') do |f|
17
+ open(
18
+ 'http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha256.js'
19
+ ) do |uri_f|
20
+ uri_f.each_line { |l| f.write l }
21
+ end
22
+ end
23
+ end
24
+
25
+ app = Echoplex::App.new
26
+ protected_app = Rack::Auth::Travis.new(app, realm: 'echoplex')
27
+
28
+ run Rack::URLMap.new(
29
+ '/unprotected' => Rack::Lint.new(app),
30
+ '/protected' => Rack::Lint.new(protected_app),
31
+ '/' => Rack::Directory.new(File.expand_path('../public', __FILE__))
32
+ )
@@ -0,0 +1,51 @@
1
+ # vim:fileencoding=utf-8
2
+
3
+ require 'json'
4
+ require 'sinatra/base'
5
+
6
+ module Echoplex
7
+ class App < Sinatra::Base
8
+ set :public_dir, File.expand_path('../public', __FILE__)
9
+
10
+ get '/' do
11
+ @owner_name = ENV['TRAVIS_REPO_OWNER_NAME']
12
+ @name = ENV['TRAVIS_REPO_NAME']
13
+ @token = ENV['TRAVIS_AUTH_DEFAULT']
14
+ @default_json = JSON.pretty_generate(
15
+ payload: { repository: { owner_name: @owner_name, name: @name } }
16
+ )
17
+ erb :index
18
+ end
19
+
20
+ post '/' do
21
+ rack_input, input_json, error = extract_post_data(request)
22
+ @authz = request.env['HTTP_AUTHORIZATION']
23
+ @input_length = rack_input.length
24
+ @owner_name = (
25
+ (input_json['payload'] || {})['repository'] || {}
26
+ )['owner_name']
27
+ @name = (
28
+ (input_json['payload'] || {})['repository'] || {}
29
+ )['name']
30
+ @error = error
31
+ @env = request.env
32
+ erb :result, layout: !request.xhr?
33
+ end
34
+
35
+ private
36
+
37
+ def extract_post_data(request)
38
+ rack_input = request.env['rack.input'].read
39
+ input_json = {}
40
+ error = nil
41
+
42
+ begin
43
+ input_json = JSON.parse(rack_input)
44
+ rescue => e
45
+ error = "#{e.class.name} #{e.message}"
46
+ end
47
+
48
+ [rack_input, input_json, error]
49
+ end
50
+ end
51
+ end
File without changes
@@ -0,0 +1,72 @@
1
+ <form action="protected" method="post">
2
+ <div>
3
+ <label for="owner_name">Repo Owner</label>
4
+ </div>
5
+ <div>
6
+ <input type="text" id="owner_name" name="owner_name"
7
+ placeholder="<github user or org>" value="<%= @owner_name %>"
8
+ size="40" autofocus />
9
+ </div>
10
+ <div>
11
+ <label for="name">Repo Name</label>
12
+ </div>
13
+ <div>
14
+ <input type="text" id="name" name="name"
15
+ placeholder="<github repo>" value="<%= @name %>"
16
+ size="40" />
17
+ </div>
18
+ <div>
19
+ <label for="token">Token</label>
20
+ </div>
21
+ <div>
22
+ <input type="text" id="token" name="token"
23
+ placeholder="<travis token>" value="<%= @token %>"
24
+ size="30" maxlength="20" />
25
+ </div>
26
+ <div>
27
+ <label for="payload">JSON payload</label>
28
+ </div>
29
+ <textarea id="payload" name="payload" rows="20" cols="80">
30
+ <%= @default_json %>
31
+ </textarea>
32
+ <div>
33
+ <button id="fake_submit">POST</button>
34
+ </div>
35
+ </form>
36
+ <script
37
+ src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js">
38
+ </script>
39
+ <script src="/sha256.js"></script>
40
+ <script type="text/javascript">
41
+ $( function () {
42
+ function travisAuthz ( ownerName, name, token ) {
43
+ var toHash = ownerName + '/' + name + token;
44
+ return CryptoJS.SHA256( toHash ).toString( CryptoJS.enc.Hex );
45
+ }
46
+
47
+ $( '#fake_submit' ).click( function ( event ) {
48
+ event.preventDefault();
49
+ var payload = $( '#payload' ).val();
50
+ console.log( 'sending payload %o', payload );
51
+ $.ajax({
52
+ url: 'protected',
53
+ type: 'POST',
54
+ headers: {
55
+ 'Authorization': travisAuthz(
56
+ $( '#owner_name' ).val(),
57
+ $( '#name' ).val(),
58
+ $( '#token' ).val()
59
+ ),
60
+ 'Accept': 'text/html'
61
+ },
62
+ contentType: 'application/json',
63
+ data: payload,
64
+ dataType: 'html',
65
+ success: function( data ) {
66
+ window.history.pushState(null, "result", "protected");
67
+ $( '#content' ).html( data );
68
+ }
69
+ });
70
+ });
71
+ });
72
+ </script>
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>rack-auth-travis example</title>
5
+ <style type="text/css">body { font-family: Menlo, monospace; }</style>
6
+ </head>
7
+ <body>
8
+ <h1>rack-auth-travis example</h1>
9
+ <div id="content">
10
+ <%= yield %>
11
+ </div>
12
+ </body>
13
+ </html>
@@ -0,0 +1,17 @@
1
+ <p><a id="redo" href="unprotected">again again!</a></p>
2
+ <dl>
3
+ <dt>authz</dt>
4
+ <dd><%= @authz.inspect %></dd>
5
+ <dt>rack input length</dt>
6
+ <dd><%= @input_length.inspect %></dd>
7
+ <dt>repo owner name</dt>
8
+ <dd><%= @owner_name.inspect %></dd>
9
+ <dt>repo name</dt>
10
+ <dd><%= @name.inspect %></dd>
11
+ <dt>error</dt>
12
+ <dd><%= @error.inspect %></dd>
13
+ </dl>
14
+
15
+ <% @env.sort.each do |k, v| %>
16
+ <!-- <%= k.to_s.gsub(/--/, '- - ') %>=<%= v.to_s.gsub(/--/, '- - ') %> -->
17
+ <% end %>
@@ -0,0 +1,3 @@
1
+ # vim:fileencoding=utf-8
2
+
3
+ require 'rack/auth/travis'
@@ -0,0 +1,153 @@
1
+ # vim:fileencoding=utf-8
2
+
3
+ require 'digest'
4
+ require 'json'
5
+ require 'rack/auth/abstract/handler'
6
+ require 'rack/auth/abstract/request'
7
+
8
+ module Rack
9
+ module Auth
10
+ class Travis < ::Rack::Auth::AbstractHandler
11
+ VERSION = '0.1.0'
12
+
13
+ def self.authz(owner_name, name, token)
14
+ ::Digest::SHA256.hexdigest([owner_name, name].join('/') + token)
15
+ end
16
+
17
+ def self.repo_env_key(repo_slug)
18
+ "TRAVIS_AUTH_#{repo_slug.gsub(/[^\p{Alnum}]/, '_').upcase}"
19
+ end
20
+
21
+ def self.valid?(env)
22
+ ::Rack::Auth::Travis::Request.new(env).valid?
23
+ end
24
+
25
+ def self.default_authenticators
26
+ [
27
+ ::Rack::Auth::Travis::ENVAuthenticator.new
28
+ ]
29
+ end
30
+
31
+ def initialize(app, config = {}, &authenticator)
32
+ @config = config
33
+ @config[:sources] ||= [:env]
34
+ super(app, config[:realm], &authenticator)
35
+ configure_authenticators
36
+ end
37
+
38
+ def call(env)
39
+ auth_req = Travis::Request.new(env, @authenticators)
40
+ return unauthorized unless auth_req.provided?
41
+ return bad_request unless auth_req.travis? && auth_req.json?
42
+ return @app.call(env) if auth_req.valid?
43
+ unauthorized
44
+ end
45
+
46
+ def build_env_authenticator
47
+ ENVAuthenticator.new
48
+ end
49
+
50
+ def configure_authenticators
51
+ @authenticators = []
52
+ @config[:sources].each do |source|
53
+ method_name = "build_#{source}_authenticator"
54
+ @authenticators << send(method_name) if respond_to?(method_name)
55
+ end
56
+ if @authenticator
57
+ @authenticators << DIYAuthenticator.new(@authenticator)
58
+ end
59
+ end
60
+
61
+ def challenge
62
+ %Q(Travis realm="#{realm}")
63
+ end
64
+
65
+ class DIYAuthenticator
66
+ def initialize(authenticator_block)
67
+ @authenticator_block = authenticator_block
68
+ end
69
+
70
+ def valid?(auth_req)
71
+ @authenticator_block.call(auth_req.repo_slug, auth_req.token)
72
+ end
73
+ end
74
+
75
+ class ENVAuthenticator
76
+ def valid?(auth_req)
77
+ [
78
+ Travis.repo_env_key(auth_req.repo_slug),
79
+ 'TRAVIS_AUTH_DEFAULT'
80
+ ].each do |k|
81
+ env_auth_token = ENV[k]
82
+ next unless env_auth_token
83
+ return true if auth_req.token == authz(auth_req, env_auth_token)
84
+ end
85
+ false
86
+ end
87
+
88
+ def authz(auth_req, env_auth_token)
89
+ Travis.authz(auth_req.owner_name, auth_req.name, env_auth_token)
90
+ end
91
+ end
92
+
93
+ class Request < ::Rack::Auth::AbstractRequest
94
+ JSON_REGEXP = /^application\/([\w!#\$%&\*`\-\.\^~]*\+)?json$/i
95
+
96
+ def initialize(env, authenticators = Travis.default_authenticators)
97
+ super(env)
98
+ @authenticators = authenticators || []
99
+ end
100
+
101
+ def travis?
102
+ token =~ /[\da-f]{64}/i
103
+ end
104
+
105
+ def json?
106
+ request.env['CONTENT_TYPE'] =~ JSON_REGEXP
107
+ end
108
+
109
+ def valid?
110
+ return false unless provided? && travis? && json?
111
+ @authenticators.each do |authenticator|
112
+ return true if authenticator.valid?(self)
113
+ end
114
+ false
115
+ end
116
+
117
+ def owner_name
118
+ @owner ||= repository['owner_name']
119
+ end
120
+
121
+ def name
122
+ @name ||= repository['name']
123
+ end
124
+
125
+ def repo_slug
126
+ [owner_name, name].join('/')
127
+ end
128
+
129
+ def token
130
+ @token ||= parts.first.to_s
131
+ end
132
+
133
+ private
134
+
135
+ def repository
136
+ @repository ||= ((
137
+ JSON.parse(request_body) || {}
138
+ )['payload'] || {})['repository'] || {}
139
+ rescue
140
+ {}
141
+ end
142
+
143
+ def request_body
144
+ @request_body ||= begin
145
+ body = request.env['rack.input'].read
146
+ request.env['rack.input'].rewind
147
+ body
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,28 @@
1
+ # vim:fileencoding=utf-8
2
+ Gem::Specification.new do |spec|
3
+ spec.name = 'rack-auth-travis'
4
+ spec.version = '0.1.0'
5
+ spec.authors = ['Dan Buch']
6
+ spec.email = ['d.buch@modcloth.com']
7
+ spec.summary = %q{Rack auth for Travis CI webhook requests!}
8
+ spec.description = spec.summary
9
+ spec.homepage = 'https://github.com/modcloth-labs/rack-auth-travis'
10
+ spec.license = 'MIT'
11
+
12
+ spec.files = `git ls-files`.split($/)
13
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
14
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
15
+ spec.require_paths = ['lib']
16
+
17
+ spec.add_runtime_dependency 'rack'
18
+
19
+ spec.add_development_dependency 'bundler', '~> 1.3'
20
+ spec.add_development_dependency 'guard-rspec'
21
+ spec.add_development_dependency 'puma'
22
+ spec.add_development_dependency 'rack-test'
23
+ spec.add_development_dependency 'rake'
24
+ spec.add_development_dependency 'rspec'
25
+ spec.add_development_dependency 'rubocop'
26
+ spec.add_development_dependency 'shotgun'
27
+ spec.add_development_dependency 'simplecov'
28
+ end
@@ -0,0 +1,151 @@
1
+ # vim:fileencoding=utf-8
2
+
3
+ require 'base64'
4
+ require 'json'
5
+ require 'rack/lobster'
6
+
7
+ describe Rack::Auth::Travis do
8
+ include Rack::Test::Methods
9
+
10
+ let :unprotected_app do
11
+ Rack::Lobster.new
12
+ end
13
+
14
+ subject :protected_app do
15
+ described_class.new(unprotected_app)
16
+ end
17
+
18
+ let :owner do
19
+ "octocat#{rand(10..19)}"
20
+ end
21
+
22
+ let :repo do
23
+ "deathrace#{rand(2000..2999)}"
24
+ end
25
+
26
+ let :repo_slug do
27
+ [owner, repo].join('/')
28
+ end
29
+
30
+ let :token do
31
+ Base64.encode64("#{rand(100..999)}#{rand(10e4..10e5)}")[0, 20]
32
+ end
33
+
34
+ let :valid_auth_header do
35
+ described_class.authz(owner, repo, token)
36
+ end
37
+
38
+ let :valid_payload do
39
+ Support.valid_payload(owner, repo)
40
+ end
41
+
42
+ let :valid_payload_json do
43
+ JSON.pretty_generate(valid_payload)
44
+ end
45
+
46
+ it 'inherits from Rack::Auth::AbstractHandler' do
47
+ subject.class.superclass.should == Rack::Auth::AbstractHandler
48
+ end
49
+
50
+ it 'accepts config as a hash' do
51
+ described_class.new(unprotected_app, realm: 'foo').realm.should == 'foo'
52
+ end
53
+
54
+ it 'provides an array of default authenticators' do
55
+ described_class.default_authenticators.should_not be_empty
56
+ end
57
+
58
+ it 'provides a `.valid?` method for checking arbitrary request envs' do
59
+ described_class.valid?('HTTP_JUST_KIDDING' => '1').should be_false
60
+ end
61
+
62
+ context 'when initialized with an authenticator block' do
63
+ it 'adds a DIYAuthenticator' do
64
+ handler = described_class.new(unprotected_app) { |r, t| true }
65
+ authenticators = handler.instance_variable_get(:@authenticators)
66
+ authenticators.map do |a|
67
+ a.class.name
68
+ end.should include('Rack::Auth::Travis::DIYAuthenticator')
69
+ end
70
+
71
+ context 'when the request is authenticated' do
72
+ def app
73
+ described_class.new(unprotected_app) { |r, t| true }
74
+ end
75
+
76
+ it 'responds 200' do
77
+ post '/', valid_payload_json, {
78
+ 'HTTP_AUTHORIZATION' => valid_auth_header,
79
+ 'CONTENT_TYPE' => 'application/json'
80
+ }
81
+ last_response.status.should == 200
82
+ end
83
+ end
84
+ end
85
+
86
+ context 'when the payload is not valid JSON' do
87
+ def app
88
+ protected_app
89
+ end
90
+
91
+ it 'does not explode' do
92
+ post '/', '{asd', {
93
+ 'HTTP_AUTHORIZATION' => 'a' * 64,
94
+ 'CONTENT_TYPE' => 'application/json'
95
+ }
96
+ (last_response.status < 500).should be_true
97
+ end
98
+ end
99
+
100
+ context 'when initialized without config or block' do
101
+ it 'adds an ENVAuthenticator' do
102
+ handler = described_class.new(unprotected_app)
103
+ authenticators = handler.instance_variable_get(:@authenticators)
104
+ authenticators.map do |a|
105
+ a.class.name
106
+ end.should == ['Rack::Auth::Travis::ENVAuthenticator']
107
+ end
108
+ end
109
+
110
+ context 'when no Authorization is provided' do
111
+ def app
112
+ protected_app
113
+ end
114
+
115
+ it 'responds 401' do
116
+ post '/foo', '{}'
117
+ last_response.status.should == 401
118
+ end
119
+ end
120
+
121
+ context 'when invalid Authorization is provided' do
122
+ def app
123
+ protected_app
124
+ end
125
+
126
+ it 'responds 401' do
127
+ post '/', valid_payload_json, {
128
+ 'HTTP_AUTHORIZATION' => 'a' * 64,
129
+ 'CONTENT_TYPE' => 'application/json'
130
+ }
131
+ last_response.status.should == 401
132
+ end
133
+ end
134
+
135
+ context 'when valid Authorization is provided' do
136
+ def app
137
+ protected_app
138
+ end
139
+
140
+ it 'responds 200' do
141
+ env_key = described_class.repo_env_key(repo_slug)
142
+ ENV[env_key] = token
143
+ post '/', valid_payload_json, {
144
+ 'HTTP_AUTHORIZATION' => valid_auth_header,
145
+ 'CONTENT_TYPE' => 'application/json'
146
+ }
147
+ ENV[env_key] = nil
148
+ last_response.status.should == 200
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,7 @@
1
+ # vim:fileencoding=utf-8
2
+
3
+ require 'rack/test'
4
+ require_relative 'support'
5
+
6
+ require 'simplecov'
7
+ require 'rack/auth/travis'
@@ -0,0 +1,83 @@
1
+ # vim:fileencoding=utf-8
2
+
3
+ class Support
4
+ PAYLOAD_TMPL = {
5
+ 'payload' => {
6
+ 'id' => 1,
7
+ 'number' => 1,
8
+ 'status' => nil,
9
+ 'started_at' => nil,
10
+ 'finished_at' => nil,
11
+ 'status_message' => 'Passed',
12
+ 'commit' => '62aae5f70ceee39123ef',
13
+ 'branch' => 'master',
14
+ 'message' => 'the commit message',
15
+ 'compare_url' => (
16
+ 'https://github.com/___OWNER___/minimal/compare/master...develop'
17
+ ),
18
+ 'committed_at' => '2011-11-11T11: 11: 11Z',
19
+ 'committer_name' => 'Sven Fuchs',
20
+ 'committer_email' => 'svenfuchs@artweb-design.de',
21
+ 'author_name' => 'Sven Fuchs',
22
+ 'author_email' => 'svenfuchs@artweb-design.de',
23
+ 'repository' => {
24
+ 'id' => 1,
25
+ 'name' => '___REPO___',
26
+ 'owner_name' => '___OWNER___',
27
+ 'url' => 'http://github.com/___OWNER___/minimal'
28
+ },
29
+ 'matrix' => [
30
+ {
31
+ 'id' => 2,
32
+ 'repository_id' => 1,
33
+ 'number' => '1.1',
34
+ 'state' => 'created',
35
+ 'started_at' => nil,
36
+ 'finished_at' => nil,
37
+ 'config' => {
38
+ 'notifications' => {
39
+ 'webhooks' => [
40
+ 'http://evome.fr/notifications',
41
+ 'http://example.com/'
42
+ ]
43
+ }
44
+ },
45
+ 'status' => nil,
46
+ 'log' => '',
47
+ 'result' => nil,
48
+ 'parent_id' => 1,
49
+ 'commit' => '62aae5f70ceee39123ef',
50
+ 'branch' => 'master',
51
+ 'message' => 'the commit message',
52
+ 'committed_at' => '2011-11-11T11: 11: 11Z',
53
+ 'committer_name' => 'Sven Fuchs',
54
+ 'committer_email' => 'svenfuchs@artweb-design.de',
55
+ 'author_name' => 'Sven Fuchs',
56
+ 'author_email' => 'svenfuchs@artweb-design.de',
57
+ 'compare_url' => (
58
+ 'https://github.com/___OWNER___/minimal/compare/master...develop'
59
+ )
60
+ }
61
+ ]
62
+ }
63
+ }.freeze
64
+
65
+ def self.valid_payload(owner = 'foo', repo = 'bar')
66
+ PAYLOAD_TMPL.clone.tap do |payload|
67
+ substitute_payload_vars!(payload['payload'], owner, repo)
68
+ end
69
+ end
70
+
71
+ def self.substitute_payload_vars!(payload, owner, repo)
72
+ payload['compare_url'] = payload['compare_url'].sub(/___OWNER___/, owner)
73
+ payload['repository'].merge!(
74
+ 'name' => repo,
75
+ 'owner_name' => owner,
76
+ 'url' => payload['repository']['url'].sub(/___OWNER___/, owner)
77
+ )
78
+ compare_url = payload['matrix'].first['compare_url']
79
+ payload['matrix'].first.merge!(
80
+ 'compare_url' => compare_url.sub(/___OWNER___/, owner)
81
+ )
82
+ end
83
+ end
metadata ADDED
@@ -0,0 +1,216 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-auth-travis
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Dan Buch
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-10-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: guard-rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: puma
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack-test
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: shotgun
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: simplecov
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ description: Rack auth for Travis CI webhook requests!
154
+ email:
155
+ - d.buch@modcloth.com
156
+ executables:
157
+ - rack-auth-travis-envify
158
+ - rack-auth-travis-hash
159
+ extensions: []
160
+ extra_rdoc_files: []
161
+ files:
162
+ - .gitignore
163
+ - .rspec
164
+ - .rubocop.yml
165
+ - .ruby-version
166
+ - .simplecov
167
+ - .travis.yml
168
+ - Gemfile
169
+ - Guardfile
170
+ - LICENSE.md
171
+ - README.md
172
+ - Rakefile
173
+ - bin/rack-auth-travis-envify
174
+ - bin/rack-auth-travis-hash
175
+ - example/Gemfile
176
+ - example/Rakefile
177
+ - example/config.ru
178
+ - example/echoplex.rb
179
+ - example/public/.gitkeep
180
+ - example/views/index.erb
181
+ - example/views/layout.erb
182
+ - example/views/result.erb
183
+ - lib/rack-auth-travis.rb
184
+ - lib/rack/auth/travis.rb
185
+ - rack-auth-travis.gemspec
186
+ - spec/lib/rack/auth/travis_spec.rb
187
+ - spec/spec_helper.rb
188
+ - spec/support.rb
189
+ homepage: https://github.com/modcloth-labs/rack-auth-travis
190
+ licenses:
191
+ - MIT
192
+ metadata: {}
193
+ post_install_message:
194
+ rdoc_options: []
195
+ require_paths:
196
+ - lib
197
+ required_ruby_version: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - '>='
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ required_rubygems_version: !ruby/object:Gem::Requirement
203
+ requirements:
204
+ - - '>='
205
+ - !ruby/object:Gem::Version
206
+ version: '0'
207
+ requirements: []
208
+ rubyforge_project:
209
+ rubygems_version: 2.0.3
210
+ signing_key:
211
+ specification_version: 4
212
+ summary: Rack auth for Travis CI webhook requests!
213
+ test_files:
214
+ - spec/lib/rack/auth/travis_spec.rb
215
+ - spec/spec_helper.rb
216
+ - spec/support.rb