jwt 2.3.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +8 -0
  3. data/.github/workflows/coverage.yml +27 -0
  4. data/.github/workflows/test.yml +15 -22
  5. data/.gitignore +2 -0
  6. data/.reek.yml +22 -0
  7. data/.rubocop.yml +17 -47
  8. data/.sourcelevel.yml +3 -4
  9. data/AUTHORS +60 -53
  10. data/Appraisals +3 -0
  11. data/CHANGELOG.md +47 -0
  12. data/CODE_OF_CONDUCT.md +84 -0
  13. data/CONTRIBUTING.md +99 -0
  14. data/Gemfile +3 -1
  15. data/README.md +114 -34
  16. data/Rakefile +2 -0
  17. data/lib/jwt/algos/ecdsa.rb +37 -8
  18. data/lib/jwt/algos/eddsa.rb +5 -0
  19. data/lib/jwt/algos/hmac.rb +2 -0
  20. data/lib/jwt/algos/none.rb +2 -0
  21. data/lib/jwt/algos/ps.rb +3 -3
  22. data/lib/jwt/algos/rsa.rb +4 -1
  23. data/lib/jwt/algos/unsupported.rb +2 -0
  24. data/lib/jwt/claims_validator.rb +3 -1
  25. data/lib/jwt/configuration/container.rb +21 -0
  26. data/lib/jwt/configuration/decode_configuration.rb +46 -0
  27. data/lib/jwt/configuration/jwk_configuration.rb +27 -0
  28. data/lib/jwt/configuration.rb +15 -0
  29. data/lib/jwt/decode.rb +42 -8
  30. data/lib/jwt/encode.rb +6 -6
  31. data/lib/jwt/error.rb +1 -0
  32. data/lib/jwt/jwk/ec.rb +92 -43
  33. data/lib/jwt/jwk/hmac.rb +19 -10
  34. data/lib/jwt/jwk/key_base.rb +23 -6
  35. data/lib/jwt/jwk/key_finder.rb +1 -1
  36. data/lib/jwt/jwk/kid_as_key_digest.rb +15 -0
  37. data/lib/jwt/jwk/rsa.rb +54 -31
  38. data/lib/jwt/jwk/thumbprint.rb +26 -0
  39. data/lib/jwt/jwk.rb +1 -0
  40. data/lib/jwt/security_utils.rb +2 -0
  41. data/lib/jwt/signature.rb +3 -7
  42. data/lib/jwt/verify.rb +10 -2
  43. data/lib/jwt/version.rb +6 -2
  44. data/lib/jwt/x5c_key_finder.rb +55 -0
  45. data/lib/jwt.rb +5 -4
  46. data/ruby-jwt.gemspec +6 -3
  47. metadata +31 -7
  48. data/.rubocop_todo.yml +0 -185
  49. data/lib/jwt/default_options.rb +0 -16
data/lib/jwt/signature.rb CHANGED
@@ -13,7 +13,8 @@ end
13
13
  module JWT
14
14
  # Signature logic for JWT
15
15
  module Signature
16
- extend self
16
+ module_function
17
+
17
18
  ToSign = Struct.new(:algorithm, :msg, :key)
18
19
  ToVerify = Struct.new(:algorithm, :public_key, :signing_input, :signature)
19
20
 
@@ -23,13 +24,8 @@ module JWT
23
24
  end
24
25
 
25
26
  def verify(algorithm, key, signing_input, signature)
26
- return true if algorithm.casecmp('none').zero?
27
-
28
- raise JWT::DecodeError, 'No verification key available' unless key
29
-
30
27
  algo, code = Algos.find(algorithm)
31
- verified = algo.verify(ToVerify.new(code, key, signing_input, signature))
32
- raise(JWT::VerificationError, 'Signature verification raised') unless verified
28
+ algo.verify(ToVerify.new(code, key, signing_input, signature))
33
29
  rescue OpenSSL::PKey::PKeyError
34
30
  raise JWT::VerificationError, 'Signature verification raised'
35
31
  ensure
data/lib/jwt/verify.rb CHANGED
@@ -19,6 +19,7 @@ module JWT
19
19
  def verify_claims(payload, options)
20
20
  options.each do |key, val|
21
21
  next unless key.to_s =~ /verify/
22
+
22
23
  Verify.send(key, payload, options) if val
23
24
  end
24
25
  end
@@ -53,9 +54,14 @@ module JWT
53
54
 
54
55
  iss = @payload['iss']
55
56
 
56
- return if Array(options_iss).map(&:to_s).include?(iss.to_s)
57
+ options_iss = Array(options_iss).map { |item| item.is_a?(Symbol) ? item.to_s : item }
57
58
 
58
- raise(JWT::InvalidIssuerError, "Invalid issuer. Expected #{options_iss}, received #{iss || '<none>'}")
59
+ case iss
60
+ when *options_iss
61
+ nil
62
+ else
63
+ raise(JWT::InvalidIssuerError, "Invalid issuer. Expected #{options_iss}, received #{iss || '<none>'}")
64
+ end
59
65
  end
60
66
 
61
67
  def verify_jti
@@ -77,12 +83,14 @@ module JWT
77
83
 
78
84
  def verify_sub
79
85
  return unless (options_sub = @options[:sub])
86
+
80
87
  sub = @payload['sub']
81
88
  raise(JWT::InvalidSubError, "Invalid subject. Expected #{options_sub}, received #{sub || '<none>'}") unless sub.to_s == options_sub.to_s
82
89
  end
83
90
 
84
91
  def verify_required_claims
85
92
  return unless (options_required_claims = @options[:required_claims])
93
+
86
94
  options_required_claims.each do |required_claim|
87
95
  raise(JWT::MissingRequiredClaim, "Missing required claim #{required_claim}") unless @payload.include?(required_claim)
88
96
  end
data/lib/jwt/version.rb CHANGED
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
3
  # Moments version builder module
@@ -12,7 +11,7 @@ module JWT
12
11
  # major version
13
12
  MAJOR = 2
14
13
  # minor version
15
- MINOR = 3
14
+ MINOR = 5
16
15
  # tiny version
17
16
  TINY = 0
18
17
  # alpha, beta, etc. tag
@@ -21,4 +20,9 @@ module JWT
21
20
  # Build version string
22
21
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
23
22
  end
23
+
24
+ def self.openssl_3?
25
+ return false if OpenSSL::OPENSSL_VERSION.include?('LibreSSL')
26
+ return true if OpenSSL::OPENSSL_VERSION_NUMBER >= 3 * 0x10000000
27
+ end
24
28
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+ require 'jwt/error'
5
+
6
+ module JWT
7
+ # If the x5c header certificate chain can be validated by trusted root
8
+ # certificates, and none of the certificates are revoked, returns the public
9
+ # key from the first certificate.
10
+ # See https://tools.ietf.org/html/rfc7515#section-4.1.6
11
+ class X5cKeyFinder
12
+ def initialize(root_certificates, crls = nil)
13
+ raise(ArgumentError, 'Root certificates must be specified') unless root_certificates
14
+
15
+ @store = build_store(root_certificates, crls)
16
+ end
17
+
18
+ def from(x5c_header_or_certificates)
19
+ signing_certificate, *certificate_chain = parse_certificates(x5c_header_or_certificates)
20
+ store_context = OpenSSL::X509::StoreContext.new(@store, signing_certificate, certificate_chain)
21
+
22
+ if store_context.verify
23
+ signing_certificate.public_key
24
+ else
25
+ error = "Certificate verification failed: #{store_context.error_string}."
26
+ if (current_cert = store_context.current_cert)
27
+ error = "#{error} Certificate subject: #{current_cert.subject}."
28
+ end
29
+
30
+ raise(JWT::VerificationError, error)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def build_store(root_certificates, crls)
37
+ store = OpenSSL::X509::Store.new
38
+ store.purpose = OpenSSL::X509::PURPOSE_ANY
39
+ store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK | OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
40
+ root_certificates.each { |certificate| store.add_cert(certificate) }
41
+ crls&.each { |crl| store.add_crl(crl) }
42
+ store
43
+ end
44
+
45
+ def parse_certificates(x5c_header_or_certificates)
46
+ if x5c_header_or_certificates.all? { |obj| obj.is_a?(OpenSSL::X509::Certificate) }
47
+ x5c_header_or_certificates
48
+ else
49
+ x5c_header_or_certificates.map do |encoded|
50
+ OpenSSL::X509::Certificate.new(::JWT::Base64.url_decode(encoded))
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
data/lib/jwt.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'jwt/version'
3
4
  require 'jwt/base64'
4
5
  require 'jwt/json'
5
6
  require 'jwt/decode'
6
- require 'jwt/default_options'
7
+ require 'jwt/configuration'
7
8
  require 'jwt/encode'
8
9
  require 'jwt/error'
9
10
  require 'jwt/jwk'
@@ -13,7 +14,7 @@ require 'jwt/jwk'
13
14
  # Should be up to date with the latest spec:
