Class: Mpp::Challenge
- Inherits:
-
Data
- Object
- Data
- Mpp::Challenge
- Defined in:
- lib/mpp/challenge.rb
Instance Attribute Summary collapse
-
#description ⇒ Object
readonly
Returns the value of attribute description.
-
#digest ⇒ Object
readonly
Returns the value of attribute digest.
-
#expires ⇒ Object
readonly
Returns the value of attribute expires.
-
#id ⇒ Object
readonly
Returns the value of attribute id.
-
#intent ⇒ Object
readonly
Returns the value of attribute intent.
-
#method ⇒ Object
readonly
Returns the value of attribute method.
-
#opaque ⇒ Object
readonly
Returns the value of attribute opaque.
-
#realm ⇒ Object
readonly
Returns the value of attribute realm.
-
#request ⇒ Object
readonly
Returns the value of attribute request.
-
#request_b64 ⇒ Object
readonly
Returns the value of attribute request_b64.
Class Method Summary collapse
-
.create(secret_key:, realm:, method:, intent:, request:, expires: nil, digest: nil, description: nil, meta: nil) ⇒ Object
Create a Challenge with an HMAC-bound ID.
- .each_auth_scheme_index(header, offset = 0) ⇒ Object
-
.from_www_authenticate(header) ⇒ Object
Parse a Challenge from a WWW-Authenticate header value.
-
.from_www_authenticate_list(header) ⇒ Object
Parse every Payment challenge from a merged WWW-Authenticate header.
- .next_auth_scheme_index(header, offset) ⇒ Object
- .payment_scheme_indices(header) ⇒ Object
- .scheme_boundary?(header, index) ⇒ Boolean
-
.www_authenticate_chunks(header) ⇒ Object
Split a merged WWW-Authenticate header value into the individual
Payment ...challenge chunks it contains, handling RFC 9110 §11.6.1 comma-separated authentication schemes.
Instance Method Summary collapse
-
#initialize(id:, method:, intent:, request:, realm: "", request_b64: "", digest: nil, expires: nil, description: nil, opaque: nil) ⇒ Challenge
constructor
A new instance of Challenge.
-
#to_echo ⇒ Object
Create a ChallengeEcho for use in credentials.
-
#to_www_authenticate(realm) ⇒ Object
Serialize to a WWW-Authenticate header value.
-
#verify(secret_key, realm) ⇒ Object
Verify the challenge ID matches the expected HMAC.
Constructor Details
#initialize(id:, method:, intent:, request:, realm: "", request_b64: "", digest: nil, expires: nil, description: nil, opaque: nil) ⇒ Challenge
Returns a new instance of Challenge.
21 22 23 24 |
# File 'lib/mpp/challenge.rb', line 21 def initialize(id:, method:, intent:, request:, realm: "", request_b64: "", digest: nil, expires: nil, description: nil, opaque: nil) super end |
Instance Attribute Details
#description ⇒ Object (readonly)
Returns the value of attribute description
9 10 11 |
# File 'lib/mpp/challenge.rb', line 9 def description @description end |
#digest ⇒ Object (readonly)
Returns the value of attribute digest
9 10 11 |
# File 'lib/mpp/challenge.rb', line 9 def digest @digest end |
#expires ⇒ Object (readonly)
Returns the value of attribute expires
9 10 11 |
# File 'lib/mpp/challenge.rb', line 9 def expires @expires end |
#id ⇒ Object (readonly)
Returns the value of attribute id
9 10 11 |
# File 'lib/mpp/challenge.rb', line 9 def id @id end |
#intent ⇒ Object (readonly)
Returns the value of attribute intent
9 10 11 |
# File 'lib/mpp/challenge.rb', line 9 def intent @intent end |
#method ⇒ Object (readonly)
Returns the value of attribute method
9 10 11 |
# File 'lib/mpp/challenge.rb', line 9 def method @method end |
#opaque ⇒ Object (readonly)
Returns the value of attribute opaque
9 10 11 |
# File 'lib/mpp/challenge.rb', line 9 def opaque @opaque end |
#realm ⇒ Object (readonly)
Returns the value of attribute realm
9 10 11 |
# File 'lib/mpp/challenge.rb', line 9 def realm @realm end |
#request ⇒ Object (readonly)
Returns the value of attribute request
9 10 11 |
# File 'lib/mpp/challenge.rb', line 9 def request @request end |
#request_b64 ⇒ Object (readonly)
Returns the value of attribute request_b64
9 10 11 |
# File 'lib/mpp/challenge.rb', line 9 def request_b64 @request_b64 end |
Class Method Details
.create(secret_key:, realm:, method:, intent:, request:, expires: nil, digest: nil, description: nil, meta: nil) ⇒ Object
Create a Challenge with an HMAC-bound ID.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/mpp/challenge.rb', line 27 def self.create(secret_key:, realm:, method:, intent:, request:, expires: nil, digest: nil, description: nil, meta: nil) challenge_id = Mpp.generate_challenge_id( secret_key: secret_key, realm: realm, method: method, intent: intent, request: request, expires: expires, digest: digest, opaque: ) request_json = Mpp::Json.compact_encode(request) request_b64 = Mpp.b64url_encode(request_json) new( id: challenge_id, method: method, intent: intent, request: request, realm: realm, request_b64: request_b64, digest: digest, expires: expires, description: description, opaque: ) end |
.each_auth_scheme_index(header, offset = 0) ⇒ Object
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/mpp/challenge.rb', line 99 def self.each_auth_scheme_index(header, offset = 0) in_quote = T.let(false, T::Boolean) escaped = T.let(false, T::Boolean) i = offset while i < header.length char = T.must(header[i]) if in_quote if escaped escaped = false elsif char == "\\" escaped = true elsif char == "\"" in_quote = false end i += 1 next end if char == "\"" in_quote = true i += 1 next end if scheme_boundary?(header, i) match = T.must(header[i..]).match(/\A([A-Za-z][A-Za-z0-9._~+\/-]*)\s+/) # An auth-param permits OWS around "=" (key\s*=\s*value), so a token # followed by whitespace then "=" is a parameter, not a new scheme. if match && header[i + T.must(match[0]).length] != "=" yield i, T.must(match[1]) i += T.must(match[0]).length next end end i += 1 end end |
.from_www_authenticate(header) ⇒ Object
Parse a Challenge from a WWW-Authenticate header value.
57 58 59 |
# File 'lib/mpp/challenge.rb', line 57 def self.from_www_authenticate(header) Mpp::Parsing.parse_www_authenticate(header) end |
.from_www_authenticate_list(header) ⇒ Object
Parse every Payment challenge from a merged WWW-Authenticate header.
Raises Mpp::ParseError on the first malformed chunk; callers that need to
tolerate a malformed challenge among valid ones should iterate
www_authenticate_chunks and parse each chunk independently instead.
80 81 82 |
# File 'lib/mpp/challenge.rb', line 80 def self.from_www_authenticate_list(header) www_authenticate_chunks(header).map { |chunk| from_www_authenticate(chunk) } end |
.next_auth_scheme_index(header, offset) ⇒ Object
92 93 94 95 96 97 |
# File 'lib/mpp/challenge.rb', line 92 def self.next_auth_scheme_index(header, offset) each_auth_scheme_index(header, offset) do |index, _scheme| return index end nil end |
.payment_scheme_indices(header) ⇒ Object
84 85 86 87 88 89 90 |
# File 'lib/mpp/challenge.rb', line 84 def self.payment_scheme_indices(header) indices = [] each_auth_scheme_index(header) do |index, scheme| indices << index if scheme.casecmp("Payment").zero? end indices end |
.scheme_boundary?(header, index) ⇒ Boolean
140 141 142 143 144 145 146 147 148 |
# File 'lib/mpp/challenge.rb', line 140 def self.scheme_boundary?(header, index) previous = T.must(header[0...index]).rstrip # Start of the header, including the case where only optional whitespace # (RFC 9110 OWS) precedes the first scheme; otherwise a scheme is a # boundary only immediately after a comma separating two schemes. return true if previous.empty? previous.end_with?(",") end |
.www_authenticate_chunks(header) ⇒ Object
Split a merged WWW-Authenticate header value into the individual
Payment ... challenge chunks it contains, handling RFC 9110 §11.6.1
comma-separated authentication schemes. Returns the raw chunk strings so
callers can parse each challenge independently; a malformed chunk then
does not prevent the remaining chunks from being considered.
66 67 68 69 70 71 72 73 74 |
# File 'lib/mpp/challenge.rb', line 66 def self.www_authenticate_chunks(header) payment_scheme_indices(header).map do |start_idx| # End each chunk at the next scheme boundary of any kind, so an # interleaved non-Payment scheme (e.g. "Payment ..., Bearer ..., # Payment ...") is not folded into the preceding Payment challenge. end_idx = next_auth_scheme_index(header, start_idx + "Payment".length) || header.length T.must(header[start_idx...end_idx]).sub(/,\s*$/, "") end end |
Instance Method Details
#to_echo ⇒ Object
Create a ChallengeEcho for use in credentials.
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/mpp/challenge.rb', line 171 def to_echo opaque_b64 = nil if opaque opaque_json = Mpp::Json.compact_encode(opaque) opaque_b64 = Mpp.b64url_encode(opaque_json) end ChallengeEcho.new( id: id, realm: realm, method: method, intent: intent, request: request_b64, expires: expires, digest: digest, opaque: opaque_b64 ) end |
#to_www_authenticate(realm) ⇒ Object
Serialize to a WWW-Authenticate header value.
151 152 153 |
# File 'lib/mpp/challenge.rb', line 151 def to_www_authenticate(realm) Mpp::Parsing.format_www_authenticate(self, realm) end |
#verify(secret_key, realm) ⇒ Object
Verify the challenge ID matches the expected HMAC.
156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/mpp/challenge.rb', line 156 def verify(secret_key, realm) expected_id = Mpp.generate_challenge_id( secret_key: secret_key, realm: realm, method: method, intent: intent, request: request, expires: expires, digest: digest, opaque: opaque ) Mpp.secure_compare(id, expected_id) end |