Skip to content

Commit e88eaa0

Browse files
authored
Update Permissions and Roles (#502)
2 parents b93cf11 + 1a0ae33 commit e88eaa0

27 files changed

+333
-338
lines changed

.env

-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ AIBUILDER_API_TOKEN=""
2828
ES_USER=elastic
2929
ES_PASSWORD=changeme
3030
ES_DISCOVERY_TYPE=single-node
31-
ES_ROLE="edit_aiod_resources"
3231
ES_JAVA_OPTS="-Xmx256m -Xms256m"
3332
AIOD_ES_HTTP_PORT=9200
3433
AIOD_ES_TRANSPORT_PORT=9300

data/keycloak/data/import/aiod-realm.json

+3-11
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,9 @@
7575
"clientRole" : false,
7676
"containerId" : "3df7e07d-ebbd-41c4-bc0c-1ba0e1a40ac5",
7777
"attributes" : { }
78-
}, {
79-
"id" : "18c2dc78-1119-4432-970b-3187f49bfa58",
80-
"name" : "edit_aiod_resources",
81-
"description" : "",
82-
"composite" : false,
83-
"clientRole" : false,
84-
"containerId" : "3df7e07d-ebbd-41c4-bc0c-1ba0e1a40ac5",
85-
"attributes" : { }
8678
}, {
8779
"id" : "2acb73ad-574d-4b77-b5dc-0fa9fd2ec8d7",
88-
"name" : "${REVIEWER_ROLE_NAME}",
80+
"name" : "review_aiod_resources",
8981
"description" : "Can review submitted AIoD resources",
9082
"composite" : false,
9183
"clientRole" : false,
@@ -1232,7 +1224,7 @@
12321224
"subType" : "authenticated",
12331225
"subComponents" : { },
12341226
"config" : {
1235-
"allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-full-name-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper", "oidc-address-mapper" ]
1227+
"allowed-protocol-mapper-types" : [ "oidc-full-name-mapper", "saml-user-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "saml-role-list-mapper", "saml-user-property-mapper" ]
12361228
}
12371229
}, {
12381230
"id" : "10f8b9b2-1038-4c98-b7a5-a9ac88fed69e",
@@ -1274,7 +1266,7 @@
12741266
"subType" : "anonymous",
12751267
"subComponents" : { },
12761268
"config" : {
1277-
"allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper", "oidc-address-mapper", "oidc-usermodel-attribute-mapper", "oidc-full-name-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-role-list-mapper" ]
1269+
"allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "saml-role-list-mapper", "oidc-full-name-mapper", "oidc-sha256-pairwise-sub-mapper" ]
12781270
}
12791271
}, {
12801272
"id" : "1d21f027-e0ae-4b80-b95e-f21d9426f115",

data/keycloak/data/import/aiod-users-0.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
} ],
5757
"disableableCredentialTypes" : [ ],
5858
"requiredActions" : [ ],
59-
"realmRoles" : [ "default-roles-aiod", "edit_aiod_resources" ],
59+
"realmRoles" : [ "default-roles-aiod"],
6060
"notBefore" : 0,
6161
"groups" : [ ]
6262
} ]