14
15
  # https://tools.ietf.org/html/rfc7519
15
16
  module JWT
16
- include JWT::DefaultOptions
17
+ extend ::JWT::Configuration
17
18
 
18
19
  module_function
19
20
 
@@ -24,7 +25,7 @@ module JWT
24
25
  headers: header_fields).segments
25
26
  end
26
27
 
27
- def decode(jwt, key = nil, verify = true, options = {}, &keyfinder)
28
- Decode.new(jwt, key, verify, DEFAULT_OPTIONS.merge(options), &keyfinder).decode_segments
28
+ def decode(jwt, key = nil, verify = true, options = {}, &keyfinder) # rubocop:disable Style/OptionalBooleanParameter
29
+ Decode.new(jwt, key, verify, configuration.decode.to_h.merge(options), &keyfinder).decode_segments
29
30
  end
30
31
  end
data/ruby-jwt.gemspec CHANGED
@@ -1,4 +1,6 @@
1
- lib = File.expand_path('../lib/', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
2
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
5
  require 'jwt/version'
4
6
 
@@ -13,10 +15,10 @@ Gem::Specification.new do |spec|
13
15
  spec.description = 'A pure ruby implementation of the RFC 7519 OAuth JSON Web Token (JWT) standard.'
14
16
  spec.homepage = 'https://github.com/jwt/ruby-jwt'
15
17
  spec.license = 'MIT'
16
- spec.required_ruby_version = '>= 2.1'
18
+ spec.required_ruby_version = '>= 2.5'
17
19
  spec.metadata = {
18
20
  'bug_tracker_uri' => 'https://github.com/jwt/ruby-jwt/issues',
19
- 'changelog_uri' => "https://github.com/jwt/ruby-jwt/blob/v#{JWT.gem_version}/CHANGELOG.md"
21
+ 'changelog_uri' => "https://github.com/jwt/ruby-jwt/blob/v#{JWT.gem_version}/CHANGELOG.md"
20
22
  }
21
23
 
22
24
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec|gemfiles|coverage|bin)/}) }
@@ -27,6 +29,7 @@ Gem::Specification.new do |spec|
27
29
  spec.add_development_dependency 'appraisal'
28
30
  spec.add_development_dependency 'bundler'
29
31
  spec.add_development_dependency 'rake'
32
+ spec.add_development_dependency 'reek'
30
33
  spec.add_development_dependency 'rspec'
31
34
  spec.add_development_dependency 'simplecov'
32
35
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jwt
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Rudat
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-03 00:00:00.000000000 Z
11
+ date: 2022-08-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: appraisal
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: reek
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rspec
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -87,15 +101,19 @@ executables: []
87
101
  extensions: []
88
102
  extra_rdoc_files: []
89
103
  files:
104
+ - ".codeclimate.yml"
105
+ - ".github/workflows/coverage.yml"
90
106
  - ".github/workflows/test.yml"
91
107
  - ".gitignore"
108
+ - ".reek.yml"
92
109
  - ".rspec"
93
110
  - ".rubocop.yml"
94
- - ".rubocop_todo.yml"
95
111
  - ".sourcelevel.yml"
96
112
  - AUTHORS
97
113
  - Appraisals
98
114
  - CHANGELOG.md
115
+ - CODE_OF_CONDUCT.md
116
+ - CONTRIBUTING.md
99
117
  - Gemfile
100
118
  - LICENSE
101
119
  - README.md
@@ -111,8 +129,11 @@ files:
111
129
  - lib/jwt/algos/unsupported.rb
112
130
  - lib/jwt/base64.rb
113
131
  - lib/jwt/claims_validator.rb
132
+ - lib/jwt/configuration.rb
133
+ - lib/jwt/configuration/container.rb
134
+ - lib/jwt/configuration/decode_configuration.rb
135
+ - lib/jwt/configuration/jwk_configuration.rb
114
136
  - lib/jwt/decode.rb
115
- - lib/jwt/default_options.rb
116
137
  - lib/jwt/encode.rb
117
138
  - lib/jwt/error.rb
118
139
  - lib/jwt/json.rb
@@ -121,18 +142,21 @@ files:
121
142
  - lib/jwt/jwk/hmac.rb
122
143
  - lib/jwt/jwk/key_base.rb
123
144
  - lib/jwt/jwk/key_finder.rb
145
+ - lib/jwt/jwk/kid_as_key_digest.rb
124
146
  - lib/jwt/jwk/rsa.rb
