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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0befd6a2ae7ba008f6b2a343a24845be1941b6645615d8a029e698c083cd4481
4
- data.tar.gz: ebd92e2c6b0991067753079c8b1bac87a6dfe644caa6380dedc63f88e74877e4
3
+ metadata.gz: d2ec6a2a846f2bb066bdd658c2e50a869ceff52f65f3e5f8670cc5509af6069c
4
+ data.tar.gz: 5401df5ea61c39290b61a21cf19c4eaddc537abe908067a47688c1f209c982e3
5
5
  SHA512:
6
- metadata.gz: c7c5b9d9568402b2b4894ef8180c321281b420a156e7386abf12f069780a6ba7e2bc79d58bd0b7c97372058f974a25a1a138e9306434c28025e39f3267562b81
7
- data.tar.gz: 7b997d06b2764171d5375baf1b889f4d98231961b86f89bea321155766932ffcd6913462df080f7edf09731d6d78332b474cda381ea4d0d8bdafba3dee76e6b3
6
+ metadata.gz: 53c504c5c0f886d3e73332d7d8bf7072137519a218ca6a005824f8ad073fb589dd91f2e7980aa02b232c17dd80a87923141e57b094087fb4f412dddbf5588a9f
7
+ data.tar.gz: 0a99fc9611e6b26ae820fecfe09e10a6c610be20bdf8f9c3fd7c6fab031f82f6f7fa109359470a2a65b7d7cd9817c21259e59fe6f99e50a1c6326d965b0df944
data/.standard.yml CHANGED
@@ -3,9 +3,8 @@
3
3
  ruby_version: 2.6
4
4
 
5
5
  ignore:
6
- - 'lib/**/*':
7
- - Layout/ExtraSpacing
8
- - Layout/HashAlignment
9
- - 'spec/**/*':
6
+ - '**/*':
10
7
  - Layout/ExtraSpacing
11
8
  - Layout/HashAlignment
9
+ - Layout/ArgumentAlignment
10
+ - Style/EmptyCaseCondition
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 { |c| raise Error.new msg % "\"#{c}\"" unless message.field?(c) }
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
- raise Error.new msg if components.size != components.uniq.size
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
@@ -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
- "@method" => :request_method,
30
- "@authority" => :authority,
31
- "@path" => :path_info,
32
- "@status" => :status,
33
- "@target-uri" => :url,
34
- "@scheme" => :scheme,
35
- "@request-target" => :fullpath,
36
- "@query" => :query_string
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
- if !field_name.start_with?("@")
41
- return @operation.env[Request.rack_header_name(field_name)] if request?
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
- case field_name
48
- when "@query"
49
- return "?#{@operation.public_send(method)}"
50
- when /\A(?<field>(?<prefix>@query-param)(?<rest>;name=.+)\Z)/
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 parse_query_param(match_data)
68
- raw_item = '"%s"%s' % [match_data[:prefix], match_data[:rest]]
69
- parsed_item = Starry.parse_item(raw_item)
70
- fail unless parsed_item.value == "@query-param"
71
- param_name = URI.decode_uri_component(parsed_item.parameters["name"])
72
- URI.encode_uri_component(@operation.params.fetch(param_name))
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
@@ -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}"
@@ -3,7 +3,7 @@
3
3
  module Linzer
4
4
  module Response
5
5
  def new_response(body = nil, status = 200, headers = {})
6
- Rack::Response.new(body, status, headers)
6
+ Rack::Response.new(body, status, headers.transform_values(&:to_s))
7
7
  end
8
8
  end
9
9
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Linzer
4
- VERSION = "0.5.2"
4
+ VERSION = "0.6.1"
5
5
  end
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.5.2
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-04-01 00:00:00.000000000 Z
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.1'
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.1'
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: '3.0'
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: '3.0'
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.12.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.12.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