Skip to content

Authentication failure in CLI client due to unencoded URL query parameters #2610

@pasqualemandato

Description

@pasqualemandato

Describe the bug
When using the Python CLI client, authentication fails with the error "Authentication failed, check API token" if any of the query parameters (such as the remote_user email address) contain special characters that alter their meaning in a URL query string (such as +).

The issue occurs because the script calculates the HMAC signature using the raw parameter values, but then appends these unencoded values directly to the URL:
url = base_url+path+'?'+('&'.join(flatten(data)))

When the requests library executes the HTTP call, characters like + in the unencoded query string are interpreted by the backend PHP server as a space ( ) during standard URL decoding. As a result, the server reconstructs the signature base string using the decoded values (e.g., with a space instead of a +), which results in a signature mismatch and rejected authentication.

To Reproduce
Steps to reproduce the behavior:

  1. Configure filesender.py with a username that contains special characters (for example, test+alias@example.com) and a valid API token.
  2. Attempt to upload a file using the CLI client: python3 filesender.py -r recipient@example.com file.txt
  3. The server responds with HTTP 500 and the error: "Authentication failed, check API token" (caused by auth_remote_signature_check_failed on the server-side).

Expected behavior
The Python client should URL-encode the query parameters when appending them to the URL, guaranteeing that the server decodes the exact same string that the client used for its HMAC calculation.

Proposed Solution
The issue can be resolved by correctly quoting the keys and values in the URL query string using urllib.parse.quote.

Here is a patch with the proposed fix:

--- a/scripts/client/filesender.py
+++ b/scripts/client/filesender.py
@@ -42,6 +42,7 @@ try:
     import concurrent.futures
     import hashlib
     import urllib3
+    import urllib.parse
     import os
     import sys
     import json
@@ -362,7 +363,12 @@ def call(method, path, data, content=None, rawContent=None, options={}, tryCount
   bkey.extend(map(ord, apikey))
   data['signature'] = hmac.new(bkey, signed, hashlib.sha1).hexdigest()
     #print("signed: " + str(signed))
-  url = base_url+path+'?'+('&'.join(flatten(data)))
+  
+  flatdata_encoded = []
+  for item in flatten(data):
+    key, value = item.split('=', 1)
+    flatdata_encoded.append(urllib.parse.quote(key) + '=' + urllib.parse.quote(value))
+  url = base_url+path+'?'+('&'.join(flatdata_encoded))
   headers = {
     "Accept": "application/json",
     "Content-Type": content_type

Regards

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions