Build nested Jira request URLs from the configured API URL

What does this MR do and why?

When the Jira API URL setting uses a different host or context path than the one Jira reports in its own self field, cross-references stop working. No comment and no remote link are created, and the logs show:

HTTParty::UnsafeURIError - Requested URI 'https://jira.example.com/jira/rest/api/2/issue/<id>/remotelink' has host 'jira.example.com' but the configured base_uri 'https://jira-api.example.com' has host 'jira-api.example.com'.

jira-ruby builds some nested requests from the issue's self URL. Two are affected: the remote link list, and the update of an existing remote link. It strips the configured site from self only when self starts with it. Otherwise it sends the request to the absolute URL Jira reported. That is the wrong host, and it often has the wrong context path too. Since httparty 0.24 the request is refused instead of being sent with the integration credentials, and !244579 (merged) then let that error abort the whole cross-reference. Before that, the request went out to the reported host.

This MR stops using Jira's reported URL for nested requests:

  • After find_issue loads an issue, it replaces self with JIRA::Resource::Issue.singular_path(client, issue.id). That is the configured context path plus /rest/api/2/issue/<id>, the same base the issue GET already uses successfully.
  • find_remote_link drops the self of the link it returns. jira-ruby then builds the update URL from the configured base and the issue id, the same way it builds create URLs.
  • Both rewrites live in private helpers, use_configured_url_for_issue and use_configured_url_for_link.
  • Gitlab::Jira::HttpClient#make_request now raises Gitlab::Jira::HttpClient::UnexpectedHostError before sending when it gets an absolute URL whose scheme, host or port differs from the configured site. The message names the requested origin and the configured site. If a request path ever follows a foreign URL again, we get this clear error instead of a generic httparty one. It is a StandardError, so it fails closed in the same places the httparty error did.

Comment, remote link and transition creates were already built from config and do not change.

The new guard is slightly stricter than httparty, which only compared hosts. An absolute URL on the same host with a different scheme or port is now rejected too. No current request path sends such a URL.

How this differs from !248334 (closed)

!248334 (closed) rewrote every absolute URL in Gitlab::Jira::HttpClient. It cut the URL at the last /rest/ and re-anchored it on the configured context path. That relies on guessing where the API path starts, which breaks for context paths that contain /rest, for non-/rest endpoints, and for gateway prefixes such as Jira Cloud's /ex/jira/<cloud_id>. This MR never parses the reported URL to rebuild it. Every nested request uses the path pattern that already worked for the issue fetch on the same instance. The guard in make_request only compares the origin and raises. It never rewrites a URL.

How to set up and validate locally

Specs:

bundle exec rspec spec/models/integrations/jira_spec.rb spec/lib/gitlab/jira/http_client_spec.rb

The nested request examples have Jira report self as https://jira.example.com/jira/rest/api/2/issue/10001 while the integration points at a different API URL. They assert that every request goes to the configured API URL, and that the reported host is never contacted. The host check is derived from each context's reported base. They cover:

  • an API URL with and without its own context path
  • a trailing slash on the API URL
  • a blank API URL with a context path in the Web URL
  • Jira Cloud reached through a customer proxy, where Jira reports self on https://api.atlassian.com/ex/jira/<cloud id>
  • updating an existing remote link
  • closing an issue when the re-fetched issue is resolved, so the solved comment and its remote link are sent, with automatic transitions and with a configured transition id

The new http_client_spec.rb covers a relative path, an absolute URL on the configured site, a foreign host, a scheme change and a port change.

Evidence
  • On an isolated GDK, spec/models/integrations/jira_spec.rb and spec/lib/gitlab/jira/http_client_spec.rb pass: 356 examples, 0 failures. With FOSS_ONLY=1: 354 examples, 0 failures.
  • With app/models/integrations/jira.rb reverted to master, the nested request examples fail with HTTParty::UnsafeURIError. With only the find_remote_link change reverted, the update-existing-link examples fail.
  • The guard examples fail with the guard disabled and pass with it restored.
  • The transition examples pass on master too. GitLab reads transitions from the embedded expand=transitions data and posts through a config-based path. They stay as regression guards.
  • The other specs that load Jira issues pass: the EE Jira model, Jira verification push check, external issue links GraphQL, merge service, branch hooks, verify commit, and the check Jira status spec.
  • End to end, against a stub Jira served on two hostnames (Web host and API host): on this branch the cross-reference posts and the Web host receives zero requests. On master it fails with HTTParty::UnsafeURIError on the remote link call.

Tested end to end against a stub Jira on two hostnames, but not against a real Jira or a customer proxy. A manual check would be:

  1. Configure the integration with a Web URL and a Jira API URL on different hosts, where Jira's Base URL matches the Web URL.
  2. Mention the Jira issue key in a commit, then confirm the comment and the remote link are created.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist.

Related to #607785

Generated with Claude Code

Edited by Jorge Tomás

Merge request reports

Loading
Loading