mirror of
https://opendev.org/openstack/keystone.git
synced 2026-01-16 23:14:51 +00:00
Merge "Separate user response and request schema"
This commit is contained in:
commit
f8539f7541
2 changed files with 229 additions and 100 deletions
|
|
@ -15,18 +15,18 @@ from typing import Any
|
|||
|
||||
from keystone.api.validation import parameter_types
|
||||
from keystone.api.validation import response_types
|
||||
from keystone.common import validation
|
||||
import keystone.conf
|
||||
from keystone.identity.backends import resource_options as ro
|
||||
from keystone.resource import schema as resource_schema
|
||||
|
||||
CONF = keystone.conf.CONF
|
||||
|
||||
|
||||
_identity_name: dict[str, Any] = {
|
||||
'type': 'string',
|
||||
'minLength': 1,
|
||||
'maxLength': 255,
|
||||
'pattern': r'[\S]+',
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 255,
|
||||
"pattern": r"[\S]+",
|
||||
}
|
||||
|
||||
# Schema for Identity v3 API
|
||||
|
|
@ -47,10 +47,10 @@ user_index_request_query: dict[str, Any] = {
|
|||
"password_expires_at": {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"Filter results based on which user passwords have "
|
||||
"expired. The query should include an operator and "
|
||||
"a timestamp with a colon (:) separating the two, for "
|
||||
"example: `password_expires_at={operator}:{timestamp}`\n"
|
||||
"Filter results based on which user passwords have expired. "
|
||||
"The query should include an operator and a timestamp with a "
|
||||
"colon (:) separating the two, for example: "
|
||||
"`password_expires_at={operator}:{timestamp}`\n"
|
||||
"Valid operators are: lt, lte, gt, gte, eq, and neq\n"
|
||||
" - lt: expiration time lower than the timestamp\n"
|
||||
" - lte: expiration time lower than or equal to the timestamp\n"
|
||||
|
|
@ -61,8 +61,8 @@ user_index_request_query: dict[str, Any] = {
|
|||
"Valid timestamps are of the form: `YYYY-MM-DDTHH:mm:ssZ`."
|
||||
"For example:"
|
||||
"`/v3/users?password_expires_at=lt:2016-12-08T22:02:00Z`\n"
|
||||
"The example would return a list of users whose password "
|
||||
"expired before the timestamp `(2016-12-08T22:02:00Z).`"
|
||||
"The example would return a list of users whose password expired"
|
||||
" before the timestamp `(2016-12-08T22:02:00Z).`"
|
||||
),
|
||||
},
|
||||
"protocol_id": {
|
||||
|
|
@ -87,54 +87,141 @@ user_index_request_query: dict[str, Any] = {
|
|||
}
|
||||
|
||||
_user_properties: dict[str, Any] = {
|
||||
'id': parameter_types.user_id,
|
||||
'default_project_id': validation.nullable(parameter_types.id_string),
|
||||
'description': validation.nullable(parameter_types.description),
|
||||
'domain_id': parameter_types.id_string,
|
||||
'enabled': parameter_types.boolean,
|
||||
'federated': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'idp_id': {'type': 'string'},
|
||||
'protocols': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'protocol_id': {'type': 'string'},
|
||||
'unique_id': {'type': 'string'},
|
||||
"default_project_id": {
|
||||
"type": ["string", "null"],
|
||||
"description": (
|
||||
"The ID of the default project for the user. A user’s default"
|
||||
" project must not be a domain. Setting this attribute does not"
|
||||
" grant any actual authorization on the project, and is merely"
|
||||
" provided for convenience. Therefore, the referenced project does"
|
||||
" not need to exist within the user domain. (Since v3.1) If the"
|
||||
" user does not have authorization to their default project, the"
|
||||
" default project is ignored at token creation. (Since v3.1)"
|
||||
" Additionally, if your default project is not valid, a token is"
|
||||
" issued without an explicit scope of authorization."
|
||||
),
|
||||
},
|
||||
"description": {
|
||||
"type": ["string", "null"],
|
||||
"description": "The description of the user resource.",
|
||||
},
|
||||
"domain_id": parameter_types.domain_id,
|
||||
"enabled": parameter_types.boolean,
|
||||
"federated": {
|
||||
"description": (
|
||||
"List of federated objects associated with a user. Each object in"
|
||||
" the list contains the idp_id and protocols. protocols is a list"
|
||||
" of objects, each of which contains protocol_id and unique_id of"
|
||||
" the protocol and user respectively."
|
||||
),
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"idp_id": {"type": "string"},
|
||||
"protocols": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"protocol_id": {"type": "string"},
|
||||
"unique_id": {"type": "string"},
|
||||
},
|
||||
'required': ['protocol_id', 'unique_id'],
|
||||
"required": ["protocol_id", "unique_id"],
|
||||
},
|
||||
'minItems': 1,
|
||||
"minItems": 1,
|
||||
},
|
||||
},
|
||||
'required': ['idp_id', 'protocols'],
|
||||
"required": ["idp_id", "protocols"],
|
||||
},
|
||||
},
|
||||
'links': response_types.links,
|
||||
'name': _identity_name,
|
||||
'password_expires_at': {
|
||||
"type": ["string", "null"],
|
||||
"format": "date-time",
|
||||
"name": {
|
||||
"description": (
|
||||
"The date and time when the password expires. The time zone is UTC. "
|
||||
"This is a response object attribute; not valid for requests. A "
|
||||
"null value indicates that the password never expires."
|
||||
"The user name. Must be unique within the owning domain."
|
||||
),
|
||||
"readOnly": True,
|
||||
**_identity_name,
|
||||
},
|
||||
'options': ro.USER_OPTIONS_REGISTRY.json_schema,
|
||||
"password": {
|
||||
"type": ["string", "null"],
|
||||
"description": "The password for the user.",
|
||||
},
|
||||
"options": ro.USER_OPTIONS_REGISTRY.json_schema,
|
||||
}
|
||||
|
||||
user_schema: dict[str, Any] = {
|
||||
"type": "object",
|
||||
"properties": _user_properties,
|
||||
"properties": {
|
||||
"id": {"type": "string", "description": "The user ID."},
|
||||
"default_project_id": {
|
||||
"type": ["string", "null"],
|
||||
"description": "The ID of the default project for the user.",
|
||||
},
|
||||
"description": {
|
||||
"type": ["string", "null"],
|
||||
"description": "The user description",
|
||||
},
|
||||
"domain_id": resource_schema.domain_id,
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": (
|
||||
"If the user is enabled, this value is true. If the user is"
|
||||
" disabled, this value is false."
|
||||
),
|
||||
},
|
||||
"federated": {
|
||||
"description": (
|
||||
"List of federated objects associated with a user. Each object"
|
||||
" in the list contains the idp_id and protocols. protocols is"
|
||||
" a list of objects, each of which contains protocol_id and"
|
||||
" unique_id of the protocol and user respectively."
|
||||
),
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"idp_id": {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"The Identity Provider ID of the federated user"
|
||||
),
|
||||
},
|
||||
"protocols": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"protocol_id": {"type": "string"},
|
||||
"unique_id": {"type": "string"},
|
||||
},
|
||||
"required": ["protocol_id", "unique_id"],
|
||||
},
|
||||
"minItems": 1,
|
||||
},
|
||||
},
|
||||
"required": ["idp_id", "protocols"],
|
||||
},
|
||||
},
|
||||
"links": response_types.links,
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"The user name. Must be unique within the owning domain."
|
||||
),
|
||||
},
|
||||
"password_expires_at": {
|
||||
"type": ["string", "null"],
|
||||
"format": "date-time",
|
||||
"description": (
|
||||
"The date and time when the password expires. The time zone is"
|
||||
" UTC. A null value indicates that the password never expires."
|
||||
),
|
||||
},
|
||||
"options": ro.USER_OPTIONS_REGISTRY.json_schema,
|
||||
},
|
||||
# NOTE(gtema) User resource supports additional attributes which are stored
|
||||
# in the `extra` DB field
|
||||
"additionalProperties": True,
|
||||
"required": ["id", "domain_id", "enabled", "name"],
|
||||
}
|
||||
|
||||
user_index_response_body: dict[str, Any] = {
|
||||
|
|
@ -160,7 +247,7 @@ user_create_request: dict[str, Any] = {
|
|||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"password": {"type": ["string", "null"]},
|
||||
"domain_id": parameter_types.domain_id,
|
||||
**_user_properties,
|
||||
},
|
||||
"required": ["name"],
|
||||
|
|
@ -175,22 +262,18 @@ user_create_response_body: dict[str, Any] = user_get_response_body
|
|||
|
||||
user_update_properties = copy.deepcopy(_user_properties)
|
||||
# It is not allowed anymore to update domain of the existing user
|
||||
user_update_properties.pop("domain_id", None)
|
||||
user_update_request: dict[str, Any] = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'user': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'password': {'type': ['string', 'null']},
|
||||
**user_update_properties,
|
||||
},
|
||||
'minProperties': 1,
|
||||
'additionalProperties': True,
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": _user_properties,
|
||||
"minProperties": 1,
|
||||
"additionalProperties": True,
|
||||
}
|
||||
},
|
||||
'required': ['user'],
|
||||
'additionalProperties': False,
|
||||
"required": ["user"],
|
||||
"additionalProperties": False,
|
||||
}
|
||||
|
||||
user_update_response_body: dict[str, Any] = user_get_response_body
|
||||
|
|
@ -213,18 +296,22 @@ group_index_request_query: dict[str, Any] = {
|
|||
"additionalProperties": True,
|
||||
}
|
||||
|
||||
_group_properties: dict[str, Any] = {
|
||||
'description': validation.nullable(parameter_types.description),
|
||||
'domain_id': parameter_types.id_string,
|
||||
'id': {"type": "string", "description": "The user ID.", "readOnly": True},
|
||||
'name': _identity_name,
|
||||
}
|
||||
|
||||
group_schema: dict[str, Any] = {
|
||||
"type": "object",
|
||||
"properties": _group_properties,
|
||||
# NOTE(gtema) Group resource supports additional attributes which are stored
|
||||
# in the `extra` DB field
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": ["string", "null"],
|
||||
"description": "The description of the user group resource.",
|
||||
},
|
||||
"domain_id": resource_schema.domain_id,
|
||||
"id": {"type": "string", "description": "The user ID."},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of tje user group.",
|
||||
},
|
||||
},
|
||||
# NOTE(gtema) Group resource supports additional attributes which are
|
||||
# stored in the `extra` DB field
|
||||
"additionalProperties": True,
|
||||
}
|
||||
|
||||
|
|
@ -245,19 +332,33 @@ group_index_response_body: dict[str, Any] = {
|
|||
|
||||
group_get_response_body: dict[str, Any] = {
|
||||
"type": "object",
|
||||
"properties": {"group": group_schema},
|
||||
"required": ["group"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"group": group_schema,
|
||||
"required": ["group"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
}
|
||||
|
||||
group_create_request_body: dict[str, Any] = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'group': {
|
||||
'type': 'object',
|
||||
'properties': _group_properties,
|
||||
'required': ['name'],
|
||||
'additionalProperties': True,
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"group": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"domain_id": parameter_types.domain_id,
|
||||
"description": {
|
||||
"type": ["string", "null"],
|
||||
"description": (
|
||||
"The description of the user group resource."
|
||||
),
|
||||
},
|
||||
"name": {
|
||||
"description": "The name of the user group.",
|
||||
**_identity_name,
|
||||
},
|
||||
},
|
||||
"required": ["name"],
|
||||
"additionalProperties": True,
|
||||
}
|
||||
},
|
||||
"required": ["group"],
|
||||
|
|
@ -266,43 +367,51 @@ group_create_request_body: dict[str, Any] = {
|
|||
|
||||
group_create_response_body = group_get_response_body
|
||||
|
||||
group_update_properties = copy.deepcopy(_group_properties)
|
||||
# It is not allowed anymore to update domain of the existing group
|
||||
group_update_properties.pop("domain_id", None)
|
||||
group_update_request_body: dict[str, Any] = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'group': {
|
||||
'type': 'object',
|
||||
'properties': group_update_properties,
|
||||
'minProperties': 1,
|
||||
'additionalProperties': True,
|
||||
group_update_request_body = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"group": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": ["string", "null"],
|
||||
"description": (
|
||||
"The new description of the user group resource."
|
||||
),
|
||||
},
|
||||
"name": {
|
||||
"description": "The new name of the user group.",
|
||||
**_identity_name,
|
||||
},
|
||||
},
|
||||
"minProperties": 1,
|
||||
"additionalProperties": True,
|
||||
}
|
||||
},
|
||||
"required": ["group"],
|
||||
'additionalProperties': False,
|
||||
"additionalProperties": False,
|
||||
}
|
||||
|
||||
group_update_response_body = group_get_response_body
|
||||
|
||||
_password_change_properties = {
|
||||
'original_password': {'type': 'string'},
|
||||
'password': {'type': 'string'},
|
||||
"original_password": {"type": "string"},
|
||||
"password": {"type": "string"},
|
||||
}
|
||||
if getattr(CONF, 'strict_password_check', None):
|
||||
_password_change_properties['password']['maxLength'] = (
|
||||
if getattr(CONF, "strict_password_check", None):
|
||||
_password_change_properties["password"]["maxLength"] = (
|
||||
CONF.identity.max_password_length
|
||||
)
|
||||
|
||||
if getattr(CONF, 'security_compliance', None):
|
||||
if getattr(CONF.security_compliance, 'password_regex', None):
|
||||
_password_change_properties['password']['pattern'] = (
|
||||
if getattr(CONF, "security_compliance", None):
|
||||
if getattr(CONF.security_compliance, "password_regex", None):
|
||||
_password_change_properties["password"]["pattern"] = (
|
||||
CONF.security_compliance.password_regex
|
||||
)
|
||||
|
||||
password_change = {
|
||||
'type': 'object',
|
||||
'properties': _password_change_properties,
|
||||
'required': ['original_password', 'password'],
|
||||
'additionalProperties': False,
|
||||
"type": "object",
|
||||
"properties": _password_change_properties,
|
||||
"required": ["original_password", "password"],
|
||||
"additionalProperties": False,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,26 @@ from keystone.common import validation
|
|||
from keystone.common.validation import parameter_types as old_parameter_types
|
||||
from keystone.resource.backends import resource_options as ro
|
||||
|
||||
domain_id: dict[str, Any] = {
|
||||
"type": "string",
|
||||
"description": "The ID of the domain.",
|
||||
}
|
||||
|
||||
domain_name: dict[str, Any] = {
|
||||
"type": "string",
|
||||
"description": "The name of the domain.",
|
||||
}
|
||||
|
||||
project_id: dict[str, Any] = {
|
||||
"type": "string",
|
||||
"description": "The ID of the project.",
|
||||
}
|
||||
|
||||
default_project_id: dict[str, Any] = {
|
||||
"type": ["string", "null"],
|
||||
"description": "The ID of the project.",
|
||||
}
|
||||
|
||||
_name_properties = {
|
||||
'type': 'string',
|
||||
'description': 'The resource name.',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue