Act as Another Principal
Every request a client sends carries one bearer token, and the token is the principal: it decides what the request may do and who a write is attributed to. Most programs need one. Some need several at once:
- a service that writes on behalf of the user who made the request;
- an agent that should only ever read the repository it was given;
- a job that holds a broad token and hands a narrow one to the code it runs.
You could construct a second WarmHubClient, but that opens a second
connection pool. client.with_access_token(token) returns a client that
authenticates as token and shares the original client’s pool.
Derive a client
Section titled “Derive a client”from warmhub import WarmHubClient
client = WarmHubClient.from_env()
reader = client.with_access_token(other_token)row = reader.repository("acme/sensors").things.get("probe-1")The call makes no request. The derived client copies the parent’s backend
URL, client identity, and client flags, and takes the new token. Everything
else about it is an ordinary client, with the same namespaces and repository
handles. AsyncWarmHubClient.with_access_token works the same way.
Example: a read-only token for one repository
Section titled “Example: a read-only token for one repository”In production, the second token usually comes from elsewhere: the user you are acting for, a secret store, or your own identity provider. Here the parent client mints it, so the example needs nothing but the token you started with.
client.token.create takes the request as a mapping. A scope entry binds a
resource (org/repo) to permissions; this one grants repo:read on a single
repository. Scopes only narrow: a token never holds more than the user who
created it. See Personal access tokens for
every permission.
from warmhub import Add, WarmHubClient, WarmHubError
client = WarmHubClient.from_env()sensors = client.repository("acme/sensors")
minted = client.token.create( { "name": "sensors-reader", "scopes": [{"resource": "acme/sensors", "permissions": ["repo:read"]}], })
reader = client.with_access_token(minted.token)as_reader = reader.repository("acme/sensors")print(as_reader.things.get("probe-1").data)minted.token is the only time the token value is returned. token.list and
token.get describe a token afterwards but never return its value.
The server enforces the scope
Section titled “The server enforces the scope”The derived client does not know what its token allows. It sends the request
and the server decides. A write with the read-only token is refused with
FORBIDDEN:
op = Add(name="probe-2", shapes=["Reading"], data={"temp_celsius": 19.8, "bay_id": "bay-a"})
try: as_reader.apply("write as the reader", [op])except WarmHubError as err: print(err.code, err) # FORBIDDEN Missing scope: repo:write
sensors.apply("write as the parent", [op]) # succeedsBranch on err.code, not on the message text. The same write through the
parent succeeds: each request runs as exactly the principal you sent it with.
Tokens that change: pass a callable
Section titled “Tokens that change: pass a callable”with_access_token — and the access_token= constructor argument — also
accept a zero-argument callable instead of a string. The client calls it
before every request, which suits a token that rotates: a short-lived token
refreshed from a secret store, or the current user in a request handler.
def current_token() -> str: return token_store.get("sensors-reader")
rotating = client.with_access_token(current_token)WarmHubClient takes a callable that returns a string. AsyncWarmHubClient
also accepts one that returns an awaitable.
Who owns the connection pool
Section titled “Who owns the connection pool”The parent owns the pool; derived clients borrow it.
client.owns_transportisTrue;reader.owns_transportisFalse.- Closing a derived client does not close the pool. The parent keeps working.
- Closing the parent closes the pool, and every client derived from it then fails. Close derived clients first, or keep the parent open for as long as they are in use.
reader.close()sensors.things.get("probe-1") # the parent still worksRevoke what you mint
Section titled “Revoke what you mint”A token you create for one task is a credential you are responsible for.
Revoke it by name when the task ends. Any later request that presents it is
refused with UNAUTHENTICATED.
client.token.revoke(minted.name)Related
Section titled “Related”- Agent identities covers acting as an agent persona.
- Access reference lists what each permission grants.
WarmHubClientreferenceclient.tokenreference