147
+ - lib/jwt/jwk/thumbprint.rb
125
148
  - lib/jwt/security_utils.rb
126
149
  - lib/jwt/signature.rb
127
150
  - lib/jwt/verify.rb
128
151
  - lib/jwt/version.rb
152
+ - lib/jwt/x5c_key_finder.rb
129
153
  - ruby-jwt.gemspec
130
154
  homepage: https://github.com/jwt/ruby-jwt
131
155
  licenses:
132
156
  - MIT
133
157
  metadata:
134
158
  bug_tracker_uri: https://github.com/jwt/ruby-jwt/issues
135
- changelog_uri: https://github.com/jwt/ruby-jwt/blob/v2.3.0/CHANGELOG.md
159
+ changelog_uri: https://github.com/jwt/ruby-jwt/blob/v2.5.0/CHANGELOG.md
136
160
  post_install_message:
137
161
  rdoc_options: []
138
162
  require_paths:
@@ -141,14 +165,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
141
165
  requirements:
142
166
  - - ">="
143
167
  - !ruby/object:Gem::Version
144
- version: '2.1'
168
+ version: '2.5'
145
169
  required_rubygems_version: !ruby/object:Gem::Requirement
146
170
  requirements:
147
171
  - - ">="
148
172
  - !ruby/object:Gem::Version
149
173
  version: '0'
150
174
  requirements: []
151
- rubygems_version: 3.2.19
175
+ rubygems_version: 3.3.21
152
176
  signing_key:
153
177
  specification_version: 4
154
178
  summary: JSON Web Token implementation in Ruby
