ey-hmac 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .rspec
data/Gemfile ADDED
@@ -0,0 +1,22 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ey-hmac.gemspec
4
+ gemspec
5
+
6
+ group(:test) do
7
+ gem 'rb-fsevent'
8
+ gem 'guard-bundler'
9
+ gem 'guard-rspec'
10
+ gem 'pry-nav'
11
+ end
12
+
13
+ group(:rack) do
14
+ gem 'rack'
15
+ gem 'rack-test'
16
+ gem 'rack-client'
17
+ end
18
+
19
+ group(:faraday) do
20
+ gem 'faraday'
21
+ gem 'faraday_middleware'
22
+ end
data/Guardfile ADDED
@@ -0,0 +1,7 @@
1
+ guard 'rspec' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^spec/(shared|support)/.*\.rb$}) { "spec" }
4
+ watch(%r{^lib/(.+)\.rb$}) { "spec" }
5
+ watch('spec/spec_helper.rb') { "spec" }
6
+ end
7
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Josh Lane & Jason Hansen
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.
data/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # Ey::Hmac
2
+
3
+ Lightweight libraries and middlewares to perform HMAC signing and verification
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'ey-hmac'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install ey-hmac
18
+
19
+ ## Documentation
20
+
21
+ [rdoc.info](http://rubydoc.info/gems/ey-hmac)
22
+
23
+ ## Usage
24
+
25
+ ## Client Middleware
26
+
27
+ ### Rack
28
+
29
+ Using ```Rack::Client```
30
+
31
+ ```ruby
32
+ client = Rack::Client.new do
33
+ use Ey::Hmac::Rack, key_id, key_secret
34
+ run app
35
+ end
36
+ ```
37
+
38
+ ### Faraday
39
+
40
+ ```ruby
41
+ require 'ey-hmac/faraday'
42
+
43
+ connection = Faraday.new do |c|
44
+ c.request :hmac, key_id, key_secret
45
+ c.adapter(:rack, app)
46
+ end
47
+ ```
48
+
49
+ ## Server
50
+
51
+ ### Rack
52
+
53
+ ```ruby
54
+ app = lambda do |env|
55
+ authenticated = Ey::Hmac.authenticated?(env, adapter: Ey::Hmac::Adapter::Rack) do |auth_id|
56
+ (auth_id == key_id) && key_secret
57
+ end
58
+ [(authenticated ? 200 : 401), {"Content-Type" => "text/plain"}, []]
59
+ end
60
+
61
+ ```
62
+
63
+ ### Rails
64
+
65
+ ```ruby
66
+ Ey::Hmac.authenticated?(request.env) do |auth_id|
67
+ if consumer_credential = ConsumerCredential.where(deleted_at: nil, auth_id: auth_id).first
68
+ consumer = consumer_credential.try(:consumer)
69
+ consumer && consumer.enabled && consumer_credential.auth_key
70
+ end
71
+ end && consumer
72
+ ```
73
+
74
+
75
+ ## Contributing
76
+
77
+ 1. Fork it
78
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
79
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
80
+ 4. Push to the branch (`git push origin my-new-feature`)
81
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/ey-hmac.gemspec ADDED
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ey-hmac/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "ey-hmac"
8
+ gem.version = Ey::Hmac::VERSION
9
+ gem.authors = ["Josh Lane & Jason Hansen"]
10
+ gem.email = ["jlane@engineyard.com"]
11
+ gem.description = %q{Lightweight HMAC signing libraries and middleware for Farday and Rack}
12
+ gem.summary = %q{Lightweight HMAC signing libraries and middleware for Farday and Rack}
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ end
@@ -0,0 +1,43 @@
1
+ class Ey::Hmac::Adapter::Faraday < Ey::Hmac::Adapter
2
+ def method
3
+ request[:method].to_s.upcase
4
+ end
5
+
6
+ def content_type
7
+ %w[CONTENT-TYPE CONTENT_TYPE Content-Type Content_Type].inject(nil){|r, h| r || request[h]}
8
+ end
9
+
10
+ def content_digest
11
+ existing = %w[CONTENT-DIGEST CONTENT_DIGEST Content-Digest Content_Digest].inject(nil){|r,h| r || request[:request_headers][h]}
12
+ existing || (request[:request_headers]['Content-Digest'] = (body && Digest::MD5.hexdigest(body)))
13
+ end
14
+
15
+ def body
16
+ if request[:body] && request[:body].to_s != ""
17
+ request[:body]
18
+ else nil
19
+ end
20
+ end
21
+
22
+ def date
23
+ existing = %w[DATE Date].inject(nil){|r,h| r || request[h]}
24
+ existing || (request[:request_headers]['Date'] = Time.now.httpdate)
25
+ end
26
+
27
+ def path
28
+ request[:url].path
29
+ end
30
+
31
+ def sign!(key_id, key_secret)
32
+ %w[CONTENT-TYPE CONTENT_TYPE Content-Type Content_Type].inject(nil){|r,h| request[:request_headers][h]}
33
+ if options[:version]
34
+ request[:request_headers]['X-Signature-Version'] = options[:version]
35
+ end
36
+
37
+ request[:request_headers][authorization_header] = authorization(key_id, key_secret)
38
+ end
39
+
40
+ def authorization_signature
41
+ %w[Authorization AUTHORIZATION].inject(nil){|r, h| r || request[:request_headers][h]}
42
+ end
43
+ end
@@ -0,0 +1,50 @@
1
+ require 'rack'
2
+
3
+ class Ey::Hmac::Adapter::Rack < Ey::Hmac::Adapter
4
+ def initialize(request, options)
5
+ super
6
+ @request = ::Rack::Request.new(request) if request.is_a?(Hash)
7
+ end
8
+
9
+ def method
10
+ request.request_method.to_s.upcase
11
+ end
12
+
13
+ def content_type
14
+ request.content_type
15
+ end
16
+
17
+ def content_digest
18
+ request.env['HTTP_CONTENT_DIGEST'] ||= body && Digest::MD5.hexdigest(body)
19
+ end
20
+
21
+ def body
22
+ if request.env["rack.input"]
23
+ request.env["rack.input"].rewind
24
+ body = request.env["rack.input"].read
25
+ request.env["rack.input"].rewind
26
+ body == "" ? nil : body
27
+ else nil
28
+ end
29
+ end
30
+
31
+ def date
32
+ request.env['HTTP_DATE'] ||= Time.now.httpdate
33
+ end
34
+
35
+ def path
36
+ request.path
37
+ end
38
+
39
+ def sign!(key_id, key_secret)
40
+ if options[:version]
41
+ request.env['HTTP_X_SIGNATURE_VERSION'] = options[:version]
42
+ end
43
+
44
+ request.env["HTTP_#{authorization_header.to_s.upcase}"] = authorization(key_id, key_secret)
45
+ end
46
+
47
+ def authorization_signature
48
+ request.env["HTTP_#{authorization_header.to_s.upcase}"]
49
+ end
50
+ end
@@ -0,0 +1,127 @@
1
+ # This class is responsible for forming the canonical string to used to sign requests
2
+ # @abstract override methods {#method}, {#path}, {#body}, {#content_type} and {#content_digest}
3
+ class Ey::Hmac::Adapter
4
+ AUTHORIZATION_REGEXP = /\w+ ([^:]+):(.+)$/
5
+
6
+ autoload :Rack, "ey-hmac/adapter/rack"
7
+ autoload :Faraday, "ey-hmac/adapter/faraday"
8
+
9
+ attr_reader :request, :options, :authorization_header, :service
10
+
11
+ # @param [Object] request signer-specific request implementation
12
+ # @option options [Integer] :version signature version
13
+ # @option options [String] :authorization_header ('Authorization') Authorization header key.
14
+ # @option options [String] :server ('EyHmac') service name prefixed to {#authorization}. set to {#service}
15
+ def initialize(request, options={})
16
+ @request, @options = request, options
17
+
18
+ @authorization_header = options[:authorization_header] || 'Authorization'
19
+ @service = options[:service] || 'EyHmac'
20
+ end
21
+
22
+ # In order for the server to correctly authorize the request, the client and server MUST AGREE on this format
23
+ #
24
+ # default canonical string formation is '{#method}\\n{#content_type}\\n{#content_digest}\\n{#date}\\n{#path}'
25
+ # @return [String] canonical string used to form the {#signature}
26
+ def canonicalize
27
+ [method, content_type, content_digest, date, path].join("\n")
28
+ end
29
+
30
+ # @param [String] key_secret private HMAC key
31
+ # @return [String] HMAC signature of {#request}
32
+ # @todo handle multiple hash functions
33
+ def signature(key_secret)
34
+ Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), key_secret, canonicalize)).strip
35
+ end
36
+
37
+ # @param [String] key_id public HMAC key
38
+ # @param [String] key_secret private HMAC key
39
+ # @return [String] HMAC header value of {#request}
40
+ def authorization(key_id, key_secret)
41
+ "#{service} #{key_id}:#{signature(key_secret)}"
42
+ end
43
+
44
+ # @abstract
45
+ # @return [String] upcased request verb. i.e. 'GET'
46
+ def method
47
+ raise NotImplementedError
48
+ end
49
+
50
+ # @abstract
51
+ # @return [String] request path. i.e. '/blogs/1'
52
+ def path
53
+ raise NotImplementedError
54
+ end
55
+
56
+ # @abstract
57
+ # Digest of body. Default is MD5.
58
+ # @todo support explicit digest methods
59
+ # @return [String] digest of body
60
+ def content_digest
61
+ raise NotImplementedError
62
+ end
63
+
64
+ # @abstract
65
+ # @return [String] request body.
66
+ # @return [NilClass] if there is no body or the body is empty
67
+ def body
68
+ raise NotImplementedError
69
+ end
70
+
71
+ # @abstract
72
+ # @return [String] value of the Content-Type header in {#request}
73
+ def content_type
74
+ raise NotImplementedError
75
+ end
76
+
77
+ # @abstract
78
+ # @return [String] value of the Date header in {#request}.
79
+ # @see {Time#http_date}
80
+ def date
81
+ raise NotImplementedError
82
+ end
83
+
84
+ # @abstract used when verifying a signed request
85
+ # @return [String] value of the {#authorization_header}
86
+ def authorization_signature
87
+ raise NotImplementedError
88
+ end
89
+
90
+ # @abstract
91
+ # Add {#signature} header to request. Typically this is 'Authorization' or 'WWW-Authorization'
92
+ def sign!(key_id, key_secret)
93
+ raise NotImplementedError
94
+ end
95
+
96
+ # Check {#authorization_signature} against calculated {#signature}
97
+ # @yieldparam key_id [String] public HMAC key
98
+ # @return [Boolean] true if block yields matching private key and signature matches, else false
99
+ # @see {#authenticated!}
100
+ def authenticated?(&block)
101
+ authenticated!(&block)
102
+ rescue Ey::Hmac::Error
103
+ false
104
+ end
105
+
106
+ # Check {#authorization_signature} against calculated {#signature}
107
+ # @yieldparam key_id [String] public HMAC key
108
+ # @return [Boolean] true if block yields matching private key
109
+ # @raise [Ey::Hmac::Error] if authentication fails
110
+ def authenticated!(&block)
111
+ if authorization_match = AUTHORIZATION_REGEXP.match(authorization_signature)
112
+ key_id = authorization_match[1]
113
+ signature_value = authorization_match[2]
114
+
115
+ if key_secret = block.call(key_id)
116
+ if signature_value == (calculated_signature = signature(key_secret))
117
+ else raise(Ey::Hmac::SignatureMismatch, "Calculated siganature #{signature_value} does not match #{calculated_signature} using #{canonicalize.inspect}")
118
+ end
119
+ else raise(Ey::Hmac::MissingSecret, "Failed to find secret matching #{key_id.inspect}")
120
+ end
121
+ else
122
+ raise(Ey::Hmac::MissingAuthorization, "Failed to parse authorization_signature #{authorization_signature}")
123
+ end
124
+ true
125
+ end
126
+ alias authenticate! authenticated!
127
+ end
@@ -0,0 +1,25 @@
1
+ require 'faraday'
2
+
3
+ if Faraday.respond_to? :register_middleware
4
+ Faraday.register_middleware(:request, {:hmac => lambda { Ey::Hmac::Faraday }})
5
+ end
6
+
7
+ # Request middleware that performs HMAC request signing
8
+ class Ey::Hmac::Faraday < Faraday::Middleware
9
+ dependency do
10
+ require 'ey-hmac' unless defined?(Ey::Hmac)
11
+ end
12
+
13
+ attr_reader :key_id, :key_secret, :options
14
+
15
+ def initialize(app, key_id, key_secret, options = {})
16
+ super(app)
17
+ @key_id, @key_secret = key_id, key_secret
18
+ @options = options
19
+ end
20
+
21
+ def call(env)
22
+ Ey::Hmac.sign!(env, key_id, key_secret, {adapter: Ey::Hmac::Adapter::Faraday}.merge(options))
23
+ @app.call(env)
24
+ end
25
+ end
@@ -0,0 +1,15 @@
1
+ # Request middleware that performs HMAC request signing
2
+ class Ey::Hmac::Rack
3
+ attr_reader :key_id, :key_secret, :options
4
+
5
+ def initialize(app, key_id, key_secret, options = {})
6
+ @app = app
7
+ @key_id, @key_secret = key_id, key_secret
8
+ @options = options
9
+ end
10
+
11
+ def call(env)
12
+ Ey::Hmac.sign!(env, key_id, key_secret, {adapter: Ey::Hmac::Adapter::Rack}.merge(options))
13
+ @app.call(env)
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ module Ey
2
+ module Hmac
3
+ VERSION = "0.0.2"
4
+ end
5
+ end
data/lib/ey-hmac.rb ADDED
@@ -0,0 +1,82 @@
1
+ require "ey-hmac/version"
2
+
3
+ require 'base64'
4
+ require 'digest/md5'
5
+ require 'openssl'
6
+
7
+ module Ey
8
+ module Hmac
9
+ Error = Class.new(StandardError)
10
+
11
+ MissingSecret = Class.new(Error)
12
+ MissingAuthorization = Class.new(Error)
13
+ SignatureMismatch = Class.new(Error)
14
+
15
+ autoload :Adapter, "ey-hmac/adapter"
16
+ autoload :Faraday, "ey-hmac/faraday"
17
+ autoload :Rack, "ey-hmac/rack"
18
+
19
+ def self.default_adapter=(default_adapter)
20
+ @default_adapter = default_adapter
21
+ end
22
+
23
+ def self.default_adapter
24
+ @default_adapter ||= begin
25
+ if defined?(Rack) || defined?(Rails)
26
+ Ey::Hmac::Adapter::Rack
27
+ elsif defined?(Faraday)
28
+ Ey::Hmac::Adapter::Rails
29
+ end
30
+ end
31
+ end
32
+
33
+ # @example
34
+ # Ey::Hmac.sign!(env, @key_id, @key_secret)
35
+ #
36
+ # @param request [Hash] request environment
37
+ # @option options [Ey::Hmac::Adapter] :adapter (#{default_adapter}) adapter to sign request with
38
+ # @option options [Integer] :version (nil) signature version
39
+ # @option options [String] :authorization_header ('Authorization') Authorization header key.
40
+ # @option options [String] :server ('EyHmac') service name prefixed to {#authorization}
41
+ # @see {Ey::Hmac::Adapter#sign!}
42
+ def self.sign!(request, key_id, key_secret, options={})
43
+ adapter = options[:adapter] || Ey::Hmac.default_adapter
44
+
45
+ raise ArgumentError, "Missing adapter and Ey::Hmac.default_adapter" unless adapter
46
+
47
+ adapter.new(request, options).sign!(key_id, key_secret)
48
+ end
49
+
50
+ # @example
51
+ # Ey::Hmac.authenticated? do |key_id|
52
+ # @consumer = Consumer.where(auth_id: key_id).first
53
+ # @consumer && @consumer.auth_key
54
+ # end
55
+ # @param request [Hash] request environment
56
+ # @option options [Ey::Hmac::Adapter] :adapter ({#default_adapter}) adapter to verify request with
57
+ # @see {Ey::Hmac::Adapter#authenticated?}
58
+ def self.authenticated?(request, options={}, &block)
59
+ adapter = options[:adapter] || Ey::Hmac.default_adapter
60
+
61
+ raise ArgumentError, "Missing adapter and Ey::Hmac.default_adapter" unless adapter
62
+
63
+ adapter.new(request, options).authenticated?(&block)
64
+ end
65
+
66
+ # @example
67
+ # Ey::Hmac.authenticate! do |key_id|
68
+ # @consumer = Consumer.where(auth_id: key_id).first
69
+ # @consumer && @consumer.auth_key
70
+ # end
71
+ # @param request [Hash] request environment
72
+ # @option options [Ey::Hmac::Adapter] :adapter ({#default_adapter}) adapter to verify request with
73
+ # @see {Ey::Hmac::Adapter#authenticate!}
74
+ def self.authenticate!(request, options={}, &block)
75
+ adapter = options[:adapter] || Ey::Hmac.default_adapter
76
+
77
+ raise ArgumentError, "Missing adapter and Ey::Hmac.default_adapter" unless adapter
78
+
79
+ adapter.new(request, options).authenticate!(&block)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe "faraday" do
4
+ before(:all) { Bundler.require(:faraday) }
5
+ let!(:key_id) { (0...8).map{ 65.+(rand(26)).chr}.join }
6
+ let!(:key_secret) { (0...16).map{ 65.+(rand(26)).chr}.join }
7
+
8
+ describe "adapter" do
9
+ let!(:adapter) { Ey::Hmac::Adapter::Faraday }
10
+ let!(:request) do
11
+ Faraday::Request.new.tap do |r|
12
+ r.method = :get
13
+ r.path = "/auth"
14
+ r.body = "{1: 2}"
15
+ r.headers = {"Content-Type" => "application/xml"}
16
+ end.to_env(Faraday::Connection.new("http://localhost"))
17
+ end
18
+
19
+ it "should sign and read request" do
20
+ Ey::Hmac.sign!(request, key_id, key_secret, adapter: adapter)
21
+
22
+ request[:request_headers]['Authorization'].should start_with("EyHmac")
23
+ request[:request_headers]['Content-Digest'].should == Digest::MD5.hexdigest(request[:body])
24
+ Time.parse(request[:request_headers]['Date']).should_not be_nil
25
+
26
+ yielded = false
27
+
28
+ Ey::Hmac.authenticated?(request, adapter: adapter) do |key_id|
29
+ key_id.should == key_id
30
+ yielded = true
31
+ key_secret
32
+ end.should be_true
33
+
34
+ yielded.should be_true
35
+ end
36
+
37
+ include_examples "authentication"
38
+ end
39
+
40
+ describe "middleware" do
41
+ it "should sign request" do
42
+ require 'ey-hmac/faraday'
43
+ Bundler.require(:rack)
44
+
45
+ app = lambda do |env|
46
+ authenticated = Ey::Hmac.authenticated?(env, adapter: Ey::Hmac::Adapter::Rack) do |auth_id|
47
+ (auth_id == key_id) && key_secret
48
+ end
49
+ [(authenticated ? 200 : 401), {"Content-Type" => "text/plain"}, []]
50
+ end
51
+
52
+ request_env = nil
53
+ connection = Faraday.new do |c|
54
+ c.request :hmac, key_id, key_secret
55
+ c.adapter(:rack, app)
56
+ end
57
+
58
+ connection.get("/resources").status.should == 200
59
+ end
60
+ end
61
+ end
data/spec/rack_spec.rb ADDED
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe "rack" do
4
+ before(:all) { Bundler.require(:rack) }
5
+
6
+ let!(:key_id) { (0...8).map{ 65.+(rand(26)).chr}.join }
7
+ let!(:key_secret) { (0...16).map{ 65.+(rand(26)).chr}.join }
8
+
9
+ describe "adapter" do
10
+ let(:adapter) { Ey::Hmac::Adapter::Rack }
11
+ let(:request) {
12
+ Rack::Request.new({
13
+ "rack.input" => StringIO.new("{1: 2}"),
14
+ "HTTP_CONTENT_TYPE" => "application/json",
15
+ })
16
+ }
17
+
18
+ it "should sign and read request" do
19
+ Ey::Hmac.sign!(request, key_id, key_secret, adapter: adapter)
20
+
21
+ request.env['HTTP_AUTHORIZATION'].should start_with("EyHmac")
22
+ request.env['HTTP_CONTENT_DIGEST'].should == Digest::MD5.hexdigest(request.body.tap(&:rewind).read)
23
+ Time.parse(request.env['HTTP_DATE']).should_not be_nil
24
+
25
+ yielded = false
26
+
27
+ Ey::Hmac.authenticated?(request, adapter: adapter) do |key_id|
28
+ key_id.should == key_id
29
+ yielded = true
30
+ key_secret
31
+ end.should be_true
32
+
33
+ yielded.should be_true
34
+ end
35
+
36
+ include_examples "authentication"
37
+ end
38
+
39
+ describe "middleware" do
40
+ it "should sign and read request" do
41
+ app = lambda do |env|
42
+ authenticated = Ey::Hmac.authenticated?(env, adapter: Ey::Hmac::Adapter::Rack) do |auth_id|
43
+ (auth_id == key_id) && key_secret
44
+ end
45
+ [(authenticated ? 200 : 401), {"Content-Type" => "text/plain"}, []]
46
+ end
47
+
48
+ _key_id, _key_secret = key_id, key_secret
49
+ client = Rack::Client.new do
50
+ use Ey::Hmac::Rack, _key_id, _key_secret
51
+ run app
52
+ end
53
+
54
+ client.get("/resource").status.should == 200
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,55 @@
1
+ shared_examples_for "authentication" do
2
+ describe "#authenticated?" do
3
+ it "should not authenticate invalid secret" do
4
+ Ey::Hmac.sign!(request, key_id, "#{key_secret}bad", adapter: adapter)
5
+
6
+ Ey::Hmac.authenticated?(request, adapter: adapter) do |auth_id|
7
+ (auth_id == key_id) && key_secret
8
+ end.should be_false
9
+ end
10
+
11
+ it "should not authenticate invalid id" do
12
+ Ey::Hmac.sign!(request, "what#{key_id}", key_secret, adapter: adapter)
13
+
14
+ Ey::Hmac.authenticated?(request, adapter: adapter) do |auth_id|
15
+ (auth_id == key_id) && key_secret
16
+ end.should be_false
17
+ end
18
+
19
+ it "should not authenticate missing header" do
20
+ Ey::Hmac.authenticated?(request, adapter: adapter) do |auth_id|
21
+ (auth_id == key_id) && key_secret
22
+ end.should be_false
23
+ end
24
+ end
25
+
26
+ describe "#authenticate!" do
27
+ it "should not authenticate invalid secret" do
28
+ Ey::Hmac.sign!(request, key_id, "#{key_secret}bad", adapter: adapter)
29
+
30
+ lambda {
31
+ Ey::Hmac.authenticate!(request, adapter: adapter) do |auth_id|
32
+ (auth_id == key_id) && key_secret
33
+ end
34
+ }.should raise_exception(Ey::Hmac::SignatureMismatch)
35
+ end
36
+
37
+ it "should not authenticate invalid id" do
38
+ Ey::Hmac.sign!(request, "what#{key_id}", key_secret, adapter: adapter)
39
+
40
+ lambda {
41
+ Ey::Hmac.authenticate!(request, adapter: adapter) do |auth_id|
42
+ (auth_id == key_id) && key_secret
43
+ end
44
+ }.should raise_exception(Ey::Hmac::MissingSecret)
45
+ end
46
+
47
+ it "should not authenticate missing header" do
48
+ lambda {
49
+ Ey::Hmac.authenticate!(request, adapter: adapter) do |auth_id|
50
+ (auth_id == key_id) && key_secret
51
+ end.should be_false
52
+ }.should raise_exception(Ey::Hmac::MissingAuthorization)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,9 @@
1
+ require File.expand_path("../../lib/ey-hmac", __FILE__)
2
+
3
+ Bundler.require(:test)
4
+
5
+ Dir[File.expand_path("../{support,shared}/*.rb", __FILE__)].each{|f| require(f)}
6
+
7
+ RSpec.configure do |config|
8
+ config.order = "random"
9
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ey-hmac
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Josh Lane & Jason Hansen
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-05 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Lightweight HMAC signing libraries and middleware for Farday and Rack
15
+ email:
16
+ - jlane@engineyard.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - Gemfile
23
+ - Guardfile
24
+ - LICENSE.txt
25
+ - README.md
26
+ - Rakefile
27
+ - ey-hmac.gemspec
28
+ - lib/ey-hmac.rb
29
+ - lib/ey-hmac/adapter.rb
30
+ - lib/ey-hmac/adapter/faraday.rb
31
+ - lib/ey-hmac/adapter/rack.rb
32
+ - lib/ey-hmac/faraday.rb
33
+ - lib/ey-hmac/rack.rb
34
+ - lib/ey-hmac/version.rb
35
+ - spec/faraday_spec.rb
36
+ - spec/rack_spec.rb
37
+ - spec/shared/authenticated.rb
38
+ - spec/spec_helper.rb
39
+ homepage: ''
40
+ licenses: []
41
+ post_install_message:
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ none: false
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ none: false
57
+ requirements: []
58
+ rubyforge_project:
59
+ rubygems_version: 1.8.24
60
+ signing_key:
61
+ specification_version: 3
62
+ summary: Lightweight HMAC signing libraries and middleware for Farday and Rack
63
+ test_files:
64
+ - spec/faraday_spec.rb
65
+ - spec/rack_spec.rb
66
+ - spec/shared/authenticated.rb
67
+ - spec/spec_helper.rb
68
+ has_rdoc: