http_signature 0.1.0 → 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.
- checksums.yaml +5 -5
- data/.github/workflows/ci.yml +32 -0
- data/.github/workflows/standardrb.yml +15 -0
- data/.gitignore +2 -0
- data/.ruby-version +1 -1
- data/Gemfile +3 -2
- data/Gemfile.lock +143 -10
- data/README.md +78 -118
- data/Rakefile +3 -3
- data/bin/standardrb +16 -0
- data/http_signature.gemspec +26 -18
- data/lib/http_signature/faraday.rb +10 -8
- data/lib/http_signature/rack.rb +30 -47
- data/lib/http_signature/rails.rb +67 -0
- data/lib/http_signature/version.rb +1 -1
- data/lib/http_signature.rb +348 -133
- metadata +80 -12
- data/.circleci/config.yml +0 -0
- data/.rubocop.yml +0 -5
data/lib/http_signature/rack.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "http_signature"
|
|
4
|
+
require "rack"
|
|
4
5
|
|
|
5
6
|
# Rack middleware using http-signature gem to validate signature on every incoming request
|
|
6
7
|
class HTTPSignature::Rack
|
|
@@ -14,46 +15,38 @@ class HTTPSignature::Rack
|
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def call(env)
|
|
17
|
-
request = Rack::Request.new(env)
|
|
18
|
+
request = ::Rack::Request.new(env)
|
|
18
19
|
|
|
19
20
|
return @app.call(env) if path_excluded?(request.path)
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
request_headers = parse_request_headers(request)
|
|
23
|
+
signature_input_header = request_headers["signature-input"]
|
|
24
|
+
signature_header = request_headers["signature"]
|
|
25
|
+
return [401, {}, ["No signature header"]] unless signature_input_header && signature_header
|
|
22
26
|
|
|
23
27
|
begin
|
|
24
|
-
request_body =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
request_body =
|
|
29
|
+
if request.body
|
|
30
|
+
body_content = request.body.read
|
|
31
|
+
request.body.rewind if request.body.respond_to?(:rewind)
|
|
32
|
+
body_content
|
|
33
|
+
else
|
|
34
|
+
""
|
|
35
|
+
end
|
|
36
|
+
valid_signature = HTTPSignature.valid?(
|
|
37
|
+
url: request.url,
|
|
38
|
+
method: request.request_method,
|
|
39
|
+
headers: request_headers,
|
|
40
|
+
body: request_body || "",
|
|
41
|
+
key_resolver: ->(key_id) { HTTPSignature.key(key_id) }
|
|
42
|
+
)
|
|
43
|
+
rescue HTTPSignature::SignatureError
|
|
44
|
+
return [401, {}, ["Invalid signature"]]
|
|
30
45
|
end
|
|
31
46
|
|
|
32
|
-
|
|
47
|
+
return [401, {}, ["Invalid signature"]] unless valid_signature
|
|
33
48
|
|
|
34
|
-
|
|
35
|
-
url: request.path,
|
|
36
|
-
method: request.request_method,
|
|
37
|
-
headers: headers_to_sign,
|
|
38
|
-
key: key,
|
|
39
|
-
key_id: parsed_signature['keyId'],
|
|
40
|
-
algorithm: parsed_signature['algorithm'],
|
|
41
|
-
body: request_body ? request_body : '',
|
|
42
|
-
query_string_params: Rack::Utils.parse_nested_query(request.query_string)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
valid_signature =
|
|
46
|
-
if parsed_signature['algorithm'].include?('rsa')
|
|
47
|
-
HTTPSignature.valid?(**params)
|
|
48
|
-
else
|
|
49
|
-
HTTPSignature.create(**params) == request_headers['signature']
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
if valid_signature
|
|
53
|
-
@app.call(env)
|
|
54
|
-
else
|
|
55
|
-
[401, {}, ['Invalid signature :(']]
|
|
56
|
-
end
|
|
49
|
+
@app.call(env)
|
|
57
50
|
end
|
|
58
51
|
|
|
59
52
|
private
|
|
@@ -62,27 +55,17 @@ class HTTPSignature::Rack
|
|
|
62
55
|
request_headers = {}
|
|
63
56
|
|
|
64
57
|
request.each_header do |header|
|
|
65
|
-
if header[0].include?(
|
|
66
|
-
request_headers[header[0].gsub(
|
|
58
|
+
if header[0].include?("HTTP_") && header[0] != "HTTP_VERSION"
|
|
59
|
+
request_headers[header[0].gsub("HTTP_", "").tr("_", "-").downcase] = header[1]
|
|
67
60
|
end
|
|
68
61
|
end
|
|
69
62
|
|
|
70
63
|
request_headers
|
|
71
64
|
end
|
|
72
65
|
|
|
73
|
-
def parse_signature(request_headers)
|
|
74
|
-
Rack::Utils.parse_nested_query(
|
|
75
|
-
request_headers['signature'].gsub(',', '&')
|
|
76
|
-
).map do |k, v|
|
|
77
|
-
[k, v.tr('"', '')]
|
|
78
|
-
end.to_h
|
|
79
|
-
end
|
|
80
|
-
|
|
81
66
|
def path_excluded?(path)
|
|
82
|
-
|
|
83
|
-
path.match(exclude_path)
|
|
67
|
+
self.class.exclude_paths.any? do |exclude_path|
|
|
68
|
+
!!path.match(exclude_path)
|
|
84
69
|
end
|
|
85
|
-
|
|
86
|
-
matches.select { |v| v == true }.length.positive?
|
|
87
70
|
end
|
|
88
71
|
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "http_signature"
|
|
4
|
+
require "action_controller"
|
|
5
|
+
|
|
6
|
+
module HTTPSignature
|
|
7
|
+
module Rails
|
|
8
|
+
module Controller
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# Use as a Rails before_action to enforce HTTP Message Signatures on an action.
|
|
14
|
+
def verify_http_signature!
|
|
15
|
+
request_headers = normalized_request_headers
|
|
16
|
+
signature_input_header = request_headers["signature-input"]
|
|
17
|
+
signature_header = request_headers["signature"]
|
|
18
|
+
|
|
19
|
+
return render status: :unauthorized, plain: "No signature header" unless signature_input_header && signature_header
|
|
20
|
+
|
|
21
|
+
request_body = read_request_body
|
|
22
|
+
|
|
23
|
+
valid_signature = HTTPSignature.valid?(
|
|
24
|
+
url: request.url,
|
|
25
|
+
method: request.request_method,
|
|
26
|
+
headers: request_headers,
|
|
27
|
+
body: request_body || "",
|
|
28
|
+
key_resolver: ->(key_id) { HTTPSignature.key(key_id) }
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
return if valid_signature
|
|
32
|
+
|
|
33
|
+
render status: :unauthorized, plain: "Invalid signature"
|
|
34
|
+
rescue HTTPSignature::SignatureError, ArgumentError
|
|
35
|
+
render status: :unauthorized, plain: "Invalid signature"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def normalized_request_headers
|
|
39
|
+
headers = {}
|
|
40
|
+
|
|
41
|
+
request.each_header do |key, value|
|
|
42
|
+
next unless key.start_with?("HTTP_")
|
|
43
|
+
next if key == "HTTP_VERSION"
|
|
44
|
+
|
|
45
|
+
canonical_key = key.sub("HTTP_", "").tr("_", "-").downcase
|
|
46
|
+
headers[canonical_key] = value
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
%w[CONTENT_TYPE CONTENT_LENGTH].each do |env_key|
|
|
50
|
+
next unless (value = request.get_header(env_key))
|
|
51
|
+
|
|
52
|
+
headers[env_key.downcase.tr("_", "-")] = value
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
headers
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def read_request_body
|
|
59
|
+
return "" unless request.body
|
|
60
|
+
|
|
61
|
+
body_content = request.body.read
|
|
62
|
+
request.body.rewind if request.body.respond_to?(:rewind)
|
|
63
|
+
body_content
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|