-
Notifications
You must be signed in to change notification settings - Fork 547
Fix URL signing api for URLs containing special characters #12435
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
f911647
2c94dc8
96be125
8812582
9cb852a
12216ba
942f3cf
4387ee9
8b1ab7b
4bbb576
4e8b6ff
77ba0c8
9a4d41f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| ### Signed URLs work again for URLs with special characters | ||
|
|
||
| Requesting a signed URL (e.g. via `/api/admin/requestSignedUrl`, used by external tools, the Globus | ||
| integration and third-party integrations such as the `rdm-integration` connector) was broken in 6.10 | ||
| for URLs whose query contained special characters — most notably persistent IDs such as | ||
| `doi:10.5072/FK2/ABC` (which contain `:` and `/`), as well as spaces, percent-encoded values and | ||
| non-ASCII characters. In 6.10 the signing step began re-encoding/normalizing the URL (for example | ||
| percent-encoding `:` and `/`) before computing the signature, while the request is validated against | ||
| the URL the caller actually presents back. The re-encoded signature no longer matched, so validation | ||
| failed with authentication / "signature does not match" errors. | ||
|
|
||
| Signing no longer re-encodes the URL: it is signed exactly as provided, with only the reserved | ||
| signing parameters (`until`, `user`, `method`, `token`, `key`, `signed`) stripped out; the rest of | ||
| the URL is left untouched, character for character. | ||
|
|
||
| **This restores the URL-signing behavior used before 6.10, so it is compatible with older versions | ||
| and with existing integrations.** Clients and connectors that build or consume signed URLs the way | ||
| they did before 6.10 keep working unchanged, signatures are computed the same way as before the | ||
| regression, and URLs containing special characters validate again. No client-side changes are | ||
| required. | ||
|
|
||
| ### A signing secret is now required for signed URLs | ||
|
|
||
| Separately from the fix above, Dataverse no longer falls back to a weak signing key when | ||
| `dataverse.api.signing-secret` is unset. Previously, with no secret configured, signed URLs were | ||
| signed using only the user's API token (or, for a guest, a value derived from the public URL), which | ||
| is too weak to be a signing key. A non-empty `dataverse.api.signing-secret` is now required wherever | ||
| URLs are signed with a key based on a user's API token: | ||
|
|
||
| - The endpoints that issue a signed URL on request - `/api/admin/requestSignedUrl` and the | ||
| guestbook-response file download (`POST /api/access/datafile/{id}`) - return an error instead of | ||
| issuing a weakly-signed URL. | ||
| - Signed callbacks and links built internally (external tool launches, Globus transfers, the | ||
| permission-history CSV links) are sent unsigned, with a warning logged, rather than weakly signed. | ||
|
|
||
| Remote and Globus overlay stores are unaffected: they sign with their own per-store secret key, not | ||
| `dataverse.api.signing-secret`. | ||
|
|
||
| **Upgrade note:** installations that rely on signed URLs - including the `rdm-integration` connector, | ||
| signed guestbook-response downloads, and external tools or Globus transfers that use signed callbacks - | ||
| must set `dataverse.api.signing-secret`. See the | ||
| [Configuration Guide](https://guides.dataverse.org/en/latest/installation/config.html#dataverse-api-signing-secret). | ||
| Treat the value like a password. Because the signing secret is part of the signing key, setting (or | ||
| later changing) it invalidates previously issued signed URLs: any existing signed URLs that have not | ||
| yet expired will stop working, and clients/integrations will need to request new ones. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,7 +17,6 @@ | |
| import edu.harvard.iq.dataverse.DvObjectServiceBean; | ||
| import edu.harvard.iq.dataverse.FileMetadata; | ||
| import edu.harvard.iq.dataverse.api.auth.AuthRequired; | ||
| import edu.harvard.iq.dataverse.settings.JvmSettings; | ||
| import edu.harvard.iq.dataverse.settings.SettingsValidationException; | ||
| import edu.harvard.iq.dataverse.util.StringUtil; | ||
| import edu.harvard.iq.dataverse.util.cache.CacheFactoryBean; | ||
|
|
@@ -2453,7 +2452,13 @@ public Response getSignedUrl(@Context ContainerRequestContext crc, JsonObject ur | |
| if (superuser == null || !superuser.isSuperuser()) { | ||
| return error(Response.Status.FORBIDDEN, "Requesting signed URLs is restricted to superusers."); | ||
| } | ||
|
|
||
|
|
||
| // Require a signing secret: without it the key is only the user's API token, which is too weak. | ||
| if (!UrlSignerUtil.isSigningSecretConfigured()) { | ||
| return error(Response.Status.INTERNAL_SERVER_ERROR, | ||
| "Requesting signed URLs requires a signing secret to be configured. Please set the dataverse.api.signing-secret JVM option."); | ||
| } | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My concern with this being here is that it doesn't cover the URL signing when requesting a file download with a guestbook response. Those APIs still work without the secret. (See Access.java) These APIs all call UrlSignerUtil.signUrl, which should have this code to return an error if no signing-secret exists. {
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. Two commits: first I added the same guard to the guestbook download ( I kept it out of Heads up: centralizing means all such signing now needs the secret: external tool/Globus callbacks and permission-history links too, not just the two endpoints. Every install using them must set That may be too much; happy to scope it back to just |
||
| String userId = urlInfo.getString("user"); | ||
| String key=null; | ||
| if (userId != null) { | ||
|
|
@@ -2475,14 +2480,13 @@ public Response getSignedUrl(@Context ContainerRequestContext crc, JsonObject ur | |
| if (key == null) { | ||
| return error(Response.Status.CONFLICT, "Do not have a valid user with apiToken"); | ||
| } | ||
| key = JvmSettings.API_SIGNING_SECRET.lookupOptional().orElse("") + key; | ||
| } | ||
|
|
||
| String baseUrl = urlInfo.getString("url"); | ||
| int timeout = urlInfo.getInt(URLTokenUtil.TIMEOUT, 10); | ||
| String method = urlInfo.getString(URLTokenUtil.HTTP_METHOD, "GET"); | ||
| String signedUrl = UrlSignerUtil.signUrl(baseUrl, timeout, userId, method, key); | ||
|
|
||
| String signedUrl = UrlSignerUtil.signUrlWithApiKey(baseUrl, timeout, userId, method, key); | ||
|
|
||
| return ok(Json.createObjectBuilder().add(URLTokenUtil.SIGNED_URL, signedUrl)); | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.