rack-auth-travis 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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