Skip to content

Commit b4e9b64

Browse files
nmaytanjmaruland
authored andcommitted
Split apikeys scope into create and revoke (#1250)
* Split apikeys scope into create and delete * delete->revoke for apikeys scope verb * Migration should use different scopes for roles
1 parent 6ff87e7 commit b4e9b64

File tree

10 files changed

+103
-19
lines changed

10 files changed

+103
-19
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ Write the date in place of the "Unreleased" in the case a new version is release
55

66
## Unreleased
77

8+
### Changed
9+
- Rename "create" scope to the more explicit "create:node"
10+
- Split "apikeys" scope into "create:apikeys" and "revoke:apikeys" scopes
11+
812
### Fixed
913

1014
- Made provision for forks of the repository to publish Helm charts.

docs/source/reference/scopes.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ with restricted scopes.
88

99
* `read:metadata` --- List and search metadata.
1010
* `read:data` --- Fetch (array, table) data.
11-
* `create` --- Create a new node.
11+
* `create:node` --- Create a new node.
1212
* `write:metadata` --- Write metadata.
1313
* `write:data` --- Write (array, table) data.
1414
* `delete:revision` --- Delete metadata revisions
1515
* `delete:node` --- Delete a node
16-
* `apikeys` --- Manage API keys for the currently-authenticated user or service.
16+
* `create:apikeys` --- Create API keys for the currently-authenticated user or service.
17+
* `revoke:apikeys` --- Revoke API keys for the currently-authenticated user or service.
1718
* `metrics` --- Access Prometheus metrics.
1819
* `admin:apikeys` --- Manage API keys on behalf of any user or service.
1920
* `read:principals` --- Read list of all users and services and their attributes.
@@ -27,7 +28,7 @@ access time.
2728
An authenticated entity ("Principal") may be assigned roles that confer a list
2829
of scopes.
2930

30-
* `user` --- default role, granted scopes `["read:metadata", "read:data", "write:metadata", "write:data", "create", "apikeys"]`
31+
* `user` --- default role, granted scopes `["read:metadata", "read:data", "write:metadata", "write:data", "create:node", "create:apikeys", "revoke:apikeys"]`
3132
* `admin` --- granted all scopes
3233

3334
There is support for custom roles at the database level, but neither role

example_configs/access_tags/tag_definitions.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ roles:
55
scopes: ["read:data", "read:metadata",
66
"write:data", "write:metadata",
77
"delete:node", "delete:revision",
8-
"create", "register"]
8+
"create:node", "register"]
99
tags:
1010
data_A:
1111
groups:

example_configs/toy_authentication.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ access_control:
2323
- "write:data"
2424
- "delete:revision"
2525
- "delete:node"
26-
- "create"
26+
- "create:node"
2727
- "register"
2828
tags_db:
2929
uri: "file:example_configs/access_tags/compiled_tags.sqlite"

tiled/_tests/test_access_control.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
"write:metadata",
6969
"delete:node",
7070
"delete:revision",
71-
"create",
71+
"create:node",
7272
"register",
7373
]
7474
},

tiled/access_control/scopes.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55
"write:data": {"description": "Write data."},
66
"delete:revision": {"description": "Delete metadata revisions."},
77
"delete:node": {"description": "Delete a node."},
8-
"create": {"description": "Add a node."},
8+
"create:node": {"description": "Add a node."},
99
"register": {"description": "Register externally-managed assets."},
1010
"metrics": {"description": "Access (Prometheus) metrics."},
11-
"apikeys": {
12-
"description": "Create and revoke API keys as the currently-authenticated user or service."
11+
"create:apikeys": {
12+
"description": "Create API keys as the currently-authenticated user or service."
13+
},
14+
"revoke:apikeys": {
15+
"description": "Revoke API keys as the currently-authenticated user or service."
1316
},
1417
"admin:apikeys": {
1518
"description": "Create and revoke API keys on behalf of any user or service."
@@ -32,7 +35,7 @@
3235
"write:data",
3336
"delete:revision",
3437
"delete:node",
35-
"create",
38+
"create:node",
3639
"register",
3740
"metrics",
3841
)

tiled/authn_database/core.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
# This is list of all valid alembic revisions (from current to oldest).
1515
ALL_REVISIONS = [
16+
"d829476bc173",
1617
"27e069ba3bf5",
1718
"a806cc635ab2",
1819
"0c705a02954c",
@@ -36,12 +37,13 @@ async def create_default_roles(db):
3637
scopes=[
3738
"read:metadata",
3839
"read:data",
39-
"create",
40+
"create:node",
4041
"write:metadata",
4142
"write:data",
4243
"delete:revision",
4344
"delete:node",
44-
"apikeys",
45+
"create:apikeys",
46+
"revoke:apikeys",
4547
],
4648
),
4749
Role(
@@ -50,7 +52,7 @@ async def create_default_roles(db):
5052
scopes=[
5153
"read:metadata",
5254
"read:data",
53-
"create",
55+
"create:node",
5456
"register",
5557
"write:metadata",
5658
"write:data",
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"""Split apikeys scope into create and delete
2+
3+
Revision ID: d829476bc173
4+
Revises: 27e069ba3bf5
5+
Create Date: 2025-12-08 17:09:18.062287
6+
7+
"""
8+
from alembic import op
9+
from sqlalchemy.orm.session import Session
10+
11+
from tiled.authn_database.orm import Role
12+
13+
# revision identifiers, used by Alembic.
14+
revision = "d829476bc173"
15+
down_revision = "27e069ba3bf5"
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
ROLES = ["admin", "user"]
21+
NEW_SCOPES_USER = ["create:apikeys", "revoke:apikeys", "create:node"]
22+
OLD_SCOPES_USER = ["apikeys", "create"]
23+
NEW_SCOPES_ADMIN = ["create:node"]
24+
OLD_SCOPES_ADMIN = ["create"]
25+
26+
27+
def upgrade():
28+
"""
29+
Add new scopes to Roles.
30+
Remove old scopes from Roles, if present.
31+
"""
32+
connection = op.get_bind()
33+
with Session(bind=connection) as db:
34+
for role_name in ROLES:
35+
role = db.query(Role).filter(Role.name == role_name).first()
36+
scopes = role.scopes.copy()
37+
if role_name == "admin":
38+
NEW_SCOPES = NEW_SCOPES_ADMIN
39+
OLD_SCOPES = OLD_SCOPES_ADMIN
40+
else:
41+
NEW_SCOPES = NEW_SCOPES_USER
42+
OLD_SCOPES = OLD_SCOPES_USER
43+
for scope in OLD_SCOPES:
44+
if scope in scopes:
45+
scopes.remove(scope)
46+
scopes.extend(NEW_SCOPES)
47+
role.scopes = scopes
48+
db.commit()
49+
50+
51+
def downgrade():
52+
"""
53+
Remove new scopes from Roles, if present.
54+
Add old scopes to Roles, if not preesent.
55+
"""
56+
connection = op.get_bind()
57+
with Session(bind=connection) as db:
58+
for role_name in ROLES:
59+
role = db.query(Role).filter(Role.name == role_name).first()
60+
scopes = role.scopes.copy()
61+
if role_name == "admin":
62+
NEW_SCOPES = NEW_SCOPES_ADMIN
63+
OLD_SCOPES = OLD_SCOPES_ADMIN
64+
else:
65+
NEW_SCOPES = NEW_SCOPES_USER
66+
OLD_SCOPES = OLD_SCOPES_USER
67+
for scope in NEW_SCOPES:
68+
if scope in scopes:
69+
scopes.remove(scope)
70+
for scope in OLD_SCOPES:
71+
if scope not in scopes:
72+
scopes.append(scope)
73+
role.scopes = scopes
74+
db.commit()

tiled/server/authentication.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1357,7 +1357,7 @@ async def new_apikey(
13571357
request: Request,
13581358
apikey_params: schemas.APIKeyRequestParams,
13591359
principal: Optional[schemas.Principal] = Depends(get_current_principal),
1360-
_=Security(check_scopes, scopes=["apikeys"]),
1360+
_=Security(check_scopes, scopes=["create:apikeys"]),
13611361
db_factory: Callable[[], Optional[AsyncSession]] = Depends(
13621362
get_database_session_factory
13631363
),
@@ -1422,7 +1422,7 @@ async def revoke_apikey(
14221422
request: Request,
14231423
first_eight: str,
14241424
principal: Optional[schemas.Principal] = Depends(get_current_principal),
1425-
_=Security(check_scopes, scopes=["apikeys"]),
1425+
_=Security(check_scopes, scopes=["revoke:apikeys"]),
14261426
db_factory: Callable[[], Optional[AsyncSession]] = Depends(
14271427
get_database_session_factory
14281428
),

tiled/server/router.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1477,11 +1477,11 @@ async def post_metadata(
14771477
authn_scopes: Scopes = Depends(get_current_scopes),
14781478
root_tree=Depends(get_root_tree),
14791479
session_state: dict = Depends(get_session_state),
1480-
_=Security(check_scopes, scopes=["write:metadata", "create"]),
1480+
_=Security(check_scopes, scopes=["write:metadata", "create:node"]),
14811481
):
14821482
entry = await get_entry(
14831483
path,
1484-
["write:metadata", "create"],
1484+
["write:metadata", "create:node"],
14851485
principal,
14861486
authn_access_tags,
14871487
authn_scopes,
@@ -1524,11 +1524,11 @@ async def post_register(
15241524
authn_scopes: Scopes = Depends(get_current_scopes),
15251525
root_tree=Depends(get_root_tree),
15261526
session_state: dict = Depends(get_session_state),
1527-
_=Security(check_scopes, scopes=["write:metadata", "create", "register"]),
1527+
_=Security(check_scopes, scopes=["write:metadata", "create:node", "register"]),
15281528
):
15291529
entry = await get_entry(
15301530
path,
1531-
["write:metadata", "create", "register"],
1531+
["write:metadata", "create:node", "register"],
15321532
principal,
15331533
authn_access_tags,
15341534
authn_scopes,

0 commit comments

Comments
 (0)