I found out that editing a full_description of a DockerHub repository can be done via a JavaScript API, and figured this would be a fun excuse to learn the requests package for python. The JavaScript API definitely works, e.g. using this simple docker image.
The JS API basically does
- Send a
POSTrequest tohttps://hub.docker.com/v2/users/loginwith the username and password. The server responds with atoken. - Send a
PATCHrequest to the specifichttps://hub.docker.com/v2/repositories/{user or org}/{repo}, making sure the header hasAuthorization: JWT {token}, and in this case with content body of{"full_description":"...value..."}.
What is troubling is that the PATCH request on the python side gets a 200 response back from the server (if you intentionally set a bad auth token, you get denied as expected). But it's response actually contains the current information (not the patched info).
The only "discoveries" I've made:
If you add the debug logging stuff, there's a 301. But this is the same URL for the javascript side, so it doesn't matter?
send: b'{"full_description": "TEST"}' reply: 'HTTP/1.1 301 MOVED PERMANENTLY\r\n'The token received by doing aThis is not true. They are different, I don't know how I thought they were the same.POSTinrequestsis the same as if IGETtoauth.docker.ioas decribed in Getting a Bearer Token section here. Notably, I didn't specify a password (just didcurl -X GET ...).
This second one makes me feel like I'm missing a step. Like I need to decode the token or something? I don't know what else to make of this, especially the 200 response from the PATCH despite no changes.
The code:
import json
from textwrap import indent
import requests
if __name__ == "__main__":
username = "<< SET THIS VALUE >>"
password = "<< SET THIS VALUE >>"
repo = "<< SET THIS VALUE >>"
base_url = "https://hub.docker.com/v2"
login_url = f"{base_url}/users/login"
repo_url = f"{base_url}/repositories/{username}/{repo}"
# NOTE: if I use a `with requests.Session()`, then I'll get
# CSRF Failed: CSRF token missing or incorrect
# Because I think that csrftoken is only valid for login page (?)
# Get login token and create authorization header
print("==> Logging into DockerHub")
tok_req = requests.post(login_url, json={"username": username, "password": password})
token = tok_req.json()["token"]
headers = {"Authorization": f"JWT {token}"}
print(f"==> Sending PATCH request to {repo_url}")
payload = {"full_description": "TEST"}
patch_req = requests.patch(repo_url, headers=headers, json=payload)
print(f" Response (status code: {patch_req.status_code}):")
print(indent(json.dumps(patch_req.json(), indent=2), " "))
token? I'm not sure if I remember correctly but in python sometimes stuff gets converted to bytestring and all of a sudden it doesn't work anymore.type(token)isstr, but I'll dig in on that a little more. It could be that there's some knobs I can turn on howrequestsor maybeurllib3actually decodes the transmitted response.Getting a bearer tokenlink you posted seems to tell that you need to encode the reply to get a functional JWT but I'm not sure without knowing what the reply on the request is.Can you also confirm that the token you get is an actual JWT?I have no idea, but I'm afraid to post the response that comes from giving my username / password ;) I'm playing with thejwtmodule, I think I either need to encode or decode something like you say. I wish I could help you help me, but I don't know what I don't know x0 I appreciate you offering suggestions though, I'll keep grinding :)200 OKeach time.