{"record":{"id":"6219eac2600b297a","repo":"SonarSource/sonarqube","slug":"invalid-azure-url-or-personal-access-token","errorCode":null,"errorMessage":"Invalid Azure URL or Personal Access Token","messagePattern":"Invalid Azure URL or Personal Access Token","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/AzureDevOpsValidator.java","lineNumber":51,"sourceCode":"  private static final Logger LOG = LoggerFactory.getLogger(AzureDevOpsValidator.class);\n\n  public static final String GLOBAL_PAT_ERROR_MESSAGE = \"Global personal access tokens (\\\"All accessible organizations\\\") are being retired by \" +\n    \"Microsoft and cannot be used. Create a personal access token scoped to a single organization.\";\n\n  private final AzureDevOpsHttpClient azureDevOpsHttpClient;\n  private final Settings settings;\n\n  public AzureDevOpsValidator(AzureDevOpsHttpClient azureDevOpsHttpClient, Settings settings) {\n    this.azureDevOpsHttpClient = azureDevOpsHttpClient;\n    this.settings = settings;\n  }\n\n  public void validate(AlmSettingDto dto) {\n    try {\n      azureDevOpsHttpClient.checkPAT(requireNonNull(dto.getUrl()),\n        requireNonNull(dto.getDecryptedPersonalAccessToken(settings.getEncryption())));\n    } catch (IllegalArgumentException e) {\n      throw new IllegalArgumentException(\"Invalid Azure URL or Personal Access Token\", e);\n    }\n  }\n\n  /**\n   * Denies binding an Azure DevOps Services (Cloud) organization with a Global PAT (\"All accessible\n   * organizations\"). Azure DevOps Server has no cross-org scope, so non-Cloud URLs are always allowed.\n   * A probe failure (network error, Microsoft outage) is treated as inconclusive and fails open, so a\n   * transient issue never blocks configuring a valid binding.\n   */\n  public void checkPatIsNotGlobal(String url, String pat) {\n    if (!AzureDevOpsUrls.isAzureDevOpsServices(url)) {\n      return;\n    }\n    boolean isGlobal;\n    try {\n      isGlobal = azureDevOpsHttpClient.isGlobalPat(pat);\n    } catch (IllegalArgumentException e) {\n      LOG.warn(\"Unable to determine whether the Azure DevOps personal access token is global, allowing the binding\", e);","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/AzureDevOpsValidator.java#L33-L69","documentation":"AzureDevOpsValidator.validate wraps AzureDevOpsHttpClient.checkPAT: if the URL or PAT check fails with IllegalArgumentException (bad URL format, unreachable server, or invalid token), it rethrows with the generic message 'Invalid Azure URL or Personal Access Token', deliberately hiding the underlying cause from the user while keeping it as the cause for logs.","triggerScenarios":"Calling validate(AlmSettingDto) when dto.getUrl() is malformed or not an Azure DevOps URL, the PAT is empty/expired/invalid, or checkPAT cannot contact the Azure DevOps instance (any IllegalArgumentException from the HTTP client).","commonSituations":"Admin saving an Azure DevOps ALM setting in SonarQube with a typo'd URL (missing https://dev.azure.com/org), a revoked or expired PAT, or a network/proxy blocking the SonarQube server from reaching Azure DevOps.","solutions":["Check the server log for the wrapped IllegalArgumentException cause to see the exact failure.","Verify the URL format: Azure DevOps Services https://dev.azure.com/{organization} or Server https://{server}/{collection}, reachable from the SonarQube host.","Regenerate the PAT in Azure DevOps (User Settings > Personal Access Tokens) and paste it freshly, ensuring it hasn't expired.","Test connectivity from the SonarQube server: curl to the Azure DevOps URL to rule out firewall/proxy issues."],"exampleFix":"// before\nalmSetting.setUrl(\"dev.azure.com/myorg/\");\n\n// after\nalmSetting.setUrl(\"https://dev.azure.com/myorg\");","handlingStrategy":"validation","validationCode":"boolean validUrl = url != null && (url.startsWith(\"https://dev.azure.com/\") || url.matches(\"https://.+/DefaultCollection\"));\nboolean validPat = pat != null && !pat.isBlank();\nif (!validUrl || !validPat) throw new IllegalArgumentException(\"Provide a valid Azure DevOps URL and a non-expired PAT\");","typeGuard":null,"tryCatchPattern":"try {\n  validator.validate(dto);\n} catch (IllegalArgumentException e) {\n  LOG.warn(\"Azure DevOps binding rejected\", e); // cause holds the real reason\n  throw new UserFacingException(\"Check the Azure DevOps URL and Personal Access Token\");\n}","preventionTips":["Always include https:// and the organization/collection path in the URL.","Set PAT expiry reminders; prefer long-expiry PATs for server integrations.","Verify network egress from the SonarQube server to Azure DevOps.","Check server logs for the wrapped cause when the generic message appears."],"tags":["azure-devops","validation","pat","configuration"],"backgroundTag":"invalid-config-value","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"}