Skip to content

fix: show wizard and insightful error when SSO is unreachable (crw-11958)#342

Open
adietish wants to merge 2 commits into
redhat-developer:mainfrom
adietish:crw-11958
Open

fix: show wizard and insightful error when SSO is unreachable (crw-11958)#342
adietish wants to merge 2 commits into
redhat-developer:mainfrom
adietish:crw-11958

Conversation

@adietish

@adietish adietish commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

fixes https://redhat.atlassian.net/browse/CRW-11958

with this fix one gets to the Devspaces connection wizard. An error is then displayed once the user tries to connect:
image

@adietish adietish self-assigned this Jul 23, 2026
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@adietish, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 3 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 99356f74-ac12-458a-9d99-761cef4982e5

📥 Commits

Reviewing files that changed from the base of the PR and between 7c81bdf and 7c62a91.

📒 Files selected for processing (8)
  • src/main/kotlin/com/redhat/devtools/gateway/auth/code/OAuthDiscovery.kt
  • src/main/kotlin/com/redhat/devtools/gateway/auth/code/OpenShiftAuthCodeFlow.kt
  • src/main/kotlin/com/redhat/devtools/gateway/auth/oidc/OidcProviderMetadataResolver.kt
  • src/main/kotlin/com/redhat/devtools/gateway/auth/session/RedHatAuthSessionManager.kt
  • src/main/resources/messages/DevSpacesBundle.properties
  • src/test/kotlin/com/redhat/devtools/gateway/auth/code/OAuthDiscoveryTest.kt
  • src/test/kotlin/com/redhat/devtools/gateway/auth/code/OpenShiftAuthCodeFlowTest.kt
  • src/test/kotlin/com/redhat/devtools/gateway/auth/oidc/OidcProviderMetadataResolverTest.kt
📝 Walkthrough

Walkthrough

OIDC metadata resolution now detects unreachable providers, converts those failures into localized SSO errors with Token authentication guidance, and preserves causes. Browser login reuses a resolver instance, with tests covering host extraction, exception classification, and failure mapping.

Changes

OIDC unreachable-host handling

Layer / File(s) Summary
Resolver error mapping and localized messaging
src/main/kotlin/com/redhat/devtools/gateway/auth/oidc/OidcProviderMetadataResolver.kt, src/main/resources/messages/DevSpacesBundle.properties
The resolver classifies nested connectivity failures, derives the provider host, preserves successful caching, and throws a localized SsoLoginException.Failed with the original cause for unreachable providers.
Browser login wiring and validation
src/main/kotlin/com/redhat/devtools/gateway/auth/session/RedHatAuthSessionManager.kt, src/test/kotlin/com/redhat/devtools/gateway/auth/oidc/OidcProviderMetadataResolverTest.kt
Browser login resolves metadata through a stored resolver, while tests cover host extraction, connectivity classification, nested causes, unrelated failures, and Token authentication messaging.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RedHatAuthSessionManager
  participant OidcProviderMetadataResolver
  participant OIDCDiscovery
  participant SsoLoginExceptionFailed

  RedHatAuthSessionManager->>OidcProviderMetadataResolver: resolve()
  OidcProviderMetadataResolver->>OIDCDiscovery: request provider metadata
  OIDCDiscovery-->>OidcProviderMetadataResolver: unreachable failure
  OidcProviderMetadataResolver->>SsoLoginExceptionFailed: create localized failure with cause
  SsoLoginExceptionFailed-->>RedHatAuthSessionManager: throw login error
Loading

Suggested reviewers: azatsarynnyy, vrubezhny

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: showing the wizard and a helpful error when SSO is unreachable.
Description check ✅ Passed The description is directly related to the change and explains the new wizard/error behavior.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov-commenter

codecov-commenter commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.97297% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 32.80%. Comparing base (71098f6) to head (7c62a91).
⚠️ Report is 394 commits behind head on main.

Files with missing lines Patch % Lines
.../gateway/auth/oidc/OidcProviderMetadataResolver.kt 71.42% 3 Missing and 5 partials ⚠️
...s/gateway/auth/session/RedHatAuthSessionManager.kt 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##            main     #342       +/-   ##
==========================================
+ Coverage   0.00%   32.80%   +32.80%     
==========================================
  Files          4      111      +107     
  Lines         26     4588     +4562     
  Branches       0      872      +872     