data/.rubocop_todo.yml DELETED
@@ -1,185 +0,0 @@
1
- # This configuration was generated by
2
- # `rubocop --auto-gen-config`
3
- # on 2020-12-21 23:11:43 +0200 using RuboCop version 0.52.1.
4
- # The point is for the user to remove these configuration records
5
- # one by one as the offenses are removed from the code base.
6
- # Note that changes in the inspected code, or installation of new
7
- # versions of RuboCop, may require this file to be generated again.
8
-
9
- # Offense count: 2
10
- # Cop supports --auto-correct.
11
- # Configuration parameters: Include, TreatCommentsAsGroupSeparators.
12
- # Include: **/*.gemspec
13
- Gemspec/OrderedDependencies:
14
- Exclude:
15
- - 'ruby-jwt.gemspec'
16
-
17
- # Offense count: 1
18
- # Cop supports --auto-correct.
19
- Layout/EmptyLines:
20
- Exclude:
21
- - 'spec/integration/readme_examples_spec.rb'
22
-
23
- # Offense count: 1
24
- # Cop supports --auto-correct.
25
- # Configuration parameters: EnforcedStyle.
26
- # SupportedStyles: empty_lines, no_empty_lines
27
- Layout/EmptyLinesAroundBlockBody:
28
- Exclude:
29
- - 'spec/jwt_spec.rb'
30
-
31
- # Offense count: 1
32
- # Cop supports --auto-correct.
33
- # Configuration parameters: AllowForAlignment, ForceEqualSignAlignment.
34
- Layout/ExtraSpacing:
35
- Exclude:
36
- - 'spec/jwk_spec.rb'
37
-
38
- # Offense count: 2
39
- # Cop supports --auto-correct.
40
- # Configuration parameters: EnforcedStyle.
41
- # SupportedStyles: normal, rails
42
- Layout/IndentationConsistency:
43
- Exclude:
44
- - 'spec/jwt_spec.rb'
45
-
46
- # Offense count: 1
47
- # Cop supports --auto-correct.
48
- # Configuration parameters: Width, IgnoredPatterns.
49
- Layout/IndentationWidth:
50
- Exclude:
51
- - 'spec/jwt_spec.rb'
52
-
53
- # Offense count: 3
54
- # Cop supports --auto-correct.
55
- Layout/SpaceAfterComma:
56
- Exclude:
57
- - 'spec/jwt_spec.rb'
58
-
59
- # Offense count: 2
60
- # Cop supports --auto-correct.
61
- # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces.
62
- # SupportedStyles: space, no_space
63
- # SupportedStylesForEmptyBraces: space, no_space
64
- Layout/SpaceBeforeBlockBraces:
65
- Exclude:
66
- - 'spec/jwk/ec_spec.rb'
67
- - 'spec/jwt/verify_spec.rb'
68
-
69
- # Offense count: 1
70
- # Cop supports --auto-correct.
71
- # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters.
72
- # SupportedStyles: space, no_space
73
- # SupportedStylesForEmptyBraces: space, no_space
74
- Layout/SpaceInsideBlockBraces:
75
- Exclude:
76
- - 'spec/jwt/verify_spec.rb'
77
-
78
- # Offense count: 1
79
- # Cop supports --auto-correct.
80
- # Configuration parameters: EnforcedStyle.
81
- # SupportedStyles: final_newline, final_blank_line
82
- Layout/TrailingBlankLines:
83
- Exclude:
84
- - 'bin/console.rb'
85
-
86
- # Offense count: 3
87
- # Cop supports --auto-correct.
88
- # Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
89
- Lint/UnusedBlockArgument:
90
- Exclude:
91
- - 'spec/jwk/decode_with_jwk_spec.rb'
92
- - 'spec/jwk/ec_spec.rb'
93
- - 'spec/jwt/verify_spec.rb'
94
-
95
- # Offense count: 2
96
- Metrics/CyclomaticComplexity:
97
- Max: 7
98
-
99
- # Offense count: 1
100
- Metrics/PerceivedComplexity:
101
- Max: 8
102
-
103
- # Offense count: 1
104
- # Cop supports --auto-correct.
105
- # Configuration parameters: MaxKeyValuePairs.
106
- Performance/RedundantMerge:
107
- Exclude:
108
- - 'spec/jwt_spec.rb'
109
-
110
- # Offense count: 1
111
- # Cop supports --auto-correct.
112
- Style/Encoding:
113
- Exclude:
114
- - 'lib/jwt/version.rb'
115
-
116
- # Offense count: 1
117
- # Cop supports --auto-correct.
118
- # Configuration parameters: InverseMethods, InverseBlocks.
119
- Style/InverseMethods:
120
- Exclude:
121
- - 'spec/jwk/ec_spec.rb'
122
-
123
- # Offense count: 2
124
- # Cop supports --auto-correct.
125
- Style/MethodCallWithoutArgsParentheses:
126
- Exclude:
127
- - 'spec/jwt_spec.rb'
128
-
129
- # Offense count: 2
130
- # Configuration parameters: EnforcedStyle.
131
- # SupportedStyles: module_function, extend_self
132
- Style/ModuleFunction:
133
- Exclude:
134
- - 'lib/jwt/algos.rb'
135
- - 'lib/jwt/signature.rb'
136
-
137
- # Offense count: 1
138
- # Cop supports --auto-correct.
139
- Style/MutableConstant:
140
- Exclude:
141
- - 'lib/jwt/version.rb'
142
-
143
- # Offense count: 1
144
- # Cop supports --auto-correct.
145
- # Configuration parameters: Strict.
146
- Style/NumericLiterals:
147
- MinDigits: 6
148
-
149
- # Offense count: 1
150
- # Cop supports --auto-correct.
151
- Style/ParallelAssignment:
152
- Exclude:
153
- - 'spec/integration/readme_examples_spec.rb'
154
-
155
- # Offense count: 11
156
- # Cop supports --auto-correct.
157
- # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
158
- # SupportedStyles: single_quotes, double_quotes
159
- Style/StringLiterals:
160
- Exclude:
161
- - 'bin/console.rb'
162
- - 'spec/jwk/ec_spec.rb'
163
- - 'spec/jwk/rsa_spec.rb'
164
- - 'spec/jwk_spec.rb'
165
- - 'spec/jwt_spec.rb'
166
-
167
- # Offense count: 1
168
- # Cop supports --auto-correct.
169
- # Configuration parameters: EnforcedStyleForMultiline.
170
- # SupportedStylesForMultiline: comma, consistent_comma, no_comma
171
- Style/TrailingCommaInArguments:
172
- Exclude:
173
- - 'spec/jwt_spec.rb'
174
-
175
- # Offense count: 1
176
- # Cop supports --auto-correct.
177
- Style/UnlessElse:
178
- Exclude:
179
- - 'spec/jwt_spec.rb'
180
-
181
- # Offense count: 162
182
- # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
183
- # URISchemes: http, https
184
- Metrics/LineLength:
185
- Max: 420
@@ -1,16 +0,0 @@
1
- module JWT
2
- module DefaultOptions
3
- DEFAULT_OPTIONS = {
4
- verify_expiration: true,
5
- verify_not_before: true,
6
- verify_iss: false,
7
- verify_iat: false,
8
- verify_jti: false,
9
- verify_aud: false,
10
- verify_sub: false,
11
- leeway: 0,
12
- algorithms: ['HS256'],
13
- required_claims: []
14
- }.freeze
15
- end
16
- end