tempestweb.access¶
Mapa papel → permissão e leitura de claims do token, para a view decidir o que desenhar. Modos A e B (o Modo C recusa o import no build). Não é autorização: o servidor decide se a requisição pode acontecer; isto decide se o botão é desenhado.
Guia com exemplos: Permissões na view.
tempestweb.access ¶
Deciding what the view draws, from the permissions a user carries.
The tempest-fastapi-sdk validates the token and the server decides what a
request may do. On the screen side there was nothing: an app that shows
"Delete user" only to admins spread if state.role == "admin" across the view,
and read the permission list out of the JWT with json.loads in some corner.
Two pieces close that, and neither of them is authorization:
- :func:
unverified_access_from_tokenreads roles, permissions and expiry off a JWT without checking the signature — there is nothing to check it with in the browser, and the server already did. - :class:
AccessControlholds the role → permission map once, and resolving it gives an :class:Accessthe view asks questions of.
Example
The view then asks the question where it draws:
if access.can("users:delete"): children.append(Button(...)). A full screen is
in the recipe.
Hiding a button is not access control
Everything here runs where the user can change it. A screen that draws no Delete button still sits in front of an endpoint that deletes, and reaching that endpoint takes a terminal, not an exploit. The pair is:
| Where | Decides | With |
|---|---|---|
Server (tempest-fastapi-sdk) |
whether the request may happen | the key |
| Here | whether the button is drawn | unverified claims |
If the server does not enforce it, it is not enforced.
Modes A and B only
Mode C transpiles the app's own Python into JavaScript and serves a fixed set
of modules — tempest_core, tempestweb.components and
tempestweb.native. Importing this package from a Mode C app is refused at
build time with a named error.
Import everything from this package level rather than from submodules.
ClaimNames
dataclass
¶
Which claims to read, for a server that names them differently.
Attributes:
| Name | Type | Description |
|---|---|---|
roles |
str
|
The claim holding role names. |
permissions |
str
|
The claim holding explicit permissions. |
scope |
str
|
The claim holding space-separated OAuth scopes. |
Source code in tempestweb/access/claims.py
TokenAccess
dataclass
¶
The access-related claims read off a token, unverified.
Attributes:
| Name | Type | Description |
|---|---|---|
roles |
tuple[str, ...]
|
The role names the token claims, in the order the claim listed them. |
permissions |
tuple[str, ...]
|
The permissions the token claims directly, including any OAuth scopes. |
expires_at |
float | None
|
The |
Source code in tempestweb/access/claims.py
is_expired ¶
Report whether the token's expiry has passed.
Reports rather than raises: an expired token is an ordinary state an app handles by refreshing, not an exceptional one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
now
|
float
|
The current UNIX time in seconds. Passed in rather than read from the clock so the caller owns the time source, and so a test pins expiry without freezing a clock. |
required |
leeway_seconds
|
float
|
Treat the token as expired this many seconds early, to absorb clock skew. |
0.0
|
Returns:
| Type | Description |
|---|---|
bool
|
|
bool
|
an expiry does not expire. |
Source code in tempestweb/access/claims.py
Access
dataclass
¶
What one user may do, already resolved.
Attributes:
| Name | Type | Description |
|---|---|---|
permissions |
frozenset[str]
|
The permissions the user carries, wildcards included. This
is the raw grant set, not an expansion of it — |
Source code in tempestweb/access/control.py
can ¶
Report whether the user carries a permission.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
permission
|
str
|
The permission to test, such as |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
bool
|
string is never granted. |
Source code in tempestweb/access/control.py
can_any ¶
Report whether the user carries at least one of the permissions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*permissions
|
str
|
The permissions to test. |
()
|
Returns:
| Type | Description |
|---|---|
bool
|
|
bool
|
asking for nothing grants nothing. |
Source code in tempestweb/access/control.py
can_all ¶
Report whether the user carries every one of the permissions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*permissions
|
str
|
The permissions to test. |
()
|
Returns:
| Type | Description |
|---|---|
bool
|
|
bool
|
empty requirement is satisfied, which keeps |
bool
|
|
Source code in tempestweb/access/control.py
AccessControl ¶
The role → permission map, in one place.
Example
control = AccessControl(roles={"editor": ["posts:*"]}) control.for_roles(["editor"]).can("posts:publish") True control.for_roles(["ghost"]).can("posts:publish") False
Source code in tempestweb/access/control.py
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | |
known_roles
property
¶
The role names this control knows about.
Returns:
| Type | Description |
|---|---|
frozenset[str]
|
Every key of the role map. |
for_permissions ¶
Resolve access from permissions the user carries directly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
permissions
|
Iterable[str]
|
The permission strings, wildcards allowed. |
required |
Returns:
| Type | Description |
|---|---|
Access
|
The resolved :class: |
Source code in tempestweb/access/control.py
for_roles ¶
Resolve access from role names, expanding each through the map.
A role this control does not know grants nothing, rather than raising:
the roles come from a server that may add one before the app models it,
and an app that crashes on a new role is worse than one that hides a
button. :attr:known_roles is there for a caller that wants to notice.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
roles
|
Iterable[str]
|
The role names the user holds. |
required |
Returns:
| Type | Description |
|---|---|
Access
|
The resolved :class: |
Access
|
role's grants. |
Source code in tempestweb/access/control.py
for_token ¶
Resolve access from a token's claims: roles expanded, plus direct.
This is the call an app makes. A token may carry roles, explicit
permissions, or both — the result is the union, so a user whose role
grants users:read and who additionally carries audit:read gets
both.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
access
|
TokenAccess
|
The claims read by
:func: |
required |
Returns:
| Type | Description |
|---|---|
Access
|
The resolved :class: |
Source code in tempestweb/access/control.py
unverified_access_from_token ¶
unverified_access_from_token(token: str, *, claims: ClaimNames = DEFAULT_CLAIM_NAMES) -> TokenAccess
Read roles, permissions and expiry off a JWT without verifying it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
A compact-serialization JWT ( |
required |
claims
|
ClaimNames
|
Which claims to read, for a server naming them differently. |
DEFAULT_CLAIM_NAMES
|
Returns:
| Name | Type | Description |
|---|---|---|
The |
TokenAccess
|
class: |
TokenAccess
|
empty tuples and |
|
TokenAccess
|
for a user with no roles, not an error. |
Raises:
| Type | Description |
|---|---|
JWTError
|
If the token is not three dot-separated segments, or its payload is not a JSON object. A wrong signature is not an error here: see the module's danger note. |