From 32cd33728da5f3007479520ea0ecd4a7dee84154 Mon Sep 17 00:00:00 2001 From: Ethan Paul Date: Thu, 27 Feb 2020 00:07:19 -0500 Subject: [PATCH] Update domain serializer to improve validation accuracy Remove extras serializers submodule Add validation regexes to constants --- keyosk/constants.py | 12 +++++ keyosk/serializers/__init__.py | 2 - keyosk/serializers/domain.py | 68 ++++++++++++++++++++++++----- keyosk/serializers/domain_extras.py | 22 ---------- 4 files changed, 70 insertions(+), 34 deletions(-) delete mode 100644 keyosk/serializers/domain_extras.py diff --git a/keyosk/constants.py b/keyosk/constants.py index e6d8461..984c456 100644 --- a/keyosk/constants.py +++ b/keyosk/constants.py @@ -3,3 +3,15 @@ DEFAULT_CONFIG_PATH = "/etc/keyosk/conf.toml" ENV_CONFIG_PATH = "KYSK_CONF_PATH" + +REGEX_FRIENDLY_NAME = r"^([a-z][a-z0-9]+)(-[a-z0-9]+)*$" + +REGEX_DOMAIN_NAME = REGEX_FRIENDLY_NAME + +REGEX_DOMAIN_ACCESS_LIST_NAME = REGEX_FRIENDLY_NAME + +REGEX_DOMAIN_PERMISSION_NAME = REGEX_FRIENDLY_NAME + +REGEX_DOMAIN_AUDIENCE = r"^[a-z][a-z0-9]{2,9}$" + +REGEX_DOMAIN_TITLE = r"^.{1,30}$" diff --git a/keyosk/serializers/__init__.py b/keyosk/serializers/__init__.py index 15d238d..e3b3f98 100644 --- a/keyosk/serializers/__init__.py +++ b/keyosk/serializers/__init__.py @@ -2,5 +2,3 @@ from keyosk.serializers.account import AccountSerializer from keyosk.serializers.account_acl import AccountACLSerializer from keyosk.serializers.domain import DomainSerializer -from keyosk.serializers.domain_extras import DomainAccessListSerializer -from keyosk.serializers.domain_extras import DomainPermissionSerializer diff --git a/keyosk/serializers/domain.py b/keyosk/serializers/domain.py index 3d982e1..131a7dd 100644 --- a/keyosk/serializers/domain.py +++ b/keyosk/serializers/domain.py @@ -1,20 +1,48 @@ +from typing import Dict from typing import List +from typing import Union import marshmallow as msh +from keyosk import constants from keyosk import fields as custom_fields -from keyosk.serializers.domain_extras import DomainAccessListSerializer -from keyosk.serializers.domain_extras import DomainPermissionSerializer + + +class DomainPermissionSerializer(msh.Schema): + """""" + + name = msh.fields.String( + required=True, + validate=msh.validate.Regexp(constants.REGEX_DOMAIN_PERMISSION_NAME), + ) + bitindex = msh.fields.Integer(required=True, validate=msh.validate.Range(min=0)) class DomainSerializer(msh.Schema): + """Serializer for domain records + + This serializer is meant to translate between the client-facing data format and a + data format that is ready to be used with the :class:`database.Domain` model for + access to and from the database. + + .. note:: Schema fields here map 1:1 with the model fields/properties of the same + names on :class:`database.Domain`. + """ uuid = msh.fields.UUID(required=True) created = custom_fields.Epoch(required=True) updated = custom_fields.Epoch(required=True) - name = msh.fields.String(required=True) - audience = msh.fields.String(required=True) - title = msh.fields.String(required=True, allow_none=True) + name = msh.fields.String( + required=True, validate=msh.validate.Regexp(constants.REGEX_DOMAIN_NAME) + ) + audience = msh.fields.String( + required=True, validate=msh.validate.Regexp(constants.REGEX_DOMAIN_AUDIENCE) + ) + title = msh.fields.String( + required=True, + allow_none=True, + validate=msh.validate.Regexp(constants.REGEX_DOMAIN_TITLE), + ) description = msh.fields.String(required=True, allow_none=True) contact = msh.fields.String(required=True, allow_none=True) enabled = msh.fields.Boolean(required=True) @@ -25,17 +53,37 @@ class DomainSerializer(msh.Schema): required=True, data_key="enable-server-set-auth" ) enable_refresh = msh.fields.Boolean(required=True, data_key="enable-refresh") - lifespan_access = msh.fields.Boolean(required=True, data_key="lifespan-access") - lifespan_refresh = msh.fields.Boolean(required=True, data_key="lifespan-refresh") - access_lists = msh.fields.List( - msh.fields.Nested(DomainAccessListSerializer), + lifespan_access = msh.fields.TimeDelta(required=True, data_key="lifespan-access") + lifespan_refresh = msh.fields.TimeDelta(required=True, data_key="lifespan-refresh") + access_list_names = msh.fields.List( + msh.fields.String( + validate=msh.validate.Regexp(constants.REGEX_DOMAIN_ACCESS_LIST_NAME) + ), required=True, data_key="access-lists", ) permissions = msh.fields.List( - msh.fields.Nested(DomainPermissionSerializer), required=True + msh.fields.Nested(DomainPermissionSerializer), required=True, ) + @msh.validates("access_list_names") + def validate_acl_names(self, data: List[str], **kwargs): + if len(data) != len(set(data)): + raise msh.ValidationError("Duplicate access list names") + + @msh.validates("permissions") + def validate_permissions(self, data: List[Dict[str, Union[str, int]]], **kwargs): + names = [item["name"] for item in data] + if len(names) != len(set(names)): + raise msh.ValidationError("Duplicat permission names") + + indexes = sorted([item["bitindex"] for item in data]) + for index in len(indexes): + if indexes[index - 1] != (index - 1): + raise msh.ValidationError( + f"Invalid bitindexes provided: expected zero-index sequential sequence, recieved {indexes}" + ) + @staticmethod def creation_fields() -> List[str]: return ["uuid", "created", "updated"] diff --git a/keyosk/serializers/domain_extras.py b/keyosk/serializers/domain_extras.py deleted file mode 100644 index cb0f736..0000000 --- a/keyosk/serializers/domain_extras.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Dict - -import marshmallow as msh - - -class DomainAccessListSerializer(msh.Schema): - - name = msh.fields.String(required=True) - - @msh.pre_load - def _from_string(self, data: str, *args, **kwargs) -> Dict[str, str]: - return {"name": data} - - @msh.post_dump - def _to_string(self, data, *args, **kwargs) -> str: - return data["name"] - - -class DomainPermissionSerializer(msh.Schema): - - name = msh.fields.String(required=True) - bitindex = msh.fields.Integer(required=True, validate=msh.validate.Range(min=0))