rack-simple_auth 0.1.0 → 0.1.1
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.
- 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
|
[](http://badge.fury.io/rb/rack-simple_auth)
|
26
26
|
[](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:
|