Repository navigation
ID-279: replace a mismatched Authorization header on server Subscriptions - #50
Conversation
…ions The server suite's Subscription check only looked for an exact "Authorization: Bearer <token>" entry in channel.header. When the tester's Subscription already carried an Authorization header with a different token, Inferno appended a second Authorization header instead of replacing the first. Notification verification then compared the notification against the stale header and failed, even though the server sent the token Inferno asked for. server_check_channel now finds Authorization entries by header name, case-insensitively, and leaves the header untouched only when there is exactly one entry and it matches "Bearer <access token>" in the same form notification verification parses. Otherwise it removes every Authorization entry, keeps all other headers, and appends the correct one, with a warning that says whether the header was added or replaced. The warning no longer repeats the old token. A header field that is not a list is replaced with a list instead of raising an error, and entries that are not strings are left alone. The Subscription conformance test description now says Inferno replaces any other Authorization entry. Fixes inferno-framework#48 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
karlnaden
left a comment
There was a problem hiding this comment.
two minor issues to fix
| end | ||
|
|
||
| def server_check_channel_authorization_header(subscription_channel, access_token) | ||
| headers = subscription_channel['header'].is_a?(Array) ? subscription_channel['header'] : [] |
There was a problem hiding this comment.
if the tester incorrectly provided a string instead of a list of strings for the header, this will cover that over and throw out that header. Previously it would result in a purple error. Let's do something smarter. We could either fix it up, but add a warning that we did so, or have an explicit failure due to a malformed Subscription. I think its probably right to fix it up, but not completely confident in that.
There was a problem hiding this comment.
Went with fixing it up (0b85f92). A string header is now converted into a one-entry list with a warning ("The header field on the Subscription resource must be a list of strings. Inferno converted the single string value into a list containing that value."), and the normal Authorization check then runs on that list: a correct token is kept, a different one is replaced, and any other header is kept with ours added. Any other non-list value (object, number, boolean) is left as is and recorded as an error, so the Subscription conformance test fails rather than erroring or quietly discarding it. Specs cover each of these shapes, including a run of the conformance test itself, and Testing Guidance steps 5 and 6 show the string and object cases in the UI.
| subscription_channel = subscription['channel'] | ||
| unless channel_field_matches?(subscription_channel, 'endpoint', subscription_channel_url) |
There was a problem hiding this comment.
pre-existing problem if the channel is missing or malformed. makes sense to fix, I think. Add specs as well.
| subscription_channel = subscription['channel'] | |
| return subscription unless subscription_channel.is_a?(Hash) | |
| unless channel_field_matches?(subscription_channel, 'endpoint', subscription_channel_url) |
There was a problem hiding this comment.
Applied the suggestion (0b85f92). A Subscription with no channel, or a channel that isn't an object (string or array), is now returned unchanged, and subscription_verification fails it with the existing "The Subscription must include a channel element." message instead of a purple error. Added specs for each case at both levels: server_check_channel and the Subscription conformance test. Testing Guidance step 6 shows the missing-channel case in the UI.
The CI and execution-script workflows ran on ubuntu-latest, which moves to a new Ubuntu release whenever GitHub updates the label and was causing CI problems. Every job in ruby.yml and run_inferno_execution_scripts.yml now runs on ubuntu-24.04, which the maintainer asked all in-progress PRs to use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
server_check_channel treated a channel.header that was not a list as empty, so a tester who wrote the header as a single string lost it without being told. A header string is now converted into a list with a warning saying Inferno did so, and the usual Authorization check then runs on that list. Any other non-list value is left unchanged and recorded as an error, so the Subscription conformance test fails instead of quietly replacing what the tester provided. server_check_channel also read channel.endpoint without checking that channel exists, so a Subscription with no channel, or with a channel that is not an object, raised an error. It now returns such a Subscription unchanged, and subscription_verification fails it with the existing "must include a channel element" message. The warning about a changed channel endpoint is now a squiggly heredoc, so an endpoint value containing a newline no longer turns the message into a code block. The Subscription conformance test description now says that header must be a list of strings, that a single string is converted and any other value fails, and that a type other than rest-hook fails the test rather than being corrected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The event notification conformance test took the requested headers from the Subscription the server returned when it was created. The IG requires each channel.header value of the Subscription to be sent on notifications, but does not require a server to echo those headers back. A server that left header out of its response made the test raise an error. A server that returned different header values had the notification compared against those values instead of the ones Inferno asked for, including the Authorization header. notification_header_verification takes a new optional requested_subscription keyword, which defaults to the subscription argument so existing callers are unaffected. The notification conformance test passes the Subscription Inferno submitted (the creation request body), so the requested headers always come from what Inferno asked for, while the content type is still checked against the payload the server returned. The test now fails with a clear message when the returned Subscription is not a JSON object. Requested header strings are parsed the way HTTP defines header fields: the name ends at the first colon, whitespace around the value is ignored, and names are compared case-insensitively. Previously a header written without a space after the colon, such as "X-Custom:value", could never match the header the server sent. A missing or malformed channel or header, entries that are not strings, blank header names, and a payload that is not a string are skipped instead of raising an error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
karlnaden
left a comment
There was a problem hiding this comment.
small wording tweaks - will probably need to adjust line length after applying the suggestions
| action = authorization_headers.empty? ? 'Added' : 'Replaced' | ||
| add_message('warning', %( | ||
| #{action} the Authorization header field with a Bearer token set to `#{access_token}` in the `header` field on | ||
| the Subscription resource in order to connect successfully with the Inferno subscription channel. |
There was a problem hiding this comment.
I don't know that testers will necessarily understand why this change is needed to
"connect successfully". Suggested change focuses on what happened and points them to specific input for more details.
| the Subscription resource in order to connect successfully with the Inferno subscription channel. | |
| Tester-provided Subscription updated before use: #{action} the Authorization header entry in the `header` field with the Bearer token `#{access_token}` provided in the "Notification Access Token" input. |
There was a problem hiding this comment.
I wrapped it onto two lines to stay under the 120-character limit, and the UI shows it as one sentence. Your suggestion only covered line 115, so I also replaced line 114 by hand. The specs now match the new wording and check that the message still names the token. Testing Guidance step 3 has a new screenshot.
| The `header` field on the Subscription resource must be a list of strings, so Inferno could not add the | ||
| Authorization header field with a Bearer token set to `#{access_token}`. |
There was a problem hiding this comment.
| The `header` field on the Subscription resource must be a list of strings, so Inferno could not add the | |
| Authorization header field with a Bearer token set to `#{access_token}`. | |
| The `header` element on the tester-provided Subscription must be a list of strings. |
There was a problem hiding this comment.
With the token gone from this message, access_token was no longer used in server_channel_header_list, and RuboCop flagged it (Lint/UnusedMethodArgument). I removed the argument from the method and from its one caller in server_check_channel_authorization_header. Testing Guidance step 6 has a new screenshot.
| The `header` field on the Subscription resource must be a list of strings. Inferno converted the single | ||
| string value into a list containing that value. |
There was a problem hiding this comment.
| The `header` field on the Subscription resource must be a list of strings. Inferno converted the single | |
| string value into a list containing that value. | |
| Tester-provided Subscription updated before use: incorrect string value for element `header` converted into a list containing the provided string. |
There was a problem hiding this comment.
I wrapped it onto two lines for the line-length limit. The specs match the new wording, and Testing Guidance step 5 has a new screenshot.
Reword the three messages server_check_channel adds when it changes or rejects the tester's channel.header, as the reviewer suggested. The Authorization warning now starts with "Tester-provided Subscription updated before use:" and says the Bearer token comes from the "Notification Access Token" input. It no longer says the change is needed to connect to the Inferno subscription channel. The warning for a single-string header and the error for any other non-list header use the same wording. The error for a non-list header no longer names the access token, so server_channel_header_list had an unused access_token argument. That argument is removed from the method and from its one caller. The module specs now match on the new wording, and each one also checks that the Authorization warning still names the token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Previously, when the Subscription a tester gave the server suite already had an
Authorizationentry inchannel.headerwith a different token than the Notification Access Token input, Inferno added a secondAuthorizationheader instead of replacing the first. The server sent notifications with the token Inferno asked for, but the notification verification compared them against the stale header and failed (Expected Bearer <old token>, received Bearer <new token>).Now the server suite replaces any
Authorizationentry that doesn't match, comparing the header name case-insensitively, and keeps all other headers. The warning says whether the header was added or replaced. This affects the server suite and the Da Vinci PAS kit, which reuses the same check.Additionally:
headerwritten as a single string is converted into a list with a warning, and any other non-listheaderfails the Subscription conformance test. A Subscription with nochannel, or achannelthat isn't an object, fails with "The Subscription must include achannelelement." instead of an error.headerout of its response used to make this test error, and one that returned different values had the notification compared against those values.X-Custom:value(no space after the colon) matches the header the server sent.ubuntu-24.04instead ofubuntu-latest, as requested for all in-progress PRs.Fixes #48
Run the specs with
bundle exec rspec spec/subscriptions_test_kit/common spec/subscriptions_test_kit/subscription_r4_server/common.How it works
lib/subscriptions_test_kit/common/subscription_conformance_verification.rbserver_check_channelreturns the Subscription unchanged whenchannelis missing or isn't an object. The conformance test'ssubscription_verificationthen fails it with the existingchannelmessage.server_check_channelnow calls a newserver_check_channel_authorization_header.Authorizationheader when it is a string whose name (the text before the first:, trimmed) isauthorizationin any casing.Authorization: Bearer <access token>. Otherwise everyAuthorizationentry is removed, other entries are kept in order, andAuthorization: Bearer <access token>is appended.headerfield with the Bearer token<access token>provided in the "Notification Access Token" input." and no longer shows the old token.headerthat is a single string is converted into a one-entry list with the warning "Tester-provided Subscription updated before use: incorrect string value for elementheaderconverted into a list containing the provided string.", and theAuthorizationcheck then runs on that list. Any other non-listheaderis left unchanged and recorded as an error, so the test fails. Entries that aren't strings are left alone.<<~heredoc, so an endpoint containing a newline no longer renders as a code block.lib/subscriptions_test_kit/common/notification_conformance_verification.rbnotification_header_verificationtakes a new optionalrequested_subscription:keyword. It defaults to thesubscriptionargument, so existing callers (including other kits) are unaffected. The requested headers come fromrequested_subscription; the content type is still checked against thepayloadofsubscription.:, the value is trimmed, and names are compared case-insensitively. Entries that aren't strings, blank names, a missing or malformedchannelorheader, and apayloadthat isn't a string are skipped instead of raising an error.lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/common/interaction/subscription_conformance_test.rbAuthorizationentry, thatheadermust be a list of strings (a single string is converted, any other value fails), and that atypeother thanrest-hookfails the test. It previously said Inferno would correct thetype.lib/subscriptions_test_kit/suites/subscriptions_r5_backport_r4_server/common/interaction_verification/notification_conformance_test.rbrequested_subscription:..github/workflows/ruby.yml,.github/workflows/run_inferno_execution_scripts.ymlruns-onisubuntu-24.04instead ofubuntu-latest.Specs
subscription_conformance_verification_spec.rb: the harness outputs the checked Subscription, so the specs assert on the resultingchannel.headeras well as the warning. Cases: correct header kept (also with a lowercase name, and with a token containing a colon); header added when missing; a different token replaced without echoing it; an uppercaseAUTHORIZATION :name replaced; a mixed list where everyAuthorizationentry is replaced andX-Custom-Headeris kept; four malformed variants of the correct header replaced; duplicate correct entries collapsed to one; a lowercasebearerscheme replaced; non-string entries ignored; names that only contain "Authorization" (X-Authorization,Authorization-Info) kept; a stringheaderconverted (correct token kept, different token replaced, other header kept); an object or numberheaderfailing unchanged; a missing, string or arraychannelreturned unchanged; an endpoint containing a newline rendering as prose.subscription_conformance_test_spec.rb: the conformance test fails, rather than erroring, for a missing, string or arraychanneland for an objectheader.notification_conformance_verification_spec.rb: a missing, string, object or non-string-entryheader, achannelthat isn't an object, apayloadthat isn't a string, blank header names, a header written without a space after the colon, and whitespace around the value.notification_conformance_test_spec.rb: the server's response has noheader, adds a header, or has a differentAuthorizationtoken than the one Inferno submitted (the notification sending the submitted token passes; sending the server's token fails); a response that isn't a JSON object fails.Impact on existing results
headerfield …" to "Tester-provided Subscription updated before use: Added/Replaced the Authorization header entry in theheaderfield with the Bearer tokenX…". This kit's execution scripts don't depend on it: all three still match their expected results unchanged.Authorizationentry moves to the end of the list.server_check_channel(v2.2.1 test 1.02 andsubscriptions_r4_server_subscription_conformancein v2.0.1), and its pended-scenario notification test 2.3.2.02 failed on the stale header. It needs no code change, onlybundle update subscriptions_test_kitafter a release (its gemspec allows~> 0.13, >= 0.13.0). Callers ofnotification_header_verificationthat pass two arguments behave as before, apart from the more lenient header parsing.Testing Guidance
Run the client and server suites against each other in the same Inferno, changing the server's Notification Access Token so it no longer matches the
Authorization: Bearer SAMPLE_TOKENheader in the preset's Subscription.1. Start the client session
Steps
Expected Result
2. Run the server workflow with a different Notification Access Token
Steps
SAMPLE_TOKENtoID279_TOKEN. Leave the Subscription unchanged and submit.Expected Result
headerfield with the Bearer tokenID279_TOKENprovided in the "Notification Access Token" input."Authorization: Bearer ID279_TOKENheader.headerfield …", and the Subscription was sent with bothAuthorization: Bearer SAMPLE_TOKENandAuthorization: Bearer ID279_TOKEN.3. Check the notification verification
Steps
Expected Result
4. Finish the client session
Steps
Expected Result
5. A
headerwritten as a single stringSteps
SAMPLE_TOKENand, in the Workflow Subscription Resource input, change"header": ["Authorization: Bearer SAMPLE_TOKEN"]to"header": "Authorization: Bearer SAMPLE_TOKEN".Expected Result
headerconverted into a list containing the provided string." and no "Added" or "Replaced" warning.SAMPLE_TOKEN…".6. A malformed
headeror a Subscription without achannelThese runs only need the server suite. Group 1 can't be run test by test, so run the whole group. 1.1.02 fails ("expected 201, but received 500") and 1.2.02 is skipped because no client session is waiting for the Subscription.
Steps
"header": ["Authorization: Bearer SAMPLE_TOKEN"]to"header": {"Authorization": "Bearer SAMPLE_TOKEN"}and submit."channel": { … }element from the Workflow Subscription Resource input.Expected Result
header, 1.1.01 fails with "Resource does not conform to the profile: …backport-subscription". Its messages include the error "Theheaderelement on the tester-provided Subscription must be a list of strings." and the validator's "The property header must be a JSON Array, not an Object".channel, 1.1.01 fails with "The Subscription must include achannelelement."