hash-auth 0.1.1 → 1.0.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.
data/README.md CHANGED
@@ -14,6 +14,7 @@ _Note: Only Ruby 1.9.2 and above are supported, due to lack of ordered Hash obje
14
14
  - Clients can be authenticated as a proxy user upon successful hash authentication (ie. if your controller action depends on having current_user, you can assign an email address to your client and have it log in that user)
15
15
  - Custom blocks can be provided to (a) acquire the string to hash, (b) hash the string, or (c) perform a custom action upon authentication from a request from each indivudual client
16
16
  - Enhanced security can be enabled by requiring each client to submit a GMT version of their system time to be included in the hash, which will mean any given request is only valid within a predefined window (reduces the possibility of a man in the middle attack through duplicate requests)
17
+ - Requests can be filtered by remote ip address specifically or by the reverse dns lookup of the remote ip address
17
18
 
18
19
  ## Usage
19
20
 
@@ -95,7 +96,9 @@ This will generate a template that needs to be filled in with the necessary beha
95
96
  :customer_identifier_param => 'customer_id',
96
97
  # the name of the parameter the client will pass their unique identifier in
97
98
  :valid_domains => '*my_organization.org',
98
- # will allow request from anything ending with my_organization.org, can also provide a list
99
+ # will allow request from anything ending with my_organization.org, can also provide a list. Note: this only works if reverse_dns ip filtering, so the reverse dns lookup for a clients IP must match up. (domain_auth :reverse_dns)
100
+ :valid_ips => '192.168.1.1',
101
+ # will allow request from a specific ip or list of ips. Note: only works if ip filtering is enabled (domain_auth :ip)
99
102
  :strategy => :my_auth_strategy,
100
103
  # If no strategy is provided, then the default (HashAuth::Strategies::Default) will be used. If the strategy symbol does not reference a valid strategy, then an exception will be raised
101
104
  }
@@ -110,6 +113,7 @@ YAML file (config/clients.yml in this example):
110
113
  customer_identifier: my_organization
111
114
  customer_identifier_param: customer_id
112
115
  valid_domains: '*my_organization.org'
116
+ valid_ips: '192.168.1.1'
113
117
  strategy: :default
114
118
  custom_key: custom_value
115
119
  -
@@ -117,6 +121,7 @@ YAML file (config/clients.yml in this example):
117
121
  customer_identifier: your_organization
118
122
  customer_identifier_param: customer_id
119
123
  valid_domains: ['your_organization.com', 'your_organization.org']
124
+ valid_ips: ['192.168.1.1', '192.168.1.2']
120
125
  strategy: :my_auth_strategy
121
126
  custom_key: custom_value
122
127
 
@@ -131,7 +136,34 @@ hash-auth initializer:
131
136
 
132
137
  **_Options in hash-auth initializer_**
133
138
 
134
- Any custom client field can be initialized with a default value through method missing
139
+ Enable ip or reverse dns filtering
140
+
141
+ ```ruby
142
+ HashAuth.configure do
143
+ domain_auth :ip
144
+ end
145
+
146
+ # or
147
+
148
+ HashAuth.configure do
149
+ domain_auth :reverse_dns
150
+ end
151
+ ```
152
+
153
+ Reverse DNS filtering caches the result. You have the option to namespace how that data gets stored. Note: client.
154
+
155
+ ```ruby
156
+ HashAuth.configure do
157
+ cache_store_namespace "foo"
158
+ end
159
+
160
+ # will store the reverse dns lookup associated with an ip address
161
+ # as "foo-#{ip}" in the Rails.cache
162
+ # Example: 192.168.1.1 whould become foo-192.168.1.1
163
+ # Defaults to "hash-auth"
164
+ ```
165
+
166
+ Any custom client field can be initialized with a default value through method missing (set_default_*)
135
167
 
136
168
  HashAuth.configure do
137
169
  set_default_authentication_success_status_message {:status => "success" }
@@ -194,8 +226,6 @@ WebRequest supports:
194
226
 
