From 488167f8d71e679373999633f6b875283e933e3e Mon Sep 17 00:00:00 2001 From: 0weng Date: Wed, 11 Dec 2024 14:47:02 -0800 Subject: [PATCH] Add JSON schema and validation for `implied role` Change-Id: I6bdd351a177b6c36e3f205da3c81fea218bd9039 --- keystone/api/role_inferences.py | 6 ++ keystone/api/roles.py | 10 ++++ keystone/assignment/schema.py | 103 ++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+) diff --git a/keystone/api/role_inferences.py b/keystone/api/role_inferences.py index b4ed9371fc..81d9b44715 100644 --- a/keystone/api/role_inferences.py +++ b/keystone/api/role_inferences.py @@ -15,6 +15,8 @@ import flask_restful from keystone.api._shared import implied_roles as shared +from keystone.api import validation +from keystone.assignment import schema from keystone.common import provider_api from keystone.common import rbac_enforcer from keystone.server import flask as ks_flask @@ -24,6 +26,10 @@ PROVIDERS = provider_api.ProviderAPIs class RoleInferencesResource(flask_restful.Resource): + @validation.request_body_schema(None) + @validation.response_body_schema( + schema.role_inferences_index_response_body + ) def get(self): """List role inference rules. diff --git a/keystone/api/roles.py b/keystone/api/roles.py index edf0ac6e96..c4ae333ad6 100644 --- a/keystone/api/roles.py +++ b/keystone/api/roles.py @@ -218,6 +218,8 @@ def _build_enforcement_target_ref(): class RoleImplicationListResource(flask_restful.Resource): + @validation.request_body_schema(None) + @validation.response_body_schema(schema.role_inference_show_response_body) def get(self, prior_role_id): """List Implied Roles. @@ -243,6 +245,8 @@ class RoleImplicationListResource(flask_restful.Resource): class RoleImplicationResource(flask_restful.Resource): + @validation.request_body_schema(None) + @validation.response_body_schema(None) def head(self, prior_role_id, implied_role_id=None): # TODO(morgan): deprecate "check_implied_role" policy, as a user must # have both check_implied_role and get_implied_role to use the head @@ -261,6 +265,8 @@ class RoleImplicationResource(flask_restful.Resource): # here is incorrect. It is maintained as is for API contract reasons. return None, http.client.NO_CONTENT + @validation.request_body_schema(None) + @validation.response_body_schema(schema.implied_role_show_response_body) def get(self, prior_role_id, implied_role_id): """Get implied role. @@ -287,6 +293,8 @@ class RoleImplicationResource(flask_restful.Resource): } return response_json + @validation.request_body_schema(None) + @validation.response_body_schema(schema.implied_role_show_response_body) def put(self, prior_role_id, implied_role_id): """Create implied role. @@ -300,6 +308,8 @@ class RoleImplicationResource(flask_restful.Resource): response_json = self._get_implied_role(prior_role_id, implied_role_id) return response_json, http.client.CREATED + @validation.request_body_schema(None) + @validation.response_body_schema(None) def delete(self, prior_role_id, implied_role_id): """Delete implied role. diff --git a/keystone/assignment/schema.py b/keystone/assignment/schema.py index 60292130ad..77e42bcd5b 100644 --- a/keystone/assignment/schema.py +++ b/keystone/assignment/schema.py @@ -120,3 +120,106 @@ role_update_request_body = { "required": ["role"], "additionalProperties": False, } + +# Individual properties of a returned prior/implied role +_implied_role_properties: dict[str, Any] = { + "id": { + "type": "string", + "format": "uuid", + "description": "The role ID.", + "readOnly": True, + }, + "links": response_types.resource_links, + "name": parameter_types.name, +} + +# Common schema of prior role +prior_role_schema: dict[str, Any] = { + "type": "object", + "description": "A prior role object.", + "properties": _implied_role_properties, + "additionalProperties": False, +} + +# Common schema of implied role +implied_role_schema: dict[str, Any] = { + "type": "object", + "description": "An implied role object.", + "properties": _implied_role_properties, + "additionalProperties": False, +} + +# Response body of API operations returning a single implied role +# `GET /v3/roles/{prior_role_id}/implies/{implies_role_id}` +# and `PUT /v3/roles/{prior_role_id}/implies/{implies_role_id}` +implied_role_show_response_body: dict[str, Any] = { + "type": "object", + "properties": { + "role_inference": { + "type": "object", + "description": ( + "Role inference object that contains " + "prior_role object and implies object." + ), + "properties": { + "prior_role": prior_role_schema, + "implies": implied_role_schema, + }, + "additionalProperties": False, + }, + "links": response_types.links, + }, + "additionalProperties": False, +} + +# Response body of the `GET /v3/roles/{prior_role_id}/implies` API operation +# returning a single role inference +role_inference_show_response_body: dict[str, Any] = { + "type": "object", + "properties": { + "role_inference": { + "type": "object", + "description": "A role inference object.", + "properties": { + "prior_role": prior_role_schema, + "implies": { + "type": "array", + "items": implied_role_schema, + "description": "A list of implied role objects.", + }, + }, + "additionalProperties": False, + }, + "links": response_types.links, + }, + "additionalProperties": False, +} + +# Response body of the `GET /v3/role_inferences` API operation +# returning a list of role inferences +role_inferences_index_response_body: dict[str, Any] = { + "type": "object", + "properties": { + "role_inferences": { + "type": "array", + "description": "A list of role inference objects.", + "items": { + "type": "object", + "properties": { + # NOTE(0weng): The example in the docs incorrectly + # includes the `description` field in the output. + "prior_role": prior_role_schema, + "implies": { + "type": "array", + "items": implied_role_schema, + "description": "A list of implied role objects.", + }, + }, + "additionalProperties": False, + }, + "additionalProperties": False, + }, + "links": response_types.links, + }, + "additionalProperties": False, +}