rack-simple_auth 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -2
- data/README.md +12 -12
- data/lib/rack/simple_auth/hmac.rb +64 -45
- data/lib/rack/simple_auth/version.rb +1 -1
- data/test/config.ru +2 -2
- data/test/rack/simple_auth/hmac_fail_test.rb +12 -0
- data/test/rack/simple_auth/hmac_test.rb +2 -2
- data/test/test_helper.rb +1 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7da6de52a27fba5b232011cb9259c8c31ba95473
|
4
|
+
data.tar.gz: 35f166b707a7da786ed0358eb958591bd82ecb67
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b7f297881cad83bf4a6123fc2f072e8d487b32f2440f667a9902c16e9ac8af608d07190c88a79a038f51869ec4a795ed39929f0ea9f0fd2932444efe818f90ba
|
7
|
+
data.tar.gz: 758c2e12bc71f147f0a9bce5c5ba9072efc37ca2a6fca980c5f20dc7aa258a0c53ac2698e71a4ca56ba1d8a94670b54c557da74d3d89fb4f216b61a7529f0f57
|
data/.rubocop.yml
CHANGED
@@ -1,2 +1 @@
|
|
1
|
-
|
2
|
-
Max: 160
|
1
|
+
inherit_from: rubocop-todo.yml
|
data/README.md
CHANGED
@@ -25,20 +25,12 @@ Or install it yourself as:
|
|
25
25
|
[![Gem Version](https://badge.fury.io/rb/rack-simple_auth.png)](http://badge.fury.io/rb/rack-simple_auth)
|
26
26
|
[![Dependency Status](https://gemnasium.com/Benny1992/rack-simple_auth.png)](https://gemnasium.com/Benny1992/rack-simple_auth)
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
28
|
## Usage
|
32
29
|
|
33
30
|
### HMAC Authorization
|
34
31
|
|
35
32
|
HMAC should be used for communication between website backend and api server/controller/whatever..
|
36
33
|
|
37
|
-
~~For usage between Server <-> Client a sniffer could easily extract the signature/public key and
|
38
|
-
the encrypted message which is for now the same for the same request (see TODO implement timestamp).~~
|
39
|
-
|
40
|
-
~~With these 2 informations a "secure" backend could be easily seen public...~~
|
41
|
-
|
42
34
|
In version 0.0.5 the timestamp has been added to the msg which will be encrypted, also the possibility to configure the allowed delay a request can have has been added.
|
43
35
|
|
44
36
|
Uses Authorization HTTP Header, example:
|
@@ -56,7 +48,8 @@ config = {
|
|
56
48
|
'DELETE' => 'path',
|
57
49
|
'PUT' => 'path',
|
58
50
|
'PATCH' => 'path'
|
59
|
-
'tolerance' =>
|
51
|
+
'tolerance' => 1,
|
52
|
+
'steps' => 0.1,
|
60
53
|
'signature' => 'signature',
|
61
54
|
'secret' => 'secret',
|
62
55
|
'logpath' => '/path/to/log/file'
|
@@ -73,6 +66,7 @@ Note: Private Key and Signature should be served by a file which is not checked
|
|
73
66
|
|
74
67
|
|
75
68
|
|
69
|
+
|
76
70
|
#### Config Hash
|
77
71
|
|
78
72
|
|
@@ -109,6 +103,14 @@ The tolerance which is configureable in the config hash sets the possible delay
|
|
109
103
|
|
110
104
|
Notice: For a set tolerance a Encrypted Message array will be generated and compared with the MessageHash from the AUTH Header
|
111
105
|
|
106
|
+
In Version 0.1.0 the stepsize option has been added
|
107
|
+
|
108
|
+
You can now specify how many valid hashes are created in a range between eg.: (-1..1) (= tolerance)
|
109
|
+
|
110
|
+
A minimum stepsize of 0.01 is required (0.01 are 10 milliseconds, this is the minimum because of ruby's float disaster and therefore the gem has to use Float#round(2))
|
111
|
+
|
112
|
+
Let me know if you need a smaller stepsize...
|
113
|
+
|
112
114
|
|
113
115
|
#### Logging
|
114
116
|
|
@@ -123,9 +125,6 @@ It contains following information:
|
|
123
125
|
- The Encrypted Message Array which was expected
|
124
126
|
- The Signature which was expected
|
125
127
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
128
|
## TODO
|
130
129
|
|
131
130
|
~~Add Timestamp to encryption..~~
|
@@ -154,3 +153,4 @@ It contains following information:
|
|
154
153
|
|
155
154
|
|
156
155
|
|
156
|
+
|
@@ -10,18 +10,22 @@ module Rack
|
|
10
10
|
@app = app
|
11
11
|
@signature = config['signature'] || ''
|
12
12
|
@secret = config['secret'] || ''
|
13
|
-
@tolerance = config['tolerance'] ||
|
13
|
+
@tolerance = config['tolerance'] || 1 # 0 if tolerance not set in config hash
|
14
14
|
@logpath = config['logpath']
|
15
15
|
@steps = config['steps'] || 1
|
16
16
|
|
17
|
+
valid_stepsize?(0.01)
|
18
|
+
valid_tolerance?
|
19
|
+
|
17
20
|
@config = config
|
18
21
|
end
|
19
22
|
|
20
23
|
# call Method for Rack Middleware/Application
|
21
24
|
# @param [Hash] env [Rack Env Hash which contains headers etc..]
|
22
25
|
def call(env)
|
23
|
-
request = Rack::Request.new(env)
|
24
|
-
|
26
|
+
@request = Rack::Request.new(env)
|
27
|
+
|
28
|
+
if valid_request?
|
25
29
|
@app.call(env)
|
26
30
|
else
|
27
31
|
response = Rack::Response.new('Unauthorized', 401, 'Content-Type' => 'text/html')
|
@@ -30,90 +34,91 @@ module Rack
|
|
30
34
|
end
|
31
35
|
|
32
36
|
# checks for valid HMAC Request
|
33
|
-
# @param [Rack::Request] request [current Request]
|
34
37
|
# @return [boolean] ValidationStatus [If authorized returns true, else false]
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
if request.env['HTTP_AUTHORIZATION'].nil?
|
39
|
-
log(request, hash_array)
|
38
|
+
def valid_request?
|
39
|
+
if @request.env['HTTP_AUTHORIZATION'].nil?
|
40
|
+
log(allowed_messages)
|
40
41
|
|
41
42
|
return false
|
42
43
|
end
|
43
44
|
|
44
|
-
|
45
|
-
message_hash = auth_array[0]
|
46
|
-
signature = auth_array[1]
|
47
|
-
|
48
|
-
if signature == @signature && hash_array.include?(message_hash)
|
45
|
+
if request_signature == @signature && allowed_messages.include?(request_message)
|
49
46
|
true
|
50
47
|
else
|
51
|
-
log(
|
48
|
+
log(allowed_messages)
|
52
49
|
|
53
50
|
false
|
54
51
|
end
|
55
52
|
end
|
56
53
|
|
54
|
+
private
|
55
|
+
|
56
|
+
# Get request signature
|
57
|
+
def request_signature
|
58
|
+
@request.env['HTTP_AUTHORIZATION'].split(':').last
|
59
|
+
end
|
60
|
+
|
61
|
+
# Get encrypted request message
|
62
|
+
def request_message
|
63
|
+
@request.env['HTTP_AUTHORIZATION'].split(':').first
|
64
|
+
end
|
65
|
+
|
57
66
|
# Builds Array of allowed message hashs
|
58
|
-
# @param [Rack::Request] request [current Request]
|
59
67
|
# @return [Array] hash_array [allowed message hashes as array]
|
60
|
-
def
|
61
|
-
|
68
|
+
def allowed_messages
|
69
|
+
messages = []
|
62
70
|
|
63
71
|
(-(@tolerance)..@tolerance).step(@steps) do |i|
|
64
72
|
i = i.round(2)
|
65
|
-
|
73
|
+
messages << OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), @secret, message(i))
|
66
74
|
end
|
67
75
|
|
68
|
-
|
76
|
+
messages
|
69
77
|
end
|
70
78
|
|
71
79
|
# Get Message for current Request and delay
|
72
|
-
# @param [Rack::Request] request [current Request]
|
73
80
|
# @param [Fixnum] delay [delay in timestamp format]
|
74
81
|
# @return [Hash] message [message which will be encrypted]
|
75
|
-
def message(
|
82
|
+
def message(delay = 0)
|
76
83
|
date = Time.now.to_i + delay
|
84
|
+
date = date.to_i if delay.eql?(0.0)
|
77
85
|
|
78
|
-
|
79
|
-
date = date.to_i
|
80
|
-
end
|
81
|
-
|
82
|
-
case request.request_method
|
86
|
+
case @request.request_method
|
83
87
|
when 'GET'
|
84
|
-
return { 'method' => request.request_method, 'date' => date, 'data' => request_data(
|
88
|
+
return { 'method' => @request.request_method, 'date' => date, 'data' => request_data(@config) }.to_json
|
85
89
|
when 'POST'
|
86
|
-
return { 'method' => request.request_method, 'date' => date, 'data' => request_data(
|
90
|
+
return { 'method' => @request.request_method, 'date' => date, 'data' => request_data(@config) }.to_json
|
87
91
|
when 'DELETE'
|
88
|
-
return { 'method' => request.request_method, 'date' => date, 'data' => request_data(
|
92
|
+
return { 'method' => @request.request_method, 'date' => date, 'data' => request_data(@config) }.to_json
|
89
93
|
when 'PUT'
|
90
|
-
return { 'method' => request.request_method, 'date' => date, 'data' => request_data(
|
94
|
+
return { 'method' => @request.request_method, 'date' => date, 'data' => request_data(@config) }.to_json
|
91
95
|
when 'PATCH'
|
92
|
-
return { 'method' => request.request_method, 'date' => date, 'data' => request_data(
|
96
|
+
return { 'method' => @request.request_method, 'date' => date, 'data' => request_data(@config) }.to_json
|
93
97
|
end
|
94
98
|
end
|
95
99
|
|
96
100
|
# Get Request Data specified by Config
|
97
|
-
# @param [Rack::Request] request [current Request]
|
98
101
|
# @param [Hash] config [Config Hash containing what type of info is data for each request]
|
99
102
|
# @return [String|Hash] data [Data for each request]
|
100
|
-
def request_data(
|
101
|
-
if config[request.request_method] == 'path' || config[request.request_method] == 'params'
|
102
|
-
request.send(config[request.request_method].to_sym)
|
103
|
+
def request_data(config)
|
104
|
+
if config[@request.request_method] == 'path' || config[@request.request_method] == 'params'
|
105
|
+
@request.send(config[@request.request_method].to_sym)
|
103
106
|
else
|
104
|
-
fail "Not a valid option #{config[request.request_method]} - Use either params or path"
|
107
|
+
fail "Not a valid option #{config[@request.request_method]} - Use either params or path"
|
105
108
|
end
|
106
109
|
end
|
107
110
|
|
108
111
|
# Log to @logpath if request is unathorized
|
109
|
-
#
|
110
|
-
|
112
|
+
# Contains:
|
113
|
+
# - allowed messages and received message
|
114
|
+
# - time when request was made
|
115
|
+
# - type of request
|
116
|
+
# - requested path
|
117
|
+
def log(hash_array)
|
111
118
|
if @logpath
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
log = "#{Time.new} - #{method} #{path} - 400 Unauthorized - HTTP_AUTHORIZATION: #{request.env['HTTP_AUTHORIZATION']}\n"
|
116
|
-
log << "Auth Message Config: #{@config[request.request_method]}\n"
|
119
|
+
log = "#{Time.new} - #{@request.request_method} #{@request.path} - 400 Unauthorized\n"
|
120
|
+
log << "HTTP_AUTHORIZATION: #{@request.env['HTTP_AUTHORIZATION']}\n"
|
121
|
+
log << "Auth Message Config: #{@config[@request.request_method]}\n"
|
117
122
|
|
118
123
|
if hash_array
|
119
124
|
log << "Allowed Encrypted Messages:\n"
|
@@ -130,7 +135,21 @@ module Rack
|
|
130
135
|
end
|
131
136
|
end
|
132
137
|
|
133
|
-
|
138
|
+
# Check if Stepsize is valid, if > min ensure stepsize is min stepsize
|
139
|
+
# @param [float] min [minimum allowed stepsize]
|
140
|
+
def valid_stepsize?(min)
|
141
|
+
if @steps < min
|
142
|
+
puts "Warning: Minimum allowed stepsize is #{min}"
|
143
|
+
@steps = min
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Check if tolerance is valid, tolerance must be greater than stepsize
|
148
|
+
def valid_tolerance?
|
149
|
+
if @tolerance < @steps
|
150
|
+
fail "Tolerance must be greater than stepsize - Tolerance: #{@tolerance}, Stepsize: #{@steps}"
|
151
|
+
end
|
152
|
+
end
|
134
153
|
end
|
135
154
|
end
|
136
155
|
end
|
data/test/config.ru
CHANGED
@@ -7,11 +7,11 @@ config = {
|
|
7
7
|
'DELETE' => 'path',
|
8
8
|
'PUT' => 'path',
|
9
9
|
'PATCH' => 'path',
|
10
|
-
'tolerance' =>
|
10
|
+
'tolerance' => 0.5,
|
11
11
|
'signature' => 'test_signature',
|
12
12
|
'secret' => 'test_secret',
|
13
13
|
'logpath' => "#{File.expand_path('..', __FILE__)}/logs",
|
14
|
-
'steps' => 0.
|
14
|
+
'steps' => 0.01
|
15
15
|
}
|
16
16
|
|
17
17
|
use Rack::SimpleAuth::HMAC, config
|
@@ -21,6 +21,18 @@ class HMACFailTest < MiniTest::Unit::TestCase
|
|
21
21
|
assert_raises(RuntimeError) { get uri, {}, 'HTTP_AUTHORIZATION' => "#{hash}:#{@signature}" }
|
22
22
|
end
|
23
23
|
|
24
|
+
def test_fail_step
|
25
|
+
out, err = capture_io do
|
26
|
+
Rack::Builder.parse_file("#{Rack::SimpleAuth.root}/test/config_fail_step.ru").first
|
27
|
+
end
|
28
|
+
|
29
|
+
assert_match('Warning: Minimum allowed stepsize is 0.01', out, 'Warning should be printed if stepsize is below 0.01')
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_fail_tolerance
|
33
|
+
assert_raises(RuntimeError) { Rack::Builder.parse_file("#{Rack::SimpleAuth.root}/test/config_fail_tolerance.ru").first }
|
34
|
+
end
|
35
|
+
|
24
36
|
def teardown
|
25
37
|
end
|
26
38
|
end
|
@@ -41,7 +41,7 @@ class HMACTest < MiniTest::Unit::TestCase
|
|
41
41
|
get uri, {}, 'HTTP_AUTHORIZATION' => "#{hash}:#{@signature}"
|
42
42
|
|
43
43
|
assert_equal(200, last_response.status, 'Delay in tolerance range should receive 200')
|
44
|
-
|
44
|
+
end
|
45
45
|
|
46
46
|
def test_get_with_too_big_delay
|
47
47
|
uri = '/'
|
@@ -55,7 +55,7 @@ class HMACTest < MiniTest::Unit::TestCase
|
|
55
55
|
|
56
56
|
def test_get_with_wrong_step
|
57
57
|
uri = '/'
|
58
|
-
message = { 'method' => 'GET', 'date' => Time.now.to_i + 0.
|
58
|
+
message = { 'method' => 'GET', 'date' => Time.now.to_i + 0.035, 'data' => uri }.to_json
|
59
59
|
hash = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), @secret, message)
|
60
60
|
|
61
61
|
get uri, {}, 'HTTP_AUTHORIZATION' => "#{hash}:#{@signature}"
|
data/test/test_helper.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
ENV['RACK_ENV']='test'
|
1
|
+
ENV['RACK_ENV'] = 'test'
|
2
2
|
|
3
3
|
require 'simplecov'
|
4
4
|
require 'coveralls'
|
@@ -44,4 +44,3 @@ Rack::SimpleAuth.failapp = Rack::Builder.parse_file("#{Rack::SimpleAuth.root}/te
|
|
44
44
|
|
45
45
|
@logpath = "#{File.expand_path("..", __FILE__)}/logs"
|
46
46
|
system("mkdir #{@logpath}")
|
47
|
-
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-simple_auth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Benny1992
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-04-
|
11
|
+
date: 2014-04-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -135,3 +135,4 @@ signing_key:
|
|
135
135
|
specification_version: 4
|
136
136
|
summary: SimpleAuth HMAC authentication
|
137
137
|
test_files: []
|
138
|
+
has_rdoc:
|