mirror of
https://github.com/enpaul/keyosk.git
synced 2024-12-26 01:33:33 +00:00
Fix linting and typing errors
Document token model Fix typo accessing incorrect crypto hash in passlib Fix cyclical import around domain admin settings Deprecate unused token usage enum Document constants module Fix typing errors in fields module Fix creation order of database models Add uuid default factory for uuid field
This commit is contained in:
parent
33325a344e
commit
71d6ed4e3c
@ -49,7 +49,7 @@ class SQLiteStorageConfigSerializer(msh.Schema):
|
||||
:class:`KeyoskSQLiteStorageConfig` class.
|
||||
"""
|
||||
|
||||
path = custom_fields.Path()
|
||||
path = custom_fields.PathString()
|
||||
pragmas = msh.fields.Dict(keys=msh.fields.String(), values=msh.fields.Raw())
|
||||
|
||||
# pylint: disable=unused-argument,no-self-use
|
||||
|
@ -1,3 +1,5 @@
|
||||
"""Constant parameter definitions"""
|
||||
|
||||
DEFAULT_CONFIG_PATH = "/etc/keyosk/conf.toml"
|
||||
|
||||
ENV_CONFIG_PATH = "KYSK_CONF_PATH"
|
||||
|
@ -28,21 +28,21 @@ from keyosk import datatypes
|
||||
from keyosk.database._shared import INTERFACE as interface
|
||||
from keyosk.database._shared import KeyoskBaseModel
|
||||
from keyosk.database.account import Account
|
||||
from keyosk.database.account import AccountAssignment
|
||||
from keyosk.database.account_acl import AccountACLEntry
|
||||
from keyosk.database.domain import Domain
|
||||
from keyosk.database.mappings import AccountACL
|
||||
from keyosk.database.mappings import AccountAssignment
|
||||
from keyosk.database.mappings import DomainAccessList
|
||||
from keyosk.database.mappings import DomainAdmin
|
||||
from keyosk.database.mappings import DomainPermission
|
||||
from keyosk.database.domain import DomainAccessList
|
||||
from keyosk.database.domain import DomainPermission
|
||||
from keyosk.database.domain_admin import DomainAdmin
|
||||
|
||||
|
||||
MODELS: List[Type[KeyoskBaseModel]] = [
|
||||
Account,
|
||||
Domain,
|
||||
DomainAccessList,
|
||||
DomainPermission,
|
||||
DomainAdmin,
|
||||
AccountACL,
|
||||
Domain,
|
||||
AccountACLEntry,
|
||||
AccountAssignment,
|
||||
]
|
||||
|
||||
|
@ -11,6 +11,7 @@ Thus the :func:`initialize` function and :class:`KeyoskBaseModel` class need to
|
||||
separate modules, and for somewhat arbitrary reasons the base model was put here and the
|
||||
init function kept in init.
|
||||
"""
|
||||
import uuid
|
||||
from typing import Any
|
||||
from typing import Generator
|
||||
from typing import List
|
||||
@ -36,7 +37,9 @@ class KeyoskBaseModel(peewee.Model):
|
||||
class Meta: # pylint: disable=missing-docstring,too-few-public-methods
|
||||
database = INTERFACE
|
||||
|
||||
uuid = peewee.UUIDField(null=False, unique=True, primary_key=True)
|
||||
uuid = peewee.UUIDField(
|
||||
null=False, unique=True, primary_key=True, default=uuid.uuid4
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def dict_keys() -> List[str]:
|
||||
|
@ -66,7 +66,7 @@ class Account(KeyoskBaseModel):
|
||||
:returns: Boolean indicating whether the provided value matches the encrypted
|
||||
secret
|
||||
"""
|
||||
return passlib.hash.pdkdf2_sha512.verify(
|
||||
return passlib.hash.pbkdf2_sha512.verify(
|
||||
value, self.encrypted_server_set_secret
|
||||
)
|
||||
|
||||
@ -75,7 +75,7 @@ class Account(KeyoskBaseModel):
|
||||
|
||||
:param value: The string to set the encrypted client-set-secret to
|
||||
"""
|
||||
self.encrypted_client_set_secret = passlib.hash.pdkdf2_sha512.hash(value)
|
||||
self.encrypted_client_set_secret = passlib.hash.pbkdf2_sha512.hash(value)
|
||||
|
||||
def update_server_set_secret(self, length: int = 42) -> str:
|
||||
"""Update the server set secret
|
||||
@ -84,7 +84,7 @@ class Account(KeyoskBaseModel):
|
||||
:returns: The new value of the server set secret
|
||||
"""
|
||||
value = secrets.token_urlsafe(length)
|
||||
self.encrypted_server_set_secret = passlib.hash.pdkdf2_sha512.hash(value)
|
||||
self.encrypted_server_set_secret = passlib.hash.pbkdf2_sha512.hash(value)
|
||||
return value
|
||||
|
||||
@staticmethod
|
||||
|
@ -5,7 +5,6 @@ from typing import List
|
||||
import peewee
|
||||
|
||||
from keyosk.database._shared import KeyoskBaseModel
|
||||
from keyosk.database.domain_admin import DomainAdmin
|
||||
|
||||
|
||||
class Domain(KeyoskBaseModel):
|
||||
@ -29,8 +28,8 @@ class Domain(KeyoskBaseModel):
|
||||
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 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
|
||||
@ -53,7 +52,6 @@ class Domain(KeyoskBaseModel):
|
||||
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]:
|
||||
@ -65,6 +63,11 @@ class Domain(KeyoskBaseModel):
|
||||
"""Return the list of permission names from the backref"""
|
||||
return [item.name for item in self._permissions]
|
||||
|
||||
@property
|
||||
def administration(self):
|
||||
"""Return administration settings container"""
|
||||
return self._administration[0]
|
||||
|
||||
@staticmethod
|
||||
def dict_keys() -> List[str]:
|
||||
return [
|
||||
|
@ -16,8 +16,9 @@ from typing import Tuple
|
||||
import peewee
|
||||
|
||||
from keyosk.database._shared import KeyoskBaseModel
|
||||
from keyosk.database.mappings import DomainAccessList
|
||||
from keyosk.database.mappings import DomainPermission
|
||||
from keyosk.database.domain import Domain
|
||||
from keyosk.database.domain import DomainAccessList
|
||||
from keyosk.database.domain import DomainPermission
|
||||
|
||||
|
||||
class DomainAdmin(KeyoskBaseModel):
|
||||
@ -55,6 +56,9 @@ class DomainAdmin(KeyoskBaseModel):
|
||||
class Meta: # pylint: disable=missing-docstring,too-few-public-methods
|
||||
table_name = "domain_admin"
|
||||
|
||||
domain = peewee.ForeignKeyField(
|
||||
Domain, unique=True, null=False, backref="_administration"
|
||||
)
|
||||
access_list = peewee.ForeignKeyField(DomainAccessList, null=True)
|
||||
domain_read = peewee.ForeignKeyField(DomainPermission, null=True)
|
||||
domain_update = peewee.ForeignKeyField(DomainPermission, null=True)
|
||||
|
@ -1,5 +1,7 @@
|
||||
"""Access token model definition"""
|
||||
import datetime
|
||||
import json
|
||||
import secrets
|
||||
from collections import OrderedDict
|
||||
from typing import Sequence
|
||||
|
||||
@ -13,42 +15,54 @@ from keyosk.database.domain import Domain
|
||||
|
||||
|
||||
class Token(KeyoskBaseModel):
|
||||
class Meta:
|
||||
"""Issued access token storage model
|
||||
|
||||
:attribute account: Account the token was issued to
|
||||
:attribute domain: Domain the token was issued for
|
||||
:attribute issuer: Value of the issuer parameter at generation time
|
||||
:attribute issued: Datetime indicating when the token was issued
|
||||
:attribute expires: Datetime indicating when the token expires
|
||||
:attribute revoked: Whether the token has been revoked
|
||||
:attribute refresh: Refresh token attached to the issued access token; can be
|
||||
``None`` if refresh tokens are disabled for the domain
|
||||
:property claims: Claims generated for the token
|
||||
|
||||
.. note:: Settings and parameters may be changed on linked records. However, the
|
||||
``claims`` property will always contain the set of claims as assigned at
|
||||
issuance time.
|
||||
"""
|
||||
|
||||
class Meta: # pylint: disable=missing-docstring,too-few-public-methods
|
||||
table_name = "token"
|
||||
|
||||
account = peewee.ForeignKeyField(Account, backref="tokens")
|
||||
domain = peewee.ForeignKeyField(Domain, backref="tokens")
|
||||
account = peewee.ForeignKeyField(Account, backref="tokens", null=True)
|
||||
domain = peewee.ForeignKeyField(Domain, backref="tokens", null=True)
|
||||
issuer = peewee.CharField(null=False)
|
||||
issued = peewee.DateTimeField(null=False, default=datetime.datetime.utcnow)
|
||||
expires = peewee.DateTimeField(null=False)
|
||||
revoked = peewee.BooleanField(null=False)
|
||||
refresh = peewee.CharField(null=True)
|
||||
_claims = peewee.CharField(null=False)
|
||||
_usage = peewee.CharField(null=False)
|
||||
|
||||
@property
|
||||
def claims(self):
|
||||
def claims(self) -> datatypes.TokenClaims:
|
||||
"""Return the claims dictionary"""
|
||||
return json.loads(self._claims)
|
||||
|
||||
@claims.setter
|
||||
def claims(self, value):
|
||||
def claims(self, value: datatypes.TokenClaims):
|
||||
"""Set the claims dictionary"""
|
||||
self._claims = json.dumps(value)
|
||||
|
||||
@property
|
||||
def usage(self) -> datatypes.TokenUsage:
|
||||
return datatypes.TokenUsage[self._usage]
|
||||
|
||||
@usage.setter
|
||||
def usage(self, value: datatypes.TokenUsage):
|
||||
self._usage = value.name
|
||||
|
||||
def make_public_claims(self):
|
||||
"""Generate the public JWT claims from current state data"""
|
||||
return {
|
||||
"jti": self.uuid,
|
||||
"sub": self.account.username,
|
||||
"aud": self.domain.audience,
|
||||
"iss": self.issuer,
|
||||
"exp": int(self.expires.timestamp()),
|
||||
"iat": int(self.issued.timestamp()),
|
||||
"exp": int(self.expires.timestamp()), # pylint: disable=no-member
|
||||
"iat": int(self.issued.timestamp()), # pylint: disable=no-member
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@ -58,16 +72,21 @@ class Token(KeyoskBaseModel):
|
||||
domain: Domain,
|
||||
issuer: str,
|
||||
lifespan: datetime.timedelta,
|
||||
usage: datatypes.TokenUsage,
|
||||
permissions: Sequence[AccountACLEntry],
|
||||
generate_refresh: bool,
|
||||
):
|
||||
"""Create a new token using provided data
|
||||
|
||||
This function is intentionally not documented, as I expect it will not survive
|
||||
first contact with a practical implementation
|
||||
"""
|
||||
new = cls(
|
||||
account=account,
|
||||
domain=domain,
|
||||
issuer=issuer,
|
||||
expires=(datetime.datetime.utcnow() + lifespan),
|
||||
usage=usage,
|
||||
revoked=False,
|
||||
refresh=secrets.token_urlsafe(42) if generate_refresh else None,
|
||||
)
|
||||
|
||||
acls = {}
|
||||
@ -96,7 +115,7 @@ class Token(KeyoskBaseModel):
|
||||
}
|
||||
|
||||
claims = new.make_public_claims()
|
||||
claims.update({"ksk-usg": new.usage.value, "ksk-pem": bitmasks})
|
||||
claims.update({"ksk-pem": bitmasks})
|
||||
|
||||
new.claims = claims
|
||||
|
||||
|
@ -7,14 +7,7 @@ from typing import Union
|
||||
Extras = Dict[str, Union[int, float, bool, str, None]]
|
||||
|
||||
|
||||
class TokenUsage(enum.Enum):
|
||||
"""Possible usage values for an issued JWT
|
||||
|
||||
Values will be the value of the ``ksk-usg`` claim in the issued token
|
||||
"""
|
||||
|
||||
REFRESH = "ref"
|
||||
ACCESS = "acc"
|
||||
TokenClaims = Dict[str, Union[str, int, bool, Dict[str, int]]]
|
||||
|
||||
|
||||
@enum.unique
|
||||
|
@ -59,7 +59,7 @@ class EnumItem(msh.fields.Field):
|
||||
return self.enum[self._from_pretty_name(value)]
|
||||
return self.enum[value]
|
||||
except ValueError as err:
|
||||
raise msh.ValidationError(err)
|
||||
raise msh.ValidationError(str(err))
|
||||
except (KeyError, AttributeError) as err:
|
||||
raise msh.ValidationError(f"No {self.enum} named {err}")
|
||||
|
||||
@ -72,11 +72,11 @@ class EnumItem(msh.fields.Field):
|
||||
return value.replace("-", "_").upper()
|
||||
|
||||
|
||||
class Path(msh.fields.String):
|
||||
class PathString(msh.fields.String):
|
||||
"""Translate between a string and a path object"""
|
||||
|
||||
def _serialize(self, value: Union[str, Path], *args, **kwargs) -> str:
|
||||
return super()._serialize(str(value), *args, **kwargs)
|
||||
def _serialize(self, value: Union[str, Path], attr, obj, **kwargs) -> str:
|
||||
return super()._serialize(str(value), attr, obj, **kwargs)
|
||||
|
||||
def _deserialize(self, value: str, *args, **kwargs) -> Path:
|
||||
return Path(super()._deserialize(value, *args, **kwargs))
|
||||
def _deserialize(self, value: str, attr, data, **kwargs) -> Path:
|
||||
return Path(super()._deserialize(value, attr, data, **kwargs))
|
||||
|
Loading…
Reference in New Issue
Block a user