hash-auth 0.1.1 → 1.0.0

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