From 425d39cd487b3367dea2b3c64c14d42f57f31745 Mon Sep 17 00:00:00 2001 From: Ethan Paul Date: Sat, 22 Feb 2020 19:27:00 -0500 Subject: [PATCH] Add domain and domain administration models --- keyosk/database/domain.py | 123 ++++++++++++++++++++++++++++++++ keyosk/database/domain_admin.py | 70 ++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 keyosk/database/domain.py create mode 100644 keyosk/database/domain_admin.py diff --git a/keyosk/database/domain.py b/keyosk/database/domain.py new file mode 100644 index 0000000..6f3acee --- /dev/null +++ b/keyosk/database/domain.py @@ -0,0 +1,123 @@ +"""Authentication domain model definition""" +import datetime +from typing import List + +import peewee + +from keyosk.database._shared import KeyoskBaseModel +from keyosk.database.domain_admin import DomainAdmin + + +class Domain(KeyoskBaseModel): + """Authentication domain storage model + + :attribute created: Datetime indicating when the domain was first created + :attribute updated: Datetime indicating when the domain was last modified + :attribute name: Simple URL-friendly name for the domain + :attribute audience: Value to populate the ``audience`` claim of issued JWTs with + when authenticating against this domain + :attribute title: Human-friendly display name for the domain + :attribute description: Human-friendly longer description of the domain's usage or + purpose + :attribute contact: Contact link for the domain + :attribute enabled: Whether the domain is enabled for authentication + :attribute enable_password: Whether to allow accounts to authenticate using the + client-set authentication secret + :attribute enable_autopassword: Whether to allow accounts to authenticate using the + server-set authentication secret + :attribute lifespan_access: Number of seconds that an issued JWT access token should + be valid for + :attribute lifespan_refresh: Number of seconds an an issued JWT refresh token should + be valid for + :attribute administration: Container of additional settings related to the + administration of the domain itself + :property access_list_names: List of Access Control Lists under the domain that accounts + can have permission entries on + :property permission_names: List of permissions that can be assigned to an account's ACL + entry + """ + + class Meta: # pylint: disable=too-few-public-methods,missing-docstring + table_name = "domain" + + created = peewee.DateTimeField(null=False, default=datetime.datetime.utcnow) + updated = peewee.DateTimeField(null=False, default=datetime.datetime.utcnow) + name = peewee.CharField(null=False, unique=True) + audience = peewee.CharField(null=False, unique=True) + title = peewee.CharField(null=True) + description = peewee.CharField(null=True) + contact = peewee.CharField(null=True) + enabled = peewee.BooleanField(null=False) + enable_password = peewee.BooleanField(null=False) + enable_autopassword = peewee.BooleanField(null=False) + enable_refresh = peewee.BooleanField(null=False) + lifespan_access = peewee.IntegerField(null=False) + lifespan_refresh = peewee.IntegerField(null=False) + administration = peewee.ForeignKeyField(DomainAdmin, null=False, unique=True) + + @property + def access_list_names(self) -> List[str]: + """Return the list of ACL items from the backref""" + return [item.name for item in self._access_lists] + + @property + def permission_names(self) -> List[str]: + """Return the list of permission names from the backref""" + return [item.name for item in self._permissions] + + @staticmethod + def dict_keys() -> List[str]: + return [ + "uuid", + "created", + "updated", + "name", + "audience", + "title", + "description", + "contact", + "enabled", + "enable_password", + "enable_autopassword", + "enable_refresh", + "lifespan_access", + "lifespan_refresh", + "access_list_names", + "permission_names", + "administration", + ] + + @staticmethod + def foreign_ref() -> List[str]: + return ["administration"] + + +class DomainAccessList(KeyoskBaseModel): + """Access list name model definition + + :attribute name: Name of the access control list + :attribute domain: Authentication domain the ACL applies to + """ + + class Meta: # pylint: disable=missing-docstring,too-few-public-methods + table_name = "domain_acl" + + name = peewee.CharField(null=False) + domain = peewee.ForeignKeyField(Domain, backref="access_lists") + + +class DomainPermission(KeyoskBaseModel): + """Permission name model definition + + :attribute name: Name of the permission + :attribute bitindex: Index in the generated bitmask that indicates this permission; + zero-indexed + :attribute domain: Authentication domain the permission should apply to the ACLs of + """ + + class Meta: # pylint: disable=missing-docstring,too-few-public-methods + table_name = "domain_permission" + + name = peewee.CharField(null=False) + bitindex = peewee.IntegerField(null=False) + domain = peewee.ForeignKeyField(Domain, backref="permissions") diff --git a/keyosk/database/domain_admin.py b/keyosk/database/domain_admin.py new file mode 100644 index 0000000..a11bdea --- /dev/null +++ b/keyosk/database/domain_admin.py @@ -0,0 +1,70 @@ +"""Authentication domain meta admin settings model definition + +The domain administration settings allow access to be granted to accounts assigned to +the domain to manage the domain itself. This allows accounts to manage the parts of +Keyosk that they need to without granting permissions to every domain Keyosk knows +about. + +However, to avoid circular foreign key references, the admin settings need their own +relation tabel. If these settings were part of the main :class:`Domain` model then there +would be circular references between it and the :class:`DomainAccessList` and +:class:`DomainPermission` models. +""" +from typing import Generator +from typing import Tuple + +import peewee + +from keyosk.database._shared import KeyoskBaseModel +from keyosk.database.mappings import DomainAccessList +from keyosk.database.mappings import DomainPermission + + +class DomainAdmin(KeyoskBaseModel): + """Authentication domain meta administration storage model + + :attribute access_list: The ACL that an account must have permissions for to manage + the domain settings + :attribute domain_read: Permission granted by the ACL entry that gives the assigned + account read access to the domain settings + :attribute domain_update: Permission granted by the ACL entry that gives the + assigned account update access to the domain settings + :attribute account_create: Permission granted by the ACL entry that gives the + assigned account access to create new accounts assigned + to the domain + :attribute account_read: Permission granted by the ACL entry that gives the + assigned account read access to the accounts assigned to + the domain + :attribute account_delete: Permission granted by the ACL entry that gives the + assigned account access to unassign an account from the + domain + + There are two permissions not available via this model that may make sense to + implement in the future: ``account_update`` and ``domain_delete``. The first is not + implemented due to the potential conflicts it causes: an account can be assigned to + multiple domains, so granting permissions on one domain to modify an account may + implicitly grant that same permission on one or more accounts assigned to another + domain; this seemed ill advised. The second is not implemented for no real good + reason, other than it seemed out of the inteneded usage of "domain management". + + .. note:: Both the permissions denoted above, as well as other permissions not + enumerated here, are available through the primary Keyosk authentication + domain. + """ + + class Meta: # pylint: disable=missing-docstring,too-few-public-methods + table_name = "domain_admin" + + access_list = peewee.ForeignKeyField(DomainAccessList, null=True) + domain_read = peewee.ForeignKeyField(DomainPermission, null=True) + domain_update = peewee.ForeignKeyField(DomainPermission, null=True) + account_create = peewee.ForeignKeyField(DomainPermission, null=True) + account_read = peewee.ForeignKeyField(DomainPermission, null=True) + account_delete = peewee.ForeignKeyField(DomainPermission, null=True) + + def __iter__(self) -> Generator[Tuple[str, str], None, None]: + yield "access_list", self.access_list.name + yield "domain_read", self.domain_read.name + yield "account_create", self.account_create.name + yield "account_read", self.account_read.name + yield "account_delete", self.account_delete.name