king_hmac 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ nbproject/*
2
+ coverage/*
3
+ rdoc/*
4
+ pkg/*
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Georg Leciejewski
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,145 @@
1
+ = king-hmac
2
+
3
+ This gem started with a copy & disection of auth-hmac gem v1.1.1
4
+
5
+ king-hmac is a Ruby implementation of HMAC[http://en.wikipedia.org/wiki/HMAC]
6
+ based authentication of HTTP requests. HMAC authentication involves a client and
7
+ server having a shared secret key. When sending the request the client, signs
8
+ the request using the secret key. This involves building a canonical
9
+ representation of the request and then generating a HMAC of the request using
10
+ the secret. The generated HMAC is then sent as part of the request.
11
+
12
+ When the server receives the request it builds the same canonical representation
13
+ and generates a HMAC using it's copy of the secret key, if the HMAC produced by
14
+ the server matches the HMAC sent by the client, the server can be assured that
15
+ the client also possesses the shared secret key.
16
+
17
+ HMAC based authentication also provides message integrity checking because the
18
+ HMAC is based on a combination of the shared secret and the content of the
19
+ request. So if any part of the request that is used to build the canonical
20
+ representation is modified by a malicious party or in transit the authentication
21
+ will then fail.
22
+
23
+ KingHmac::Auth is loosely based on the Amazon Web Services authentication scheme
24
+ but without the Amazon specific components, i.e. it is HMAC for the rest of us.
25
+
26
+
27
+ == INSTALL:
28
+ Gem hosted on gemcutter.org
29
+ sudo gem install king-hmac
30
+
31
+ == Source Code
32
+ See http://github.com/salesking/king-hmac
33
+ The source repository:
34
+ git clone git://github.com/salesking/king-hmac.git
35
+
36
+ == When to use it?
37
+
38
+ HMAC Authentication is best used as authentication for communication between
39
+ applications such as web services. It provides better security than HTTP Basic
40
+ authentication without the need to set up SSL. Of course if you need to protect
41
+ the confidentiality of the data then you need SSL, but if you just want to
42
+ authenticate requests without sending credentials in the clear KingHmac::Auth
43
+ is a good choice.
44
+
45
+ == Usage
46
+
47
+ The simplest way to use KingHmac::Auth is with the KingHmac::Auth.sign! and
48
+ KingHmac::Auth.authenticate? methods. KingHmac::Auth.sign! takes a HTTP request
49
+ object, an access id and a secret key and signs the request with the access_id
50
+ and secret key.
51
+
52
+ On the server side you can then authenticate these requests using the
53
+ KingHmac::Auth.authenticated? method. This takes the same arguments as the sign!
54
+ method but returns true if the request has been signed with the access id and
55
+ secret or false if it hasn't.
56
+
57
+ If you have more than one set of credentials you might find it useful to create
58
+ an instance of the KingHmac::Auth class, passing your credentials as a Hash of
59
+ access id => secret keys, like so:
60
+ @hmac_auth = KingHmac::Auth.new('access_id1' => 'secret1', 'access_id2' => 'secret2')
61
+
62
+ You can then use the instance methods of the @hmac_auth object to sign and
63
+ authenticate requests, for example:
64
+ @hmac_auth.sign!(request, "access_id1")
65
+
66
+ Sign +request+ with "access_id1" and it's corresponding secret key. Similarly
67
+ authentication is done like so:
68
+ @hmac_auth.authenticated?(request)
69
+ which will return true if the request has been signed with one of the access id
70
+ and secret key pairs provided in the constructor.
71
+
72
+ === Supported HTTP request objects
73
+
74
+ KingHmac::Auth will do its best to figure out which type it is an handle it
75
+ accordingly.
76
+ * Net::HTTP::HTTPRequest
77
+ * CGI::Request
78
+ * Webrick HTTP request object.
79
+ * Rack::Request
80
+
81
+ === access id
82
+ The access_id is used to identify the secret key that was used to sign the
83
+ request. Think of it as like a user name, it allows you to hand out different
84
+ keys to different clients and authenticate each of them individually. The
85
+ access_id is sent in the clear so you should avoid making it an important string.
86
+
87
+ === secret key
88
+ The secret key is the shared secret between the client and the server.
89
+ You should make this sufficiently random so that is can't be guessed or exposed
90
+ to dictionary attacks.
91
+ The follow code will give you a pretty good secret key:
92
+
93
+ random = File.read('/dev/random', 512)
94
+ secret_key = Base64.encode64(Digest::SHA2.new(512).digest(random))
95
+
96
+ === Rails Integration
97
+
98
+ KingHmac::Auth supports authentication within Rails controllers and signing of
99
+ requests generated by Active Resource. See
100
+ KingHmac::Rails::ControllerFilter::ClassMethods and
101
+ KingHmac::Rails::ActiveResourceExtension::BaseHmac::ClassMethods for details.
102
+
103
+ == How does it work?
104
+
105
+ When creating a signature for a HTTP request KingHmac::Auth first generates a
106
+ canonical representation of the request.
107
+
108
+ This canonical string is created like so:
109
+
110
+ canonical_string = HTTP-Verb + "\n" +
111
+ Content-Type + "\n" +
112
+ Content-MD5 + "\n" +
113
+ Date + "\n" +
114
+ request-uri;
115
+
116
+ Where Content-Type, Content-MD5 and Date are all taken from the headers of the
117
+ request. If Content-Type or Content-MD5 are not present, they are substituted
118
+ with an empty string. If Date is not present it is added to the request headers
119
+ with the value +Time.now.httpdate+. +request-uri+ is the path component of the
120
+ request, without any query string, i.e. everything up to the ?.
121
+
122
+ This string is then used with the secret to generate a SHA1 HMAC using the
123
+ following:
124
+
125
+ OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), secret_key, canonical_string)
126
+
127
+ The result is then Base64 encoded and added to the headers of the request as the
128
+ +Authorization+ header in the format:
129
+ Authorization: KingHmac::Auth <access_id>:<base64 encoded king-hmac>
130
+
131
+ When authenaticating a request, KingHmac::Auth looks for the Authorization
132
+ header in the above format, parses out the components, regenerates a HMAC for
133
+ the request, using the secret key identified by the access id and then compares
134
+ the generated HMAC with the one provided by the client. If they match the
135
+ request is authenticated.
136
+
137
+ Using these details it is possible to build code that will sign and authenticate
138
+ KingHmac::Auth style requests in other languages.
139
+
140
+
141
+ == Authors and Contributors
142
+
143
+ This gem started with a copy & disection of auth-king-hmac gem v1.1.1.
144
+ Most of this doc was written by Sean Geoghegan
145
+ auth-king-hmac was developed by Sean Geoghegan http://rubyforge.org/projects/auth-king-hmac && by Peerworks[http://peerworks.org].
data/Rakefile ADDED
@@ -0,0 +1,48 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/rdoctask'
4
+ require 'spec/rake/spectask'
5
+
6
+ begin
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |gem|
9
+ gem.name = "king_hmac"
10
+ gem.summary = %Q{A Ruby Gem for authenticating HTTP requests using a HMAC}
11
+ gem.description = %Q{A Ruby Gem for authenticating HTTP requests using a HMAC}
12
+ gem.email = "gl@salesking.eu"
13
+ gem.homepage = "http://github.com/salesking/king_hmac"
14
+ gem.authors = ["Georg Leciejewski"]
15
+ gem.add_development_dependency "rspec", ">= 0"
16
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
21
+ end
22
+
23
+ desc 'Default: run specs.'
24
+ task :default => :spec
25
+
26
+ spec_files = Rake::FileList["spec/**/*_spec.rb"]
27
+
28
+ desc "Run specs"
29
+ Spec::Rake::SpecTask.new do |t|
30
+ t.spec_files = spec_files
31
+ t.spec_opts = ["-c"]
32
+ end
33
+
34
+ desc "Generate code coverage"
35
+ Spec::Rake::SpecTask.new(:coverage) do |t|
36
+ t.spec_files = spec_files
37
+ t.rcov = true
38
+ t.rcov_opts = ['--exclude', 'spec,/var/lib/gems']
39
+ end
40
+
41
+ desc 'Generate king_hmac documentation.'
42
+ Rake::RDocTask.new(:rdoc) do |rdoc|
43
+ rdoc.rdoc_dir = 'rdoc'
44
+ rdoc.title = 'king_hmac'
45
+ rdoc.options << '--line-numbers' << '--inline-source'
46
+ rdoc.rdoc_files.include('README')
47
+ rdoc.rdoc_files.include('lib/**/*.rb')
48
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require "#{File.dirname(__FILE__)}/lib/king_hmac"
data/king_hmac.gemspec ADDED
@@ -0,0 +1,60 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{king_hmac}
8
+ s.version = "1.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Georg Leciejewski"]
12
+ s.date = %q{2010-04-10}
13
+ s.description = %q{A Ruby Gem for authenticating HTTP requests using a HMAC}
14
+ s.email = %q{gl@salesking.eu}
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "MIT-LICENSE",
21
+ "README.rdoc",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "init.rb",
25
+ "king_hmac.gemspec",
26
+ "lib/king_hmac.rb",
27
+ "lib/king_hmac/auth.rb",
28
+ "lib/king_hmac/cannonical_string.rb",
29
+ "lib/king_hmac/headers.rb",
30
+ "lib/king_hmac/rack/middleware.rb",
31
+ "lib/king_hmac/rails/active_resource.rb",
32
+ "lib/king_hmac/rails/controller.rb",
33
+ "spec/fixtures/credentials.yml",
34
+ "spec/king_hmac/king_hmac_spec.rb",
35
+ "spec/spec_helper.rb"
36
+ ]
37
+ s.homepage = %q{http://github.com/salesking/king_hmac}
38
+ s.rdoc_options = ["--charset=UTF-8"]
39
+ s.require_paths = ["lib"]
40
+ s.rubygems_version = %q{1.3.6}
41
+ s.summary = %q{A Ruby Gem for authenticating HTTP requests using a HMAC}
42
+ s.test_files = [
43
+ "spec/king_hmac/king_hmac_spec.rb",
44
+ "spec/spec_helper.rb"
45
+ ]
46
+
47
+ if s.respond_to? :specification_version then
48
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
49
+ s.specification_version = 3
50
+
51
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
52
+ s.add_development_dependency(%q<rspec>, [">= 0"])
53
+ else
54
+ s.add_dependency(%q<rspec>, [">= 0"])
55
+ end
56
+ else
57
+ s.add_dependency(%q<rspec>, [">= 0"])
58
+ end
59
+ end
60
+
@@ -0,0 +1,143 @@
1
+ module KingHmac
2
+ # This module provides a HMAC Authentication method for HTTP requests. It should work with
3
+ # net/http request classes and CGIRequest classes and hence Rails.
4
+ #
5
+ # It is loosely based on the Amazon Web Services Authentication mechanism but
6
+ # generalized to be useful to any application that requires HMAC based authentication.
7
+ # As a result of the generalization, it won't work with AWS because it doesn't support
8
+ # the Amazon extension headers.
9
+ #
10
+ # === References
11
+ # Cryptographic Hash functions:: http://en.wikipedia.org/wiki/Cryptographic_hash_function
12
+ # SHA-1 Hash function:: http://en.wikipedia.org/wiki/SHA-1
13
+ # HMAC algorithm:: http://en.wikipedia.org/wiki/HMAC
14
+ # RFC 2104:: http://tools.ietf.org/html/rfc2104
15
+ #
16
+ class Auth
17
+
18
+ include KingHmac::Headers
19
+
20
+
21
+ @@default_signature_class = KingHmac::CanonicalString
22
+
23
+ # Create an KingHmac::Auth instance using the given credential store
24
+ #
25
+ # Credential Store:
26
+ # * Credential store must respond to the [] method and return a secret for access key id
27
+ #
28
+ # Options:
29
+ # Override default options
30
+ # * <tt>:service_id</tt> - Service ID used in the AUTHORIZATION header string. Default is KingHmac::Auth.
31
+ # * <tt>:signature_method</tt> - Proc object that takes request and produces the signature string
32
+ # used for authentication. Default is CanonicalString.
33
+ # Examples:
34
+ # my_hmac = KingHmac::Auth.new('access_id1' => 'secret1', 'access_id2' => 'secret2')
35
+ #
36
+ # cred_store = { 'access_id1' => 'secret1', 'access_id2' => 'secret2' }
37
+ # options = { :service_id => 'MyApp', :signature_method => lambda { |r| MyRequestString.new(r) } }
38
+ # my_hmac = KingHmac::Auth.new(cred_store, options)
39
+ #
40
+ def initialize(credential_store, options = nil)
41
+ @credential_store = credential_store
42
+
43
+ # Defaults
44
+ @service_id = self.class.name
45
+ @signature_class = @@default_signature_class
46
+
47
+ unless options.nil?
48
+ @service_id = options[:service_id] if options.key?(:service_id)
49
+ @signature_class = options[:signature] if options.key?(:signature) && options[:signature].is_a?(Class)
50
+ end
51
+
52
+ @signature_method = lambda { |r| @signature_class.send(:new, r) }
53
+ end
54
+
55
+ # Generates canonical signing string for given request
56
+ #
57
+ # Supports same options as KingHmac::Auth.initialize for overriding service_id and
58
+ # signature method.
59
+ #
60
+ def self.canonical_string(request, options = nil)
61
+ self.new(nil, options).canonical_string(request)
62
+ end
63
+
64
+ # Generates signature string for a given secret
65
+ #
66
+ # Supports same options as KingHmac::Auth.initialize for overriding service_id and
67
+ # signature method.
68
+ #
69
+ def self.signature(request, secret, options = nil)
70
+ self.new(nil, options).signature(request, secret)
71
+ end
72
+
73
+ # Signs a request using a given access key id and secret.
74
+ #
75
+ # Supports same options as KingHmac::Auth.initialize for overriding service_id and
76
+ # signature method.
77
+ #
78
+ def self.sign!(request, access_key_id, secret, options = nil)
79
+ credentials = { access_key_id => secret }
80
+ self.new(credentials, options).sign!(request, access_key_id)
81
+ end
82
+
83
+ # Authenticates a request using HMAC
84
+ #
85
+ # Supports same options as KingHmac::Auth.initialize for overriding service_id and
86
+ # signature method.
87
+ #
88
+ def self.authenticated?(request, access_key_id, secret, options)
89
+ credentials = { access_key_id => secret }
90
+ self.new(credentials, options).authenticated?(request)
91
+ end
92
+
93
+ # Signs a request using the access_key_id and the secret associated with that id
94
+ # in the credential store.
95
+ #
96
+ # Signing a requests adds an Authorization header to the request in the format:
97
+ #
98
+ # <service_id> <access_key_id>:<signature>
99
+ #
100
+ # where <signature> is the Base64 encoded HMAC-SHA1 of the CanonicalString and the secret.
101
+ #
102
+ def sign!(request, access_key_id)
103
+ secret = @credential_store[access_key_id]
104
+ raise ArgumentError, "No secret found for key id '#{access_key_id}'" if secret.nil?
105
+ request['Authorization'] = authorization(request, access_key_id, secret)
106
+ end
107
+
108
+ # Authenticates a request using HMAC
109
+ #
110
+ # Returns true if the request has an KingHmac::Auth Authorization header and
111
+ # the access id and HMAC match an id and HMAC produced for the secret
112
+ # in the credential store. Otherwise returns false.
113
+ #
114
+ def authenticated?(request)
115
+ rx = Regexp.new("#{@service_id} ([^:]+):(.+)$")
116
+ if md = rx.match(authorization_header(request))
117
+ access_key_id = md[1]
118
+ hmac = md[2]
119
+ secret = @credential_store[access_key_id]
120
+ !secret.nil? && hmac == signature(request, secret)
121
+ else
122
+ false
123
+ end
124
+ end
125
+
126
+ def signature(request, secret)
127
+ digest = OpenSSL::Digest::Digest.new('sha1')
128
+ Base64.encode64(OpenSSL::HMAC.digest(digest, secret, canonical_string(request))).strip
129
+ end
130
+
131
+ def canonical_string(request)
132
+ @signature_method.call(request)
133
+ end
134
+
135
+ def authorization_header(request)
136
+ find_header(%w(Authorization HTTP_AUTHORIZATION), headers(request))
137
+ end
138
+
139
+ def authorization(request, access_key_id, secret)
140
+ "#{@service_id} #{access_key_id}:#{signature(request, secret)}"
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,69 @@
1
+ module KingHmac
2
+ # Build a Canonical String for a HTTP request.
3
+ #
4
+ # A Canonical String has the following format:
5
+ #
6
+ # CanonicalString = HTTP-Verb + "\n" +
7
+ # Content-Type + "\n" +
8
+ # Content-MD5 + "\n" +
9
+ # Date + "\n" +
10
+ # request-uri;
11
+ #
12
+ #
13
+ # If the Date header doesn't exist, one will be generated since
14
+ # Net/HTTP will generate one if it doesn't exist and it will be
15
+ # used on the server side to do authentication.
16
+ #
17
+ class CanonicalString < String # :nodoc:
18
+ include Headers
19
+
20
+ def initialize(request)
21
+ self << request_method(request) + "\n"
22
+ self << header_values(headers(request)) + "\n"
23
+ self << request_path(request)
24
+ end
25
+
26
+ private
27
+ def request_method(request)
28
+ if request.respond_to?(:request_method) && request.request_method.is_a?(String)
29
+ request.request_method
30
+ elsif request.respond_to?(:method) && request.method.is_a?(String)
31
+ request.method
32
+ elsif request.respond_to?(:env) && request.env
33
+ request.env['REQUEST_METHOD']
34
+ else
35
+ raise ArgumentError, "Don't know how to get the request method from #{request.inspect}"
36
+ end
37
+ end
38
+
39
+ def header_values(headers)
40
+ [ content_type(headers),
41
+ content_md5(headers),
42
+ (date(headers) or headers['Date'] = Time.now.utc.httpdate)
43
+ ].join("\n")
44
+ end
45
+
46
+ def content_type(headers)
47
+ find_header(%w(CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE), headers)
48
+ end
49
+
50
+ def date(headers)
51
+ find_header(%w(DATE HTTP_DATE), headers)
52
+ end
53
+
54
+ def content_md5(headers)
55
+ find_header(%w(CONTENT-MD5 CONTENT_MD5), headers)
56
+ end
57
+
58
+ def request_path(request)
59
+ # Try unparsed_uri in case it is a Webrick request
60
+ path = if request.respond_to?(:unparsed_uri)
61
+ request.unparsed_uri
62
+ else
63
+ request.path
64
+ end
65
+
66
+ path[/^[^?]*/]
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,28 @@
1
+ module KingHmac
2
+ module Headers # :nodoc:
3
+ # Gets the headers for a request.
4
+ #
5
+ # Attempts to deal with known HTTP header representations in Ruby.
6
+ # Currently handles net/http and Rails.
7
+ #
8
+ def headers(request)
9
+ if request.respond_to?(:headers)
10
+ request.headers
11
+ elsif request.respond_to?(:env) && request.env
12
+ request.env
13
+ elsif request.respond_to?(:[])
14
+ request
15
+ else
16
+ raise ArgumentError, "Don't know how to get the headers from #{request.inspect}"
17
+ end
18
+ end
19
+
20
+ def find_header(keys, headers)
21
+ val = keys.map do |key|
22
+ headers[key]
23
+ end.compact.first
24
+ var = 'adsf'
25
+ val
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,44 @@
1
+ require 'rack/request'
2
+ module KingHmac
3
+ module Rack
4
+ class Middleware
5
+
6
+ # === Parameter
7
+ # opts<Hash>::
8
+ # === opts params:
9
+ # keys<Hash{String=>String}>:: Must be an array of accesskey=> secret
10
+ # respond to the [] method and return a secret for access key id
11
+ # only<Array[String]>:: path's to protect
12
+ def initialize(app, opts={})
13
+ @app = app
14
+ @opts = opts
15
+ @plain_error = "HMAC Authentication failed. Get yourself a valid HMAC Key .. Dude .. or ask your admin to get you some credentials"
16
+ @hmac_auth = KingHmac::Auth.new(@opts['keys'])
17
+ end
18
+
19
+ def call(env)
20
+ path = env['PATH_INFO']
21
+ do_hmac_check = @opts['only'].detect{|i| path.include?(i) }
22
+ if do_hmac_check
23
+ unless hmac_authenticated?(::Rack::Request.new(env))
24
+ headers = {'Content-Type' => "text/plain",
25
+ 'Content-Length' => "#{@plain_error.length}",
26
+ 'WWW-Authenticate' => 'AuthHMAC'
27
+ }
28
+ [401, headers, [@plain_error]]
29
+ else #valid credentials
30
+ @app.call(env)
31
+ end
32
+ else # unprotected
33
+ @app.call(env)
34
+ end
35
+ end
36
+
37
+ def hmac_authenticated?(request)
38
+ @hmac_auth.authenticated?(request)
39
+ end
40
+
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,99 @@
1
+ module ActiveResourceExtension # :nodoc:
2
+ module BaseHmac # :nodoc:
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+
6
+ base.class_inheritable_accessor :hmac_access_id
7
+ base.class_inheritable_accessor :hmac_secret
8
+ base.class_inheritable_accessor :use_hmac
9
+ base.class_inheritable_accessor :hmac_options
10
+ end
11
+
12
+ module ClassMethods
13
+ # Call with an Active Resource class definition to sign
14
+ # all HTTP requests sent by that class with the provided
15
+ # credentials.
16
+ #
17
+ # Can be called with either a hash or two separate parameters
18
+ # like so:
19
+ #
20
+ # class MyResource < ActiveResource::Base
21
+ # with_auth_hmac("my_access_id", "my_secret")
22
+ # end
23
+ #
24
+ # or
25
+ #
26
+ # class MyOtherResource < ActiveResource::Base
27
+ # with_auth_hmac("my_access_id" => "my_secret")
28
+ # end
29
+ #
30
+ #
31
+ # This has only been tested with Rails 2.1 and since it is virtually a monkey
32
+ # patch of the internals of ActiveResource it might not work with past or
33
+ # future versions.
34
+ #
35
+ def with_auth_hmac(access_id, secret = nil, options = nil)
36
+ if access_id.is_a?(Hash)
37
+ self.hmac_access_id = access_id.keys.first
38
+ self.hmac_secret = access_id[self.hmac_access_id]
39
+ else
40
+ self.hmac_access_id = access_id
41
+ self.hmac_secret = secret
42
+ end
43
+ self.use_hmac = true
44
+ self.hmac_options = options
45
+
46
+ class << self
47
+ alias_method_chain :connection, :hmac
48
+ end
49
+ end
50
+
51
+ def connection_with_hmac(refresh = false) # :nodoc:
52
+ c = connection_without_hmac(refresh)
53
+ c.hmac_access_id = self.hmac_access_id
54
+ c.hmac_secret = self.hmac_secret
55
+ c.use_hmac = self.use_hmac
56
+ c.hmac_options = self.hmac_options
57
+ c
58
+ end
59
+ end
60
+
61
+ module InstanceMethods # :nodoc:
62
+ end
63
+ end
64
+
65
+ module Connection # :nodoc:
66
+ def self.included(base)
67
+ base.send :alias_method_chain, :request, :hmac
68
+ base.class_eval do
69
+ attr_accessor :hmac_secret, :hmac_access_id, :use_hmac, :hmac_options
70
+ end
71
+ end
72
+
73
+ def request_with_hmac(method, path, *arguments)
74
+ if use_hmac && hmac_access_id && hmac_secret
75
+ arguments.last['Date'] = Time.now.httpdate if arguments.last['Date'].nil?
76
+ temp = "Net::HTTP::#{method.to_s.capitalize}".constantize.new(path, arguments.last)
77
+ AuthHMAC.sign!(temp, hmac_access_id, hmac_secret, hmac_options)
78
+ arguments.last['Authorization'] = temp['Authorization']
79
+ end
80
+
81
+ request_without_hmac(method, path, *arguments)
82
+ end
83
+ end
84
+
85
+ unless defined?(ActiveResource)
86
+ begin
87
+ require 'rubygems'
88
+ gem 'activeresource'
89
+ require 'activeresource'
90
+ rescue
91
+ nil
92
+ end
93
+ end
94
+
95
+ if defined?(ActiveResource)
96
+ ActiveResource::Base.send(:include, BaseHmac)
97
+ ActiveResource::Connection.send(:include, Connection)
98
+ end
99
+ end