{"record":{"id":"bed604e0753714c3","repo":"SonarSource/sonarqube","slug":"invalid-saml-login-url","errorCode":null,"errorMessage":"Invalid SAML Login URL","messagePattern":"Invalid SAML Login URL","errorType":"validation","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SonarqubeRelyingPartyRegistrationRepository.java","lineNumber":76,"sourceCode":"    RelyingPartyRegistration.Builder builder = RelyingPartyRegistration.withRegistrationId(\"saml\")\n      .assertionConsumerServiceLocation(callbackUrl != null ? callbackUrl : ANY_URL)\n      .assertionConsumerServiceBinding(Saml2MessageBinding.POST)\n      .entityId(samlSettings.getApplicationId())\n      .assertingPartyMetadata(metadata -> metadata\n        .entityId(samlSettings.getProviderId())\n        .singleSignOnServiceLocation(validateLoginUrl(samlSettings.getLoginUrl()))\n        .verificationX509Credentials(c -> c.add(Saml2X509Credential.verification(x509Certificate)))\n        .wantAuthnRequestsSigned(samlSettings.isSignRequestsEnabled())\n      );\n    addSignRequestFieldsIfNecessary(builder);\n    return builder.build();\n  }\n\n  private static String validateLoginUrl(String url) {\n    try {\n      return new URI(url).toURL().toExternalForm();\n    } catch (MalformedURLException | URISyntaxException | IllegalArgumentException e) {\n      throw new IllegalStateException(\"Invalid SAML Login URL\", e);\n    }\n  }\n\n  private void addSignRequestFieldsIfNecessary(RelyingPartyRegistration.Builder builder) {\n    //(on SQ) to sign request we need SP private key and certificate\n    //(on IDP) to verify request IDP needs SP public key (certificate)\n\n    //(on IDP) to sign response we need IDP private key (embedded)\n    //(on SQ) to verify response we need IDP public key (certificate) !mandatory!\n\n    //(on IDP) encryption: we need SP public key (certificate)\n    //(on SQ) decryption: we need Service Provide private key and certificate\n    Optional<String> serviceProviderPrivateKey = samlSettings.getServiceProviderPrivateKey();\n\n    if (serviceProviderPrivateKey.isEmpty() || samlSettings.getServiceProviderCertificate() == null) {\n      if (samlSettings.isSignRequestsEnabled()) {\n        throw new IllegalStateException(\"Sign requests is enabled but SonarQube private key and/or SonarQube certificate is missing\");\n      }","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SonarqubeRelyingPartyRegistrationRepository.java#L58-L94","documentation":"SonarqubeRelyingPartyRegistrationRepository.validateLoginUrl() converts the configured SAML login URL string into a java.net.URI and then a URL to validate it. If URI parsing, toURL(), or toExternalForm() throws MalformedURLException, URISyntaxException, or IllegalArgumentException, the configured SSO login URL is not a syntactically valid absolute URL and it throws IllegalStateException.","triggerScenarios":"Calling builder()/findByRegistrationId() during SAML initialization with sonar.auth.saml.loginUrl set to something like 'idp.example.com/sso' (no scheme), 'http:/host' (malformed), a URL with illegal characters (unescaped spaces, '<', '>'), or an empty/blank value.","commonSituations":"Misconfigured sonar.auth.saml.loginUrl copied from IdP documentation without the https:// prefix, values containing unencoded query characters, or settings left blank after enabling SAML.","solutions":["Set sonar.auth.saml.loginUrl to a fully qualified absolute URL, e.g. https://idp.example.com/saml/sso","Escape or remove illegal characters in the URL (spaces, quotes, raw Unicode)","If the IdP gives a relative endpoint, prepend the IdP base URL scheme and host"],"exampleFix":"// before\nsonar.auth.saml.loginUrl=idp.example.com/saml/sso\n// after\nsonar.auth.saml.loginUrl=https://idp.example.com/saml/sso","handlingStrategy":"validation","validationCode":"static boolean isValidUrl(String url) {\n  try {\n    return new URI(url).toURL().toExternalForm() != null;\n  } catch (MalformedURLException | URISyntaxException | IllegalArgumentException e) {\n    return false;\n  }\n}\n// guard: if (!isValidUrl(loginUrl)) fix before calling","typeGuard":"static boolean isAbsoluteHttpUrl(String s) {\n  try { return java.net.URI.create(s).getScheme() != null; } catch (IllegalArgumentException e) { return false; }\n}","tryCatchPattern":"try {\n  repository.builder().build();\n} catch (IllegalStateException e) {\n  if (e.getMessage().contains(\"Invalid SAML Login URL\")) {\n    throw new ConfigurationException(\"sonar.auth.saml.loginUrl must be absolute, e.g. https://idp/sso\");\n  }\n  throw e;\n}","preventionTips":["Always include the scheme (https://) in sonar.auth.saml.loginUrl","Copy the login URL from IdP metadata XML (EntityDescriptor/IDPSSODescriptor) not from prose","URL-encode spaces and special characters","Validate the URL with `curl -I` before saving settings"],"tags":["saml","url","config"],"backgroundTag":"invalid-url-format","analyzedSha":"184c821202192afc1c599fc912d0889b69fffa53","analyzedAt":"2026-09-09T12:23:51.573Z","contentChangedAt":"2026-09-09T12:23:51.573Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}