==========================================
+ Hits           0     1505     +1505     
- Misses        26     2876     +2850     
- Partials       0      207      +207     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/main/kotlin/com/redhat/devtools/gateway/auth/oidc/OidcProviderMetadataResolver.kt (1)

41-43: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add finite timeouts to the OIDC discovery request.

OIDCProviderConfigurationRequest.toHTTPRequest().send() does not set connect/read timeouts by default, so a non-responding provider can suspend login indefinitely. Set finite connectTimeout and readTimeout on the returned HTTPRequest, for example 10s connect and a higher read timeout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/kotlin/com/redhat/devtools/gateway/auth/oidc/OidcProviderMetadataResolver.kt`
around lines 41 - 43, Update the OIDC discovery request in
OidcProviderMetadataResolver by configuring finite connectTimeout and
readTimeout values on the HTTPRequest returned by
OIDCProviderConfigurationRequest.toHTTPRequest() before calling send(), using
approximately 10 seconds for connection establishment and a higher finite
duration for reading the response.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/kotlin/com/redhat/devtools/gateway/auth/oidc/OidcProviderMetadataResolver.kt`:
- Around line 67-74: Update isSsoUnreachable to classify NoRouteToHostException
as an unreachable SSO cause alongside the existing network exceptions, and add a
cause-chain test covering this exception so route failures receive the
Token-authentication SSO guidance.

In `@src/main/resources/messages/DevSpacesBundle.properties`:
- Line 28: Update the
connector.wizard_step.openshift_connection.error.sso_unreachable message to
escape the apostrophe in “you're” for MessageFormat compatibility, using the
existing wording and placeholder unchanged.

In
`@src/test/kotlin/com/redhat/devtools/gateway/auth/oidc/OidcProviderMetadataResolverTest.kt`:
- Around line 62-76: Make the `resolve maps unreachable host to
SsoLoginException with Token hint` test deterministic by replacing the external
`.invalid` host resolution with an injected request/transport failure or
controlled local endpoint configured with bounded timing. Preserve the existing
`SsoLoginException.Failed` assertions, including the message hints and
unreachable cause validation, without relying on real DNS or unbounded network
access.

---

Nitpick comments:
In
`@src/main/kotlin/com/redhat/devtools/gateway/auth/oidc/OidcProviderMetadataResolver.kt`:
- Around line 41-43: Update the OIDC discovery request in
OidcProviderMetadataResolver by configuring finite connectTimeout and
readTimeout values on the HTTPRequest returned by
OIDCProviderConfigurationRequest.toHTTPRequest() before calling send(), using
approximately 10 seconds for connection establishment and a higher finite
duration for reading the response.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a9d571e8-a78e-49d5-97c0-09d7782805c8

📥 Commits

Reviewing files that changed from the base of the PR and between 07ca022 and 7c81bdf.

📒 Files selected for processing (4)
  • src/main/kotlin/com/redhat/devtools/gateway/auth/oidc/OidcProviderMetadataResolver.kt
  • src/main/kotlin/com/redhat/devtools/gateway/auth/session/RedHatAuthSessionManager.kt
  • src/main/resources/messages/DevSpacesBundle.properties
  • src/test/kotlin/com/redhat/devtools/gateway/auth/oidc/OidcProviderMetadataResolverTest.kt

connector.wizard_step.openshift_connection.text.redhat_sso_info=Authenticate using Red Hat SSO (Sandbox only)
connector.wizard_step.openshift_connection.text.redhat_sso_token_note=Token will not be saved to kubeconfig
connector.wizard_step.openshift_connection.text.pipeline_token_comment=Pipeline tokens require special handling
connector.wizard_step.openshift_connection.error.sso_unreachable=Cannot reach SSO provider ({0}). If you're behind a corporate proxy or firewall, try using Token authentication instead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C3 'MessageFormat|BundleBase|DevSpacesBundle' src/main

Repository: redhat-developer/devspaces-gateway-plugin