docs/developer/authentication.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ For authentication, we use a [keycloak](https://www.keycloak.org) service.
44
For development, make sure to use the `USE_LOCAL_DEV=true` environment variable so that the local
55
keycloak server is configured with default users:
66

7-
| User | Password | Role(s) | Comment |
8-
|------|----------|----------------------------------------------------------------------------|---------|
9-
| user | password | edit_aiod_resources, default-roles-aiod, offline_access, uma_authorization | |
7+
| User | Password | Role(s) | Comment |
8+
|------|----------|-------------------------------------------------------|---------|
9+
| user | password | default-roles-aiod, offline_access, uma_authorization | |
1010

1111
For a description of the roles, see ["AIoD Keycloak Roles"](../hosting/authentication.md#roles).
1212
With the local development configuration, you will only be able to authenticate with keycloak users (OAuth2, password) not by other means.

docs/developer/users.md

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ The user model in AIoD allows users to maintain and share ownership over assets,
44
and to allow a review process for new assets.
55
The components of this user model are defined in [src/database/authorization.py](https://github.com/aiondemand/AIOD-rest-api/blob/develop/src/database/authorization.py)
66
and [src/database/review.py](https://github.com/aiondemand/AIOD-rest-api/blob/develop/src/database/review.py).
7+
Note that for special users (e.g., administrators), this user model may be circumvented through
8+
special Keycloak roles (for more information, see ["Roles"](../hosting/authentication.md)).
79

810
[//]: # (Add a diagram overview once the model is more final, e.g., groups are added)
911

docs/hosting/authentication.md

+10-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,16 @@ Create both a private and a public client in the external provider (this step is
3838
## Roles
3939

4040
Roles identify a type or category of user and determine their access and permissions within applications.
41-
Currently, only the ` edit_aiod_resources` role is defined, granting users the ability to upload and edit resources.
41+
Roles are generally only necessary for special cases.
42+
The normal flow for granting individual users permissions for individual assets is detailed in the ["user model"](../developer/users.md) documentation.
43+
These are the roles the metadata catalogue uses (`*` in a role indicates its defined for each asset type individually):
44+
45+
* `review_aiod_resources`: identifies the user as having permission to view asset submissions and review them.
46+
* `read_*`: allows the user read access to all assets on the platform, regardless of the asset-specific permissions.
47+
* `update_*`: allows the user update permission for all assets on the platform, regardless of the asset-specific permissions.
48+
* `delete_*`: allows the user delete permission for all assets on the platform, regardless of the asset-specific permissions.
49+
* `create_platforms`: allows the user to define new platforms.
50+
4251
Note that roles may be used for services other than the metadata catalogue.
4352
New roles can be created from the admin console, see ["Creating a realm role"](https://www.keycloak.org/docs/latest/server_admin/index.html#proc-creating-realm-roles_server_administration_guide).
4453

src/authentication.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,23 @@
3535
oidc = OpenIdConnect(openIdConnectUrl=KEYCLOAK_CONFIG.get("openid_connect_url"), auto_error=False)
3636

3737

38+
REVIEWER_ROLE = os.getenv("REVIEWER_ROLE_NAME")
3839
client_secret = os.getenv("KEYCLOAK_CLIENT_SECRET")
40+
3941
keycloak_openid = KeycloakOpenID(
4042
server_url=KEYCLOAK_CONFIG.get("server_url"),
4143
client_id=KEYCLOAK_CONFIG.get("client_id"),
4244
client_secret_key=client_secret,
4345
realm_name=KEYCLOAK_CONFIG.get("realm"),
4446
verify=True,
4547
)
46-
_REVIEWER_ROLE = os.getenv("REVIEWER_ROLE_NAME")
48+
49+
50+
def assert_required_settings_configured() -> None:
51+
# These variables are required for the API to function, so we provide context beyond KeyError.
52+
# Should be managed together with other settings in the future (#67)
53+
assert REVIEWER_ROLE, "Environment variable 'REVIEWER_ROLE_NAME' not set." # noqa: S101
54+
assert client_secret, "Environment variable 'KEYCLOAK_CLIENT_SECRET' not set." # noqa: S101
4755

4856

4957
@dataclasses.dataclass
@@ -60,8 +68,7 @@ def has_any_role(self, *roles: str) -> bool:
6068

6169
@property
6270
def is_reviewer(self):
63-
assert _REVIEWER_ROLE is not None, "Must configure role `reviewer` in config.toml file." # noqa: S101
64-
return _REVIEWER_ROLE in self.roles
71+
return REVIEWER_ROLE in self.roles
6572

6673

6774
async def _get_user(token) -> KeycloakUser:

src/config.default.toml

-1
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,3 @@ client_id = "aiod-api" # a private client, used by the backend
2323
client_id_swagger = "aiod-api-swagger" # a public client, used by the Swagger Frontend
2424
openid_connect_url = "http://localhost/aiod-auth/realms/aiod/.well-known/openid-configuration"
2525
scopes = "openid profile roles"
26-
role = "edit_aiod_resources"

src/main.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from fastapi.responses import HTMLResponse
1515
from sqlmodel import select, SQLModel
1616

17-
from authentication import get_user_or_raise, KeycloakUser
17+
from authentication import get_user_or_raise, KeycloakUser, assert_required_settings_configured
1818
from config import KEYCLOAK_CONFIG
1919
from database.deletion.triggers import create_delete_triggers
2020
import database.authorization # noqa # Trigger registration of User, Permission -> likely obsolete when couple with aiod_entry is done
@@ -110,6 +110,7 @@ def create_app() -> FastAPI:
110110
"""Create the FastAPI application, complete with routes."""
111111
setup_logger()
112112
args = _parse_args()
113+
assert_required_settings_configured()
113114
if args.build_db == "never":
114115
if not database_exists():
115116
logging.warning(

src/routers/resource_router.py

+18-28
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
set_permission,
2121
register_user,
2222
PermissionType,
23+
user_can_write,
2324
)
2425
from database.model.ai_resource.resource import AIResource
2526
from database.model.concept.aiod_entry import AIoDEntryORM, EntryStatus
@@ -404,15 +405,6 @@ def register_resource(
404405
resource_create: clz_create, # type: ignore
405406
user: KeycloakUser = Depends(get_user_or_raise),
406407
):
407-
if not user.has_any_role(
408-
KEYCLOAK_CONFIG.get("role"),
409-
f"create_{self.resource_name_plural}",
410-
f"crud_{self.resource_name_plural}",
411-
):
412-
raise HTTPException(
413-
status_code=status.HTTP_403_FORBIDDEN,
414-
detail=f"You do not have permission to create {self.resource_name_plural}.",
415-
)
416408
try:
417409
with DbSession() as session:
418410
try:
@@ -453,19 +445,18 @@ def put_resource(
453445
resource_create_instance: clz_create, # type: ignore
454446
user: KeycloakUser = Depends(get_user_or_raise),
455447
):
456-
if not user.has_any_role(
457-
KEYCLOAK_CONFIG.get("role"),
458-
f"update_{self.resource_name_plural}",
459-
f"crud_{self.resource_name_plural}",
460-
):
461-
raise HTTPException(
462-
status_code=status.HTTP_403_FORBIDDEN,
463-
detail=f"You do not have permission to edit {self.resource_name_plural}.",
464-
)
465-
466448
with DbSession() as session:
467449
try:
468450
resource: Any = self._retrieve_resource(session, identifier)
451+
if not (
452+
user_can_write(user, resource.aiod_entry)
453+
or user.has_role(f"update_{self.resource_name_plural}")
454+
):
455+
raise HTTPException(
456+
status_code=status.HTTP_403_FORBIDDEN,
457+
detail=f"You do not have permission to edit {self.resource_name_plural}.",
458+
)
459+
469460
if resource.aiod_entry.status == EntryStatus.SUBMITTED:
470461
raise HTTPException(
471462
status_code=status.HTTP_403_FORBIDDEN,
@@ -503,18 +494,17 @@ def delete_resource(
503494
user: KeycloakUser = Depends(get_user_or_raise),
504495
):
505496
with DbSession() as session:
506-
if not user.has_any_role(
507-
KEYCLOAK_CONFIG.get("role"),
508-
f"delete_{self.resource_name_plural}",
509-
f"crud_{self.resource_name_plural}",
510-
):
511-
raise HTTPException(
512-
status_code=status.HTTP_403_FORBIDDEN,
513-
detail=f"You do not have permission to delete {self.resource_name_plural}.",
514-
)
515497
try:
516498
# Raise error if it does not exist
517499
resource: Any = self._retrieve_resource(session, identifier)
500+
if not (
501+
user_can_administer(user, resource.aiod_entry)
502+
or user.has_role(f"delete_{self.resource_name_plural}")
503+
):
504+
raise HTTPException(
505+
status_code=status.HTTP_403_FORBIDDEN,
506+
detail=f"You do not have permission to delete {self.resource_name_plural}.",
507+
)
518508
if (
519509
hasattr(self.resource_class, "__deletion_config__")
520510
and not self.resource_class.__deletion_config__["soft_delete"]

src/routers/resource_routers/platform_router.py

+3-16
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from sqlmodel import SQLModel, Session, select
66

77
from authentication import KeycloakUser, get_user_or_raise
8-
from config import KEYCLOAK_CONFIG
98
from database.model.platform.platform import Platform
109
from database.model.resource_read_and_create import resource_create, resource_read
1110
from database.model.serializers import deserialize_resource_relationships
@@ -178,11 +177,7 @@ def register_resource(
178177
resource_create: clz_create, # type: ignore
179178
user: KeycloakUser = Depends(get_user_or_raise),
180179
):
181-
if not user.has_any_role(
182-
KEYCLOAK_CONFIG.get("role"),
183-
f"create_{self.resource_name_plural}",
184-
f"crud_{self.resource_name_plural}",
185-
):
180+
if not user.has_role("create_platforms"):
186181
raise HTTPException(
187182
status_code=status.HTTP_403_FORBIDDEN,
188183
detail=f"You do not have permission to create {self.resource_name_plural}.",
@@ -222,11 +217,7 @@ def put_resource(
222217
resource_create_instance: clz_create, # type: ignore
223218
user: KeycloakUser = Depends(get_user_or_raise),
224219
):
225-
if not user.has_any_role(
226-
KEYCLOAK_CONFIG.get("role"),
227-
f"update_{self.resource_name_plural}",
228-
f"crud_{self.resource_name_plural}",
229-
):
220+
if not user.has_role("update_platforms"):
230221
raise HTTPException(
231222
status_code=status.HTTP_403_FORBIDDEN,
232223
detail=f"You do not have permission to edit {self.resource_name_plural}.",
@@ -265,11 +256,7 @@ def delete_resource(
265256
user: KeycloakUser = Depends(get_user_or_raise),
266257
):
267258
with DbSession() as session:
268-
if not user.has_any_role(
269-
KEYCLOAK_CONFIG.get("role"),
270-
f"delete_{self.resource_name_plural}",
271-
f"crud_{self.resource_name_plural}",
272-
):
259+
if not user.has_role("delete_platforms"):
273260
raise HTTPException(
274261
status_code=status.HTTP_403_FORBIDDEN,
275262
detail=f"You do not have permission to delete {self.resource_name_plural}.",

0 commit comments

Comments
 (0)