linzer 0.5.2 → 0.6.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/.standard.yml +3 -4
- data/CHANGELOG.md +16 -0
- data/README.md +3 -0
- data/lib/linzer/common.rb +21 -2
- data/lib/linzer/message.rb +159 -28
- data/lib/linzer/request.rb +3 -1
- data/lib/linzer/response.rb +1 -1
- data/lib/linzer/version.rb +1 -1
- metadata +28 -11
- data/linzer.gemspec +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d2ec6a2a846f2bb066bdd658c2e50a869ceff52f65f3e5f8670cc5509af6069c
|
4
|
+
data.tar.gz: 5401df5ea61c39290b61a21cf19c4eaddc537abe908067a47688c1f209c982e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 53c504c5c0f886d3e73332d7d8bf7072137519a218ca6a005824f8ad073fb589dd91f2e7980aa02b232c17dd80a87923141e57b094087fb4f412dddbf5588a9f
|
7
|
+
data.tar.gz: 0a99fc9611e6b26ae820fecfe09e10a6c610be20bdf8f9c3fd7c6fab031f82f6f7fa109359470a2a65b7d7cd9817c21259e59fe6f99e50a1c6326d965b0df944
|
data/.standard.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.6.1] - 2024-12-02
|
4
|
+
|
5
|
+
- Add more validation checks on HTTP field component identifiers and parameters.
|
6
|
+
|
7
|
+
- Relax rack version requirements.
|
8
|
+
|
9
|
+
- Update uri dependency to the latest version.
|
10
|
+
|
11
|
+
## [0.6.0] - 2024-04-06
|
12
|
+
|
13
|
+
- Support parameters on HTTP field component identifiers.
|
14
|
+
|
15
|
+
- Add a work-around for failed unit-tests in ruby HEAD CI jobs.
|
16
|
+
|
17
|
+
- Set up simplecov and improve unit-tests coverage.
|
18
|
+
|
3
19
|
## [0.5.2] - 2024-04-02
|
4
20
|
|
5
21
|
- Make all unit tests pass on Ruby 3.0:
|
data/README.md
CHANGED
@@ -159,6 +159,9 @@ Please note that is still early days and extensive testing is still ongoing. For
|
|
159
159
|
I'll be expanding the library to cover more functionality specified in the RFC
|
160
160
|
in subsequent releases.
|
161
161
|
|
162
|
+
## Ruby version compatibility
|
163
|
+
|
164
|
+
linzer is built in [Continuous Integration](https://github.com/nomadium/linzer/actions/workflows/main.yml) on Ruby 3.0+.
|
162
165
|
|
163
166
|
## Development
|
164
167
|
|
data/lib/linzer/common.rb
CHANGED
@@ -18,10 +18,29 @@ module Linzer
|
|
18
18
|
if components.include?("@signature-params")
|
19
19
|
raise Error.new "Invalid component in signature input"
|
20
20
|
end
|
21
|
+
|
21
22
|
msg = "Cannot verify signature. Missing component in message: %s"
|
22
|
-
components.each
|
23
|
+
components.each do |c|
|
24
|
+
raise Error.new msg % "\"#{c}\"" unless message.field?(c)
|
25
|
+
end
|
26
|
+
|
27
|
+
validate_uniqueness components
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate_uniqueness(components)
|
23
31
|
msg = "Invalid signature. Duplicated component in signature input."
|
24
|
-
|
32
|
+
|
33
|
+
uniq_components =
|
34
|
+
components
|
35
|
+
.partition { |c| c.start_with?("@") }
|
36
|
+
.flat_map
|
37
|
+
.with_index do |group, idx|
|
38
|
+
group
|
39
|
+
.map { |comp| Starry.parse_item(idx.zero? ? comp[1..] : comp) }
|
40
|
+
.uniq { |comp| [comp.value, comp.parameters] }
|
41
|
+
end
|
42
|
+
|
43
|
+
raise Error.new msg if components.count != uniq_components.count
|
25
44
|
end
|
26
45
|
end
|
27
46
|
end
|
data/lib/linzer/message.rb
CHANGED
@@ -2,8 +2,10 @@
|
|
2
2
|
|
3
3
|
module Linzer
|
4
4
|
class Message
|
5
|
-
def initialize(operation)
|
5
|
+
def initialize(operation, attached_request: nil)
|
6
6
|
@operation = operation
|
7
|
+
validate
|
8
|
+
@attached_request = attached_request ? Message.new(attached_request) : nil
|
7
9
|
freeze
|
8
10
|
end
|
9
11
|
|
@@ -15,6 +17,10 @@ module Linzer
|
|
15
17
|
@operation.is_a?(Rack::Response) || @operation.respond_to?(:status)
|
16
18
|
end
|
17
19
|
|
20
|
+
def attached_request?
|
21
|
+
!!@attached_request
|
22
|
+
end
|
23
|
+
|
18
24
|
def headers
|
19
25
|
return @operation.headers if response? || @operation.respond_to?(:headers)
|
20
26
|
|
@@ -26,32 +32,25 @@ module Linzer
|
|
26
32
|
end
|
27
33
|
|
28
34
|
DERIVED_COMPONENT = {
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
"
|
34
|
-
|
35
|
-
"
|
36
|
-
|
35
|
+
method: :request_method,
|
36
|
+
authority: :authority,
|
37
|
+
path: :path_info,
|
38
|
+
status: :status,
|
39
|
+
"target-uri": :url,
|
40
|
+
scheme: :scheme,
|
41
|
+
"request-target": :fullpath,
|
42
|
+
query: :query_string
|
37
43
|
}.freeze
|
38
44
|
|
39
45
|
def [](field_name)
|
40
|
-
|
41
|
-
|
42
|
-
return @operation.headers[field_name] # if response?
|
43
|
-
end
|
44
|
-
|
45
|
-
method = DERIVED_COMPONENT[field_name]
|
46
|
+
name = parse_field_name(field_name)
|
47
|
+
return nil if name.nil?
|
46
48
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
return parse_query_param Regexp.last_match
|
49
|
+
if field_name.start_with?("@")
|
50
|
+
retrieve(name, :derived)
|
51
|
+
else
|
52
|
+
retrieve(name, :field)
|
52
53
|
end
|
53
|
-
|
54
|
-
method ? @operation.public_send(method) : nil
|
55
54
|
end
|
56
55
|
|
57
56
|
class << self
|
@@ -64,14 +63,146 @@ module Linzer
|
|
64
63
|
|
65
64
|
private
|
66
65
|
|
67
|
-
def
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
66
|
+
def validate
|
67
|
+
msg = "Message instance must be an HTTP request or response"
|
68
|
+
raise Error.new msg if response? == request?
|
69
|
+
end
|
70
|
+
|
71
|
+
def parse_field_name(field_name)
|
72
|
+
if field_name&.start_with?("@")
|
73
|
+
Starry.parse_item(field_name[1..])
|
74
|
+
else
|
75
|
+
Starry.parse_item(field_name)
|
76
|
+
end
|
73
77
|
rescue => _
|
74
78
|
nil
|
75
79
|
end
|
80
|
+
|
81
|
+
def validate_parameters(name, method)
|
82
|
+
has_unknown = name.parameters.any? { |p, _| !KNOWN_PARAMETERS.include?(p) }
|
83
|
+
return nil if has_unknown
|
84
|
+
|
85
|
+
has_name = name.parameters["name"]
|
86
|
+
has_req = name.parameters["req"]
|
87
|
+
has_sf = name.parameters["sf"] || name.parameters.key?("key")
|
88
|
+
has_bs = name.parameters["bs"]
|
89
|
+
value = name.value
|
90
|
+
|
91
|
+
# Section 2.2.8 of RFC 9421
|
92
|
+
return nil if has_name && value != :"query-param"
|
93
|
+
|
94
|
+
# No derived values come from trailers section
|
95
|
+
return nil if method == :derived && name.parameters["tr"]
|
96
|
+
|
97
|
+
# From: 2.1. HTTP Fields:
|
98
|
+
# The bs parameter, which requires the raw bytes of the field values
|
99
|
+
# from the message, is not compatible with the use of the sf or key
|
100
|
+
# parameters, which require the parsed data structures of the field
|
101
|
+
# values after combination
|
102
|
+
return nil if has_sf && has_bs
|
103
|
+
|
104
|
+
# req param only makes sense on responses with an associated request
|
105
|
+
return nil if has_req && (!response? || !attached_request?)
|
106
|
+
|
107
|
+
name
|
108
|
+
end
|
109
|
+
|
110
|
+
KNOWN_PARAMETERS = %w[sf key bs req tr name]
|
111
|
+
private_constant :KNOWN_PARAMETERS
|
112
|
+
|
113
|
+
def retrieve(name, method)
|
114
|
+
if !name.parameters.empty?
|
115
|
+
valid_params = validate_parameters(name, method)
|
116
|
+
return nil if !valid_params
|
117
|
+
end
|
118
|
+
|
119
|
+
has_req = name.parameters["req"]
|
120
|
+
has_sf = name.parameters["sf"] || name.parameters.key?("key")
|
121
|
+
has_bs = name.parameters["bs"]
|
122
|
+
|
123
|
+
if has_req
|
124
|
+
name.parameters.delete("req")
|
125
|
+
return req(name, method)
|
126
|
+
end
|
127
|
+
|
128
|
+
value = send(method, name)
|
129
|
+
|
130
|
+
case
|
131
|
+
when has_sf
|
132
|
+
key = name.parameters["key"]
|
133
|
+
sf(value, key)
|
134
|
+
when has_bs then bs(value)
|
135
|
+
else value
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def derived(name)
|
140
|
+
method = DERIVED_COMPONENT[name.value]
|
141
|
+
|
142
|
+
value = case name.value
|
143
|
+
when :query then derive(@operation, method)
|
144
|
+
when :"query-param" then query_param(name)
|
145
|
+
end
|
146
|
+
|
147
|
+
return nil if !method && !value
|
148
|
+
value || derive(@operation, method)
|
149
|
+
end
|
150
|
+
|
151
|
+
def field(name)
|
152
|
+
has_tr = name.parameters["tr"]
|
153
|
+
if has_tr
|
154
|
+
value = tr(name)
|
155
|
+
else
|
156
|
+
if request?
|
157
|
+
rack_header_name = Request.rack_header_name(name.value.to_s)
|
158
|
+
value = @operation.env[rack_header_name]
|
159
|
+
end
|
160
|
+
value = @operation.headers[name.value.to_s] if response?
|
161
|
+
end
|
162
|
+
value.dup&.strip
|
163
|
+
end
|
164
|
+
|
165
|
+
def derive(operation, method)
|
166
|
+
return nil unless operation.respond_to?(method)
|
167
|
+
value = operation.public_send(method)
|
168
|
+
return "?" + value if method == :query_string
|
169
|
+
return value.downcase if %i[authority scheme].include?(method)
|
170
|
+
value
|
171
|
+
end
|
172
|
+
|
173
|
+
def query_param(name)
|
174
|
+
param_name = name.parameters["name"]
|
175
|
+
return nil if !param_name
|
176
|
+
decoded_param_name = URI.decode_uri_component(param_name)
|
177
|
+
URI.encode_uri_component(@operation.params.fetch(decoded_param_name))
|
178
|
+
rescue => _
|
179
|
+
nil
|
180
|
+
end
|
181
|
+
|
182
|
+
def sf(value, key = nil)
|
183
|
+
dict = Starry.parse_dictionary(value)
|
184
|
+
|
185
|
+
if key
|
186
|
+
obj = dict[key]
|
187
|
+
Starry.serialize(obj.is_a?(Starry::InnerList) ? [obj] : obj)
|
188
|
+
else
|
189
|
+
Starry.serialize(dict)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def bs(value)
|
194
|
+
Starry.serialize(value.encode(Encoding::ASCII_8BIT))
|
195
|
+
end
|
196
|
+
|
197
|
+
def tr(trailer)
|
198
|
+
@operation.body.trailers[trailer.value.to_s]
|
199
|
+
end
|
200
|
+
|
201
|
+
def req(field, method)
|
202
|
+
case method
|
203
|
+
when :derived then @attached_request["@#{field}"]
|
204
|
+
when :field then @attached_request[field.to_s]
|
205
|
+
end
|
206
|
+
end
|
76
207
|
end
|
77
208
|
end
|
data/lib/linzer/request.rb
CHANGED
@@ -11,7 +11,8 @@ module Linzer
|
|
11
11
|
request_method = Rack.const_get(verb.upcase)
|
12
12
|
args = {
|
13
13
|
"REQUEST_METHOD" => request_method,
|
14
|
-
"PATH_INFO" => uri.to_str
|
14
|
+
"PATH_INFO" => uri.to_str,
|
15
|
+
"rack.input" => StringIO.new
|
15
16
|
}
|
16
17
|
|
17
18
|
Rack::Request.new(build_rack_env(headers).merge(args))
|
@@ -84,6 +85,7 @@ module Linzer
|
|
84
85
|
def build_rack_env(headers)
|
85
86
|
headers
|
86
87
|
.to_hash
|
88
|
+
.transform_values(&:to_s)
|
87
89
|
.transform_keys { |k| k.upcase.tr("-", "_") }
|
88
90
|
.transform_keys do |k|
|
89
91
|
%w[CONTENT_TYPE CONTENT_LENGTH].include?(k) ? k : "HTTP_#{k}"
|
data/lib/linzer/response.rb
CHANGED
data/lib/linzer/version.rb
CHANGED
metadata
CHANGED
@@ -1,19 +1,22 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: linzer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Miguel Landaeta
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: openssl
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
17
20
|
- - ">="
|
18
21
|
- !ruby/object:Gem::Version
|
19
22
|
version: 3.0.0
|
@@ -21,6 +24,9 @@ dependencies:
|
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.0'
|
24
30
|
- - ">="
|
25
31
|
- !ruby/object:Gem::Version
|
26
32
|
version: 3.0.0
|
@@ -50,42 +56,54 @@ dependencies:
|
|
50
56
|
requirements:
|
51
57
|
- - "~>"
|
52
58
|
- !ruby/object:Gem::Version
|
53
|
-
version: '0.
|
59
|
+
version: '0.2'
|
54
60
|
type: :runtime
|
55
61
|
prerelease: false
|
56
62
|
version_requirements: !ruby/object:Gem::Requirement
|
57
63
|
requirements:
|
58
64
|
- - "~>"
|
59
65
|
- !ruby/object:Gem::Version
|
60
|
-
version: '0.
|
66
|
+
version: '0.2'
|
61
67
|
- !ruby/object:Gem::Dependency
|
62
68
|
name: rack
|
63
69
|
requirement: !ruby/object:Gem::Requirement
|
64
70
|
requirements:
|
65
|
-
- - "
|
71
|
+
- - ">="
|
66
72
|
- !ruby/object:Gem::Version
|
67
|
-
version: '
|
73
|
+
version: '2.2'
|
74
|
+
- - "<"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '4.0'
|
68
77
|
type: :runtime
|
69
78
|
prerelease: false
|
70
79
|
version_requirements: !ruby/object:Gem::Requirement
|
71
80
|
requirements:
|
72
|
-
- - "
|
81
|
+
- - ">="
|
73
82
|
- !ruby/object:Gem::Version
|
74
|
-
version: '
|
83
|
+
version: '2.2'
|
84
|
+
- - "<"
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '4.0'
|
75
87
|
- !ruby/object:Gem::Dependency
|
76
88
|
name: uri
|
77
89
|
requirement: !ruby/object:Gem::Requirement
|
78
90
|
requirements:
|
91
|
+
- - "~>"
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '1.0'
|
79
94
|
- - ">="
|
80
95
|
- !ruby/object:Gem::Version
|
81
|
-
version: 0.
|
96
|
+
version: 1.0.2
|
82
97
|
type: :runtime
|
83
98
|
prerelease: false
|
84
99
|
version_requirements: !ruby/object:Gem::Requirement
|
85
100
|
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.0'
|
86
104
|
- - ">="
|
87
105
|
- !ruby/object:Gem::Version
|
88
|
-
version: 0.
|
106
|
+
version: 1.0.2
|
89
107
|
description:
|
90
108
|
email:
|
91
109
|
- miguel@miguel.cc
|
@@ -115,7 +133,6 @@ files:
|
|
115
133
|
- lib/linzer/signer.rb
|
116
134
|
- lib/linzer/verifier.rb
|
117
135
|
- lib/linzer/version.rb
|
118
|
-
- linzer.gemspec
|
119
136
|
homepage: https://github.com/nomadium/linzer
|
120
137
|
licenses:
|
121
138
|
- MIT
|
data/linzer.gemspec
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "lib/linzer/version"
|
4
|
-
|
5
|
-
Gem::Specification.new do |spec|
|
6
|
-
spec.name = "linzer"
|
7
|
-
spec.version = Linzer::VERSION
|
8
|
-
spec.authors = ["Miguel Landaeta"]
|
9
|
-
spec.email = %w[miguel@miguel.cc]
|
10
|
-
|
11
|
-
spec.summary = "An implementation of HTTP Messages Signatures (RFC9421)"
|
12
|
-
spec.homepage = "https://github.com/nomadium/linzer"
|
13
|
-
spec.license = "MIT"
|
14
|
-
spec.required_ruby_version = ">= 2.6.0"
|
15
|
-
|
16
|
-
spec.metadata["homepage_uri"] = spec.homepage
|
17
|
-
spec.metadata["source_code_uri"] = spec.homepage
|
18
|
-
spec.metadata["changelog_uri"] = spec.homepage + "/blob/master/CHANGELOG.md"
|
19
|
-
|
20
|
-
# Specify which files should be added to the gem when it is released.
|
21
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
-
spec.files = Dir.chdir(__dir__) do
|
23
|
-
`git ls-files -z`.split("\x0").reject do |f|
|
24
|
-
(File.expand_path(f) == __FILE__) ||
|
25
|
-
f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
|
26
|
-
end
|
27
|
-
end
|
28
|
-
spec.bindir = "exe"
|
29
|
-
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
30
|
-
spec.require_paths = ["lib"]
|
31
|
-
|
32
|
-
spec.add_runtime_dependency "openssl", ">= 3.0.0"
|
33
|
-
spec.add_runtime_dependency "ed25519", "~> 1.3", ">= 1.3.0"
|
34
|
-
spec.add_runtime_dependency "starry", "~> 0.1"
|
35
|
-
spec.add_runtime_dependency "rack", "~> 3.0"
|
36
|
-
spec.add_runtime_dependency "uri", ">= 0.12.0"
|
37
|
-
end
|