195
227
  See [REST client](https://github.com/rest-client/rest-client) for futher detail.
196
228
 
197
-
198
- ## Examples
199
-
229
+ ## License
200
230
 
201
231
  This project rocks and uses MIT-LICENSE.
@@ -10,7 +10,6 @@ HashAuth.configure do
10
10
  # YAML::load( File.open('../clients.yml') )
11
11
  # end
12
12
 
13
-
14
13
  ## Any attributes can be added to a client object at initialization.
15
14
  #### The default can be added to HashAuth configuration and will be picked up in every strategy automagically
16
15
  #### Default values must be set with set_default_* methods and will be picked up by [client].* methods
@@ -19,10 +18,12 @@ HashAuth.configure do
19
18
  #
20
19
  # client.foo_bar will return 'bar baz' unless specifically set for that client
21
20
 
22
-
23
21
  ## Set default strategy by handle
24
22
  # set_default_strategy :strategy_fifty_one
25
23
 
26
-
24
+ ## Set ip specific filtering
25
+ # domain_auth :ip
26
+ ## OR set ip filtering by reverse_dns (uses reverse dns lookup for the remote_ip provided in a request)
27
+ # domain_auth :reverse_dns
27
28
 
28
29
  end
@@ -78,6 +78,7 @@ module HashAuth
78
78
  end
79
79
  client[:strategy] = HashAuth.find_strategy(client[:strategy]) if client[:strategy]
80
80
  client[:valid_domains] = [client[:valid_domains]] unless client[:valid_domains].kind_of? Array
81
+ client[:valid_ips] = [client[:valid_ips]] unless client[:valid_ips].kind_of? Array
81
82
  HashAuth::Client.new client
82
83
  end
83
84
 
@@ -90,6 +91,13 @@ module HashAuth
90
91
  singleton = (class << @config; self end)
91
92
  singleton.send :define_method, "#{default_var_name}".to_sym do instance_variable_get("@#{default_var_name}") end
92
93
  end
94
+ else
95
+ var_name = method
96
+ @config.instance_variable_set("@#{var_name}", args[0])
97
+ if @config.respond_to?("#{var_name}".to_sym) == false
98
+ singleton = (class << @config; self end)
99
+ singleton.send :define_method, "#{var_name}".to_sym do instance_variable_get("@#{var_name}") end
100
+ end
93
101
  end
94
102
  end
95
103
  end
@@ -106,5 +114,13 @@ module HashAuth
106
114
  @default_strategy || HashAuth::Strategies::Default
107
115
  end
108
116
 
117
+ def cache_store_namespace
118
+ @cache_store_namespace.to_s || "hash-auth"
119
+ end
120
+
121
+ def domain_auth
122
+ @domain_auth || :none
123
+ end
124
+
109
125
  end
110
126
  end
@@ -1,3 +1,5 @@
1
+ require 'resolv'
2
+
1
3
  module HashAuth
2
4
  module Controllers
3
5
  module Helpers
@@ -18,18 +20,20 @@ module HashAuth
18
20
  @client = extract_client_from_request
19
21
  return HashAuth.configuration.default_strategy.on_failure(nil, self, :no_matching_client) unless @client
20
22
 
21
- valid_domain = check_host(request.host)
22
- return @client.strategy.on_failure(@client, self, :invalid_domain) unless valid_domain
23
+ case HashAuth.configuration.domain_auth
24
+ when :ip
25
+ return @client.strategy.on_failure(@client, self, :invalid_ip) unless check_ip(request.remote_ip)
26
+ when :reverse_dns
27
+ domain = extract_domain(request.remote_ip)
28
+ return @client.strategy.on_failure(@client, self, :invalid_domain) unless check_host(domain)
29
+ end
23
30
 
24
31
  string_to_hash = @client.strategy.acquire_string_to_hash self, @client
25
32
  target_string = @client.strategy.hash_string @client, string_to_hash
26
- @authenticated = @client.strategy.verify_hash(target_string, @client, self) && valid_domain
27
-
28
- if @authenticated
29
- @client.strategy.on_authentication @client, self
30
- else
31
- @client.strategy.on_failure(@client, self, :invalid_hash)
32
- end
33
+ @authenticated = @client.strategy.verify_hash(target_string, @client, self)
34
+
35
+ return @client.strategy.on_authentication @client, self if @authenticated
36
+ @client.strategy.on_failure(@client, self, :invalid_hash)
33
37
  end
34
38
 
35
39
  def extract_client_from_request
@@ -39,6 +43,20 @@ module HashAuth
39
43
  nil
40
44
  end
41
45
 
46
+ def extract_domain(ip)
47
+ cache_address = "#{HashAuth.configuration.cache_store_namespace}-#{ip}"
48
+ cached_result = Rails.cache.read cache_address
49
+ return cached_result unless cached_result == nil
50
+ hostname = Resolv.new.getname(ip)
51
+ Rails.cache.write cache_address, hostname
52
+ hostname
53
+ end
54
+
55
+ def check_ip(ip)
56
+ @client.valid_ips.each { |valid_ip| return true if ip == valid_ip }
57
+ false
58
+ end
59
+
42
60
  def check_host(host)
43
61
  @client.valid_domains.each do |d|
44
62
  match = regexp_from_host(d).match(host)
@@ -7,12 +7,13 @@ module HashAuth
7
7
  end
8
8
 
9
9
  def self.acquire_string_to_hash(controller, client)
10
- params = controller.params.select{|k,v| k != 'controller' && k != 'action' && k != client.signature_param }.map{|k,v| "#{k}=#{v}"}.join('&')
10
+ params = controller.params.select{|k,v| !['controller', 'action', 'format', client.signature_param].include? k }.map{|k,v| "#{k}=#{v}"}.join('&')
11
11
  params + client.customer_key.to_s
12
12
  end
13
13
 
14
14
  def self.hash_string(client, string)
15
- Digest::SHA2.new(256) << string
15
+ digest = Digest::SHA256.new
16
+ digest.hexdigest string
16
17
  end
17
18
 
18
19
  def self.verify_hash(target_string, client, controller)
@@ -25,9 +26,16 @@ module HashAuth
25
26
  end
26
27
 
27
28
  def self.on_failure(client, controller, type)
28
- controller.instance_variable_set '@failure_message', 'Not a valid client' if type == :no_matching_client
29
- controller.instance_variable_set '@failure_message', 'Request coming from invalid domain' if type == :invalid_domain
30
- controller.instance_variable_set '@failure_message', 'Signature hash is invalid' if type == :invalid_hash
29
+ case type
30
+ when :no_matching_client
31
+ controller.instance_variable_set '@failure_message', 'Not a valid client'
32
+ when :invalid_domain
33
+ controller.instance_variable_set '@failure_message', 'Request coming from invalid domain'
34
+ when :invalid_hash
35
+ controller.instance_variable_set '@failure_message', 'Signature hash is invalid'
36
+ when :invalid_ip
37
+ controller.instance_variable_set '@failure_message', 'Request coming from invalid IP'
38
+ end
31
39
  end
32
40
 
33
41
  def self.sign_request(client, verb, params)
@@ -1,3 +1,3 @@
1
1
  module HashAuth
2
- VERSION = "0.1.1"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -1,41 +0,0 @@
1
- require File.expand_path('../../spec_helper', __FILE__)
2
-
3
- describe TestRailsApp::TestController do
4
-
5
- it "extracts client from request" do
6
- controller.params = Request.parse_params 'a=b&c=d&customer_id=my_organization'
7
- client = controller.extract_client_from_request_helper
8
- client.customer_key.should == 1234567890
9
- end
10
-
11
- it "checks the request host for a clients valid domain" do
12
- controller.params = Request.parse_params 'a=b&c=d&customer_id=my_organization'
13
- controller.instance_variable_set '@client', controller.extract_client_from_request_helper
14
- controller.check_host_helper('localhost').should == true
15
- controller.check_host_helper('localhostwithstuffafterit').should == false
16
- controller.check_host_helper('prependinglocalhost').should == false
17
- controller.check_host_helper('localSTUFFhost').should == false
18
- end
19
-
20
- it "checks the request host for client's valid domain with wildcarding" do
21
- controller.params = Request.parse_params 'a=b&c=d&customer_id=your_organization'
22
- controller.instance_variable_set '@client', controller.extract_client_from_request_helper
23
- controller.check_host_helper('maps.google.com').should == true
24
- controller.check_host_helper('google.com').should == true
25
- controller.check_host_helper('google.coma').should == false
26
- controller.check_host_helper('google.co.uk').should == false
27
- controller.check_host_helper('hello.org').should == true
28
- controller.check_host_helper('org').should == false
29
- end
30
-
31
- it "executes on_failure block when request parameters do not match a client" do
32
- controller.params = Request.parse_params 'a=b&c=d&customer_id=not_an_organization'
33
- controller.verify_hash_helper
34
- controller.instance_variable_get('@failure_message').should_not == nil
35
- end
36
-
37
- it "responds on_failure when authentication fails" do
38
- controller.params = Request.parse_params 'a=b&c=d&customer_id=test&signature=abcde'
39
- expect{controller.verify_hash_helper}.to raise_error(OnFailureError)
40
- end
41
- end
@@ -0,0 +1,108 @@
1
+ #### Faking a rails application that is configured with HashAuth for spec purposes
2
+
3
+ class OnFailureError < Exception
4
+ end
5
+
6
+ module HashAuth
7
+ module Strategies
8
+ class New < Base
9
+
10
+ def self.identifier
11
+ :new
12
+ end
13
+
14
+ def self.acquire_string_to_hash(controller, client)
15
+ controller.params.select{|k,v| k != 'controller' && k != 'action' && k != client.signature_parameter }.map{|k,v| "#{k}=#{v}"}.join('&')
16
+ end
17
+
18
+ def self.hash_string(client, string)
19
+ Digest::MD5.digest string
20
+ end
21
+
22
+ def self.verify_hash(target_string, client, controller)
23
+ raise 'Parameters do not contain this client\'s signature_parameter' if controller.params[client.signature_parameter] == nil
24
+ target_string == controller.params[client.signature_parameter]
25
+ end
26
+
27
+ def self.on_authentication(client, controller)
28
+ # Do nothing
29
+ end
30
+
31
+ def self.on_failure(client, controller, type)
32
+ raise OnFailureError, "Failure to authenticate"
33
+ # Do nothingå
34
+ end
35
+
36
+ end
37
+ end
38
+ end
39
+
40
+ # clients = [
41
+ # {
42
+ # :customer_key => 1234567890,
43
+ # :customer_identifier => 'my_organization',
44
+ # :customer_identifier_param => 'customer_id',
45
+ # :valid_domains => 'localhost',
46
+ # :strategy => :default
47
+ # },
48
+ # {
49
+ # :customer_key => 987654321,
50
+ # :customer_identifier => 'your_organization',
51
+ # :customer_identifier_param => 'customer_id',
52
+ # :valid_domains => ['*google.com', '*.org'],
53
+ # :strategy => :default
54
+ # },
55
+ # {
56
+ # :customer_key => 'zyxwvut',
57
+ # :customer_identifier => 'test',
58
+ # :valid_domains => '*',
59
+ # :strategy => :new
60
+ # },
61
+ # {
62
+ # :customer_key => 9988776655,
63
+ # :customer_identifier => 'no_matching_client',
64
+ # :customer_identifier_param => 'customer_id',
65
+ # :valid_domains => 'localhost',
66
+ # :strategy => :default
67
+ # },
68
+ # {
69
+ # :customer_key => 'something other than will be on server',
70
+ # :customer_identifier => 'incorrect_hash',
71
+ # :customer_identifier_param => 'customer_id',
72
+ # :valid_domains => 'localhost',
73
+ # :strategy => :default
74
+ # }
75
+ # ]
76
+
77
+ # Faking controller/action requests
78
+ class RequestHelper
79
+
80
+ def self.parse_params(string)
81
+ h = {}
82
+ s = string.split('&').map{|set| set.split '=' }.each do |p|
83
+ h[p[0]] = p[1]
84
+ end
85
+ h
86
+ end
87
+
88
+ def initialize(hash)
89
+ hash.each do |key,value|
90
+ add_instance_getters_and_setters key
91
+ send "#{key}=", value
92
+ end
93
+ end
94
+
95
+ # Add instance specific getters and setters for the name passed in,
96
+ # so as to allow different Client objects to have different properties
97
+ # that are accessible by . notation
98
+ def add_instance_getters_and_setters(var)
99
+ singleton = (class << self; self end)
100
+ singleton.send :define_method, var.to_sym do
101
+ instance_variable_get "@#{var}"
102
+ end
103
+ singleton.send :define_method, "#{var}=".to_sym do |val|
104
+ instance_variable_set "@#{var}", val
105
+ end
106
+ end
107
+
108
+ end
@@ -1,8 +1,10 @@
1
- require 'rails'
1
+ require 'rails/all'
2
2
  require 'active_support/all'
3
3
  require 'action_controller/railtie'
4
4
  require 'hash-auth'
5
- require 'fake-rails-app'
5
+ require 'rails-helpers'
6
+ require File.expand_path('../../test/dummy/spec/spec_helper', __FILE__)
7
+ require File.expand_path('../../test/dummy/spec/controllers/helper_spec', __FILE__)
6
8
  RSpec.configure do |config|
7
9
  config.order = "random"
8
10
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hash-auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-05 00:00:00.000000000 Z
12
+ date: 2013-05-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -75,11 +75,11 @@ files:
75
75
  - Rakefile
76
76
  - README.md
77
77
  - spec/controllers/helper_spec.rb
78
- - spec/fake-rails-app.rb
79
78
  - spec/lib/client_spec.rb
80
79
  - spec/lib/config_spec.rb
81
80
  - spec/lib/railtie_spec.rb
82
81
  - spec/lib/web_request_spec.rb
82
+ - spec/rails-helpers.rb
83
83
  - spec/spec_helper.rb
84
84
  homepage: http://maxwells.github.com
85
85
  licenses: []
@@ -108,10 +108,10 @@ summary: Rails gem to authenticate HTTP requests by hashing request components a
108
108
  passing as parameter
109
109
  test_files:
110
110
  - spec/controllers/helper_spec.rb
111
- - spec/fake-rails-app.rb
112
111
  - spec/lib/client_spec.rb
113
112
  - spec/lib/config_spec.rb
114
113
  - spec/lib/railtie_spec.rb
115
114
  - spec/lib/web_request_spec.rb
115
+ - spec/rails-helpers.rb
116
116
  - spec/spec_helper.rb
117
117
  has_rdoc:
@@ -1,168 +0,0 @@
1
- #### Faking a rails application that is configured with HashAuth for spec purposes
2
-
3
- class OnFailureError < Exception
4
- end
5
-
6
- module HashAuth
7
- module Strategies
8
- class New < Base
9
-
10
- def self.identifier
11
- :new
12
- end
13
-
14
- def self.acquire_string_to_hash(controller, client)
15
- controller.params.select{|k,v| k != 'controller' && k != 'action' && k != client.signature_parameter }.map{|k,v| "#{k}=#{v}"}.join('&')
16
- end
17
-
18
- def self.hash_string(client, string)
19
- Digest::MD5.digest string
20
- end
21
-
22
- def self.verify_hash(target_string, client, controller)
23
- raise 'Parameters do not contain this client\'s signature_parameter' if controller.params[client.signature_parameter] == nil
24
- target_string == controller.params[client.signature_parameter]
25
- end
26
-
27
- def self.on_authentication(client, controller)
28
- # Do nothing
29
- end
30
-
31
- def self.on_failure(client, controller, type)
32
- raise OnFailureError, "Failure to authenticate"
33
- # Do nothingå
34
- end
35
-
36
- end
37
- end
38
- end
39
-
40
- clients = [
41
- {
42
- :customer_key => 1234567890,
43
- :customer_identifier => 'my_organization',
44
- :customer_identifier_param => 'customer_id',
45
- :valid_domains => 'localhost',
46
- :strategy => :default
47
- },
48
- {
49
- :customer_key => 987654321,
50
- :customer_identifier => 'your_organization',
51
- :customer_identifier_param => 'customer_id',
52
- :valid_domains => ['*google.com', '*.org'],
53
- :strategy => :default
54
- },
55
- {
56
- :customer_key => 'zyxwvut',
57
- :customer_identifier => 'test',
58
- :valid_domains => '*',
59
- :strategy => :new
60
- },
61
- {
62
- :customer_key => 9988776655,
63
- :customer_identifier => 'no_matching_client',
64
- :customer_identifier_param => 'customer_id',
65
- :valid_domains => 'localhost',
66
- :strategy => :default
67
- },
68
- {
69
- :customer_key => 'something other than will be on server',
70
- :customer_identifier => 'incorrect_hash',
71
- :customer_identifier_param => 'customer_id',
72
- :valid_domains => 'localhost',
73
- :strategy => :default
74
- }
75
- ]
76
-
77
- HashAuth.configure do
78
-
79
- ## Block to allow dynamic loading of customer keys (Optional)
80
- #### Could be from YAML
81
- #### Could be from DB
82
-
83
- #set_default_customer_identifier_param
84
-
85
- add_clients clients
86
-
87
- set_default_signature_parameter 'signature'
88
-
89
- end
90
-
91
- module TestRailsApp
92
-
93
- class Application < Rails::Application
94
- # app config here
95
- # config.secret_token = '572c86f5ede338bd8aba8dae0fd3a326aabababc98d1e6ce34b9f5'
96
- routes.draw do
97
- match "test_rails_app/test/one" => "test#one"
98
- match "/test/two" => "test#two"
99
- match "/test/three" => "test#three"
100
- end
101
- end
102
-
103
- class ApplicationController < ActionController::Base
104
- # setup
105
- end
106
-
107
- class TestController < ApplicationController
108
- validates_auth_for :one, :two
109
-
110
- def one
111
- end
112
-
113
- def two
114
- end
115
-
116
- def three
117
- end
118
-
119
- def extract_client_from_request_helper
120
- extract_client_from_request
121
- end
122
-
123
- def check_host_helper(host)
124
- check_host(host)
125
- end
126
-
127
- def verify_hash_helper
128
- verify_hash
129
- end
130
-
131
- end
132
-
133
- require 'rspec/rails'
134
-
135
- end
136
-
137
- # Faking controller/action requests
138
- class Request
139
-
140
- def self.parse_params(string)
141
- h = {}
142
- s = string.split('&').map{|set| set.split '=' }.each do |p|
143
- h[p[0]] = p[1]
144
- end
145
- h
146
- end
147
-
148
- def initialize(hash)
149
- hash.each do |key,value|
150
- add_instance_getters_and_setters key
151
- send "#{key}=", value
152
- end
153
- end
154
-
155
- # Add instance specific getters and setters for the name passed in,
156
- # so as to allow different Client objects to have different properties
157
- # that are accessible by . notation
158
- def add_instance_getters_and_setters(var)
159
- singleton = (class << self; self end)
160
- singleton.send :define_method, var.to_sym do
161
- instance_variable_get "@#{var}"
162
- end
163
- singleton.send :define_method, "#{var}=".to_sym do |val|
164
- instance_variable_set "@#{var}", val
165
- end
166
- end
167
-
168
- end