Length of output: 39850


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== DevSpacesBundle.kt =="
cat -n src/main/kotlin/com/redhat/devtools/gateway/DevSpacesBundle.kt

echo
echo "== target bundle line =="
cat -n src/main/resources/messages/DevSpacesBundle.properties | sed -n '20,32p'

echo
echo "== call sites involving sso_unreachable/message formatter deps =="
cat -n src/main/kotlin/com/redhat/devtools/gateway/auth/oidc/OidcProviderMetadataResolver.kt | sed -n '75,90p'
rg -n "class DynamicBundle|DynamicBundle|MessageFormat|ResourceBundle|message\\(" . --glob '*.{kt,java,groovy}'

Repository: redhat-developer/devspaces-gateway-plugin

Length of output: 10602


🌐 Web query:

IntelliJ platform DynamicBundle getMessage String format MessageFormat apostrophe quote semantics

💡 Result:

In the IntelliJ Platform, DynamicBundle uses Java's MessageFormat for string substitution [1][2]. Because MessageFormat interprets certain characters as special syntax, you must adhere to specific escaping rules for apostrophes and single quotes in your resource bundle property files [1][3]. The fundamental rules are: 1. Escape Single Quotes: If your string contains a single quote (apostrophe) and is processed by MessageFormat (i.e., it contains placeholders like {0}), you must double the single quote to render it correctly [1][3]. For example, "doesn't" becomes "doesn''t" [1][3]. 2. When to Escape: Escaping is only required if the string is treated as a MessageFormat pattern [3]. IntelliJ's property file inspection tool typically identifies strings that contain placeholders (e.g., {0}) as MessageFormat patterns and will flag unescaped single quotes as potential errors [3]. Strings without placeholders do not require this escaping [1]. 3. Nested Patterns (ChoiceFormat): If your message includes a nested pattern, such as a ChoiceFormat, the escaping requirements increase [1][3]. Because the nested content is evaluated as a separate format string, single quotes may need to be doubled twice. For example, a single quote needed in the result of a choice variant would require four single quotes ('''') [1]. 4. Best Practice: A widely recommended practice to avoid these complexities in human-readable text is to use the typographic right single quotation mark (’) (U+2019) for actual apostrophes, reserving the standard ASCII apostrophe/single quote (') (U+0027) exclusively for MessageFormat syntax escaping [4]. IntelliJ provides IDE support, such as inspections in property files, to help identify and correct these MessageFormat pattern issues [1][3]. If you are working in Java/Kotlin code, you can rely on DynamicBundle's methods, which handle the underlying ResourceBundle and MessageFormat delegation automatically [1][5][6].

Citations:


🌐 Web query:

IntelliJ AbstractBundle DynamicBundle format pattern quote escaping apostrophe before placeholder MessageFormat

💡 Result:

When using DynamicBundle in the IntelliJ Platform, message formatting relies on standard java.text.MessageFormat, which uses single quotes (') as escape characters for both placeholders and literal text [1][2]. To correctly escape apostrophes or display single quotes in your message patterns, follow these rules: 1. Basic Escaping: To include a single literal apostrophe (') in a message that contains a placeholder, you must use two consecutive single quotes ('') [1][2]. For example, the string "File {0} doesn''t exist" will render as "File doesn't exist" [1]. 2. Plain Text: If a message does not contain any placeholders (no curly braces like {0}), MessageFormat is not invoked, and single quotes do not need to be escaped [1]. 3. Choice Formats (Nested Patterns): When using a choice format, the sub-message is treated as a new MessageFormat pattern. This introduces an additional layer of nesting, requiring you to double the existing escape sequences [1][2]. To display a single apostrophe inside a choice format, you must use four consecutive single quotes ('''') [1][2]. Example for choice format: {0,choice,1#One person doesn''t|2#{0} people don''''t} like MessageFormat [1] In this example: - The first variant uses '' to render a single apostrophe [1]. - The second variant uses '''' to render a single apostrophe because it is nested within the choice pattern [1][2]. When implementing, ensure you are using DynamicBundle as recommended (delegating to an instance rather than extending the class) to handle localization and resource resolution correctly [1][3][4]. The IntelliJ platform automatically validates these patterns and will report errors in properties files if quotes are unpaired or incorrectly escaped [2].

Citations:


🌐 Web query:

Java MessageFormat single quote apostrophe before placeholder escaping single quote literal

💡 Result:

In Java's MessageFormat, a single quote (apostrophe) must be represented by doubling it (i.e., using two single quotes: '') throughout the pattern string [1][2][3]. When you need to include a literal single quote, simply replace every instance of ' with ''. For example, the word "doesn't" should be written as "doesn''t" within a MessageFormat pattern [4]. It is important to note that a pair of single quotes (e.g., '{0}') is used to quote arbitrary characters (treating them as literals rather than format elements) [1][3]. Because of this, if you want to output a literal single quote, you must double it (''), even if it appears before a placeholder [1][2]. If you are working with languages that frequently use apostrophes (such as French), the doubling requirement can become burdensome [5]. Common approaches to handle this include: 1. Doubling quotes programmatically: Use string replacement (e.g., str.replace("'", "''")) before passing the string to MessageFormat [5][4]. 2. Using typographic apostrophes: Some developers use the right single quotation mark character (’) instead of the standard ASCII apostrophe (') [5]. Since ’ is not a special metacharacter in MessageFormat, it does not require escaping [5]. 3. Alternative libraries: For more robust internationalization support, some projects migrate to ICU4J (International Components for Unicode), which offers more flexible handling of message patterns [5].

Citations:


🌐 Web query:

site:github.com/RedHat-DevSpacesGateway-plugin OidcProviderMetadataResolver ssoUnreachableMessage connector.wizard_step.openshift_connection.error.sso_unreachable

💡 Result:

The terms OidcProviderMetadataResolver, ssoUnreachableMessage, and connector.wizard_step.openshift_connection.error.sso_unreachable appear to be specific application-level configuration keys, error codes, or localization strings, likely belonging to a private or proprietary software project. While OIDC provider metadata resolution is a standard concept used in libraries like node-oidc-provider [1][2] and Apache Pulsar's OIDC authentication module [3], the specific identifiers provided do not correspond to public APIs or documented error codes in widely available open-source frameworks. The structure connector.wizard_step.openshift_connection.error.sso_unreachable suggests a UI-driven wizard for configuring OpenShift connections where an SSO service is expected but cannot be reached [4][5]. If you are encountering this error, it indicates that the application's configuration wizard is failing to validate or connect to the configured OpenID Connect (SSO) endpoint. Troubleshooting steps should include: 1. Verifying that the Issuer URL or Discovery endpoint provided in the configuration is reachable from the network where the application is running [1][3]. 2. Checking for proxy or firewall settings that might be blocking the connection between the application and the OIDC provider. 3. Reviewing the application logs for a more detailed error message accompanying the sso_unreachable code, as this often contains the underlying HTTP error or timeout exception [3][6].

Citations:


🌐 Web query:

site:github.com/intellij-community AbstractBundle String.format getMessage quote escaping MessageFormat

💡 Result:

AbstractBundle-style messages in IntelliJ follow java.text.MessageFormat, not String.format, so use {0}, {1}, etc. for arguments. In MessageFormat, a single quote starts/ends quoted literal text, so to render an apostrophe you must double it: ''. (docs.oracle.com)

So, for example:

  • It's {0} → needs escaping as It''s {0}
  • Hello, {0} → fine as-is

If you want, I can also point you to the exact IntelliJ source file/class that does the formatting.

Citations:


🏁 Script executed:

#!/bin/bash
python3 - <<'PY'
from argparse import ArgumentParser

class MessageFormatFormatError(Exception):
    pass

class MessageFormat:
    def __init__(self, pattern):
        self.pattern = pattern

    def format(self, arguments):
        result = []
        pos = 0
        arg_index = 0
        num = len(arguments)
        pattern_length = len(self.pattern)
        while pos < pattern_length:
            ch = self.pattern[pos]
            if ch == "'":
                pos += 1
                if pos < pattern_length:
                    if self.pattern[pos] == "'":
                        result.append("'")
                        pos += 1
                        continue
                temp = pos
                while pos < pattern_length and self.pattern[pos] != "'":
                    pos += 1
                quoted = self.pattern[temp:pos]
                if quoted.startswith("{") and quoted.endswith("}"):
                    raise MessageFormatFormatError(f"can't format quoted placeholder: {self.pattern}")
                result.append(quoted)
                pos += 1
            elif ch == "{":
                temp = pos
                pos += 1
                while pos < pattern_length and self.pattern[pos] != "}":
                    pos += 1
                form = self.pattern[temp + 1:pos]
                pos += 1
                if arg_index >= num:
                    raise MessageFormatFormatError(f"can't format value for argument {arg_index} because there are only {num} arguments")
                result.append(arguments[arg_index])
                arg_index += 1
            else:
                result.append(ch)
                pos += 1
        return "".join(result)

def js_format(strings):
    s = strings[0]
    args = strings[1:]
    def replacer(i):
        for j, pos in enumerate(i.args):
            if pos < 0 or pos >= len(i.args) or (pos == len(i.args) - 1 and pos != j):
                raise Exception("invalid argument index")
        return i.values[pos]
    # naive JS-style simulation based on the source pattern for this message shape
    i = None
    print("js_format not fully implemented")

if __name__ == "__main__":
    pattern = "Cannot reach SSO provider ({0}). If you're behind a corporate proxy or firewall, try using Token authentication instead."
    args = ["example"]
    mf = MessageFormat(pattern)
    try:
        print(mf.format(args))
    except Exception as e:
        print("MessageFormat exception:", repr(e))
    # Show where first apostrophe ends quoted block in MessageFormat, if present
    p = pattern
    apost = p.index("'")
    block = p[apost+1:]
    print("block after first apostrophe:", block[:50])
PY

Repository: redhat-developer/devspaces-gateway-plugin

Length of output: 384


Escape the apostrophe before the placeholder text.

DevSpacesBundle.message formats with MessageFormat; because this value contains {0}, If you're... drops the apostrophe and any text following it until the next apostrophe. Use If you''re, or rewrite as If you are behind....

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/resources/messages/DevSpacesBundle.properties` at line 28, Update
the connector.wizard_step.openshift_connection.error.sso_unreachable message to
escape the apostrophe in “you're” for MessageFormat compatibility, using the
existing wording and placeholder unchanged.

Source: Path instructions

Comment on lines +62 to +76
@Test
fun `resolve maps unreachable host to SsoLoginException with Token hint`() {
val resolver = OidcProviderMetadataResolver(
authUrl = "https://oidc-unreachable-host.invalid/auth/realms/test/"
)

val error = assertThrows<SsoLoginException.Failed> {
runBlocking { resolver.resolve() }
}

assertThat(error.message).contains("Cannot reach SSO provider (oidc-unreachable-host.invalid)")
assertThat(error.message).contains("Token authentication")
assertThat(error.cause).isNotNull
assertThat(isSsoUnreachable(error.cause!!)).isTrue()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Make the failure-mapping test deterministic.

This test performs real DNS/HTTP resolution inside runBlocking with no test-level timeout. CI can hang or behave differently under DNS interception, even though the test only needs to verify exception mapping. Inject a request/transport or use a controlled local endpoint with bounded timing.

As per path instructions: “Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity.” This is a test-suite availability issue, not a style preference.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/test/kotlin/com/redhat/devtools/gateway/auth/oidc/OidcProviderMetadataResolverTest.kt`
around lines 62 - 76, Make the `resolve maps unreachable host to
SsoLoginException with Token hint` test deterministic by replacing the external
`.invalid` host resolution with an injected request/transport failure or
controlled local endpoint configured with bounded timing. Preserve the existing
`SsoLoginException.Failed` assertions, including the message hints and
unreachable cause validation, without relying on real DNS or unbounded network
access.

Source: Path instructions

…958)

Map DNS/connect/timeout failures when resolving OIDC metadata to a
user-facing hint to try Token auth; resolve metadata lazily on login.

Signed-off-by: Andre Dietisheim <adietish@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…tadata()

Signed-off-by: Andre Dietisheim <adietish@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants