mirror of
https://github.com/enpaul/keyosk.git
synced 2024-11-24 23:47:49 +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.
|
:class:`KeyoskSQLiteStorageConfig` class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
path = custom_fields.Path()
|
path = custom_fields.PathString()
|
||||||
pragmas = msh.fields.Dict(keys=msh.fields.String(), values=msh.fields.Raw())
|
pragmas = msh.fields.Dict(keys=msh.fields.String(), values=msh.fields.Raw())
|
||||||
|
|
||||||
# pylint: disable=unused-argument,no-self-use
|
# pylint: disable=unused-argument,no-self-use
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"""Constant parameter definitions"""
|
||||||
|
|
||||||
DEFAULT_CONFIG_PATH = "/etc/keyosk/conf.toml"
|
DEFAULT_CONFIG_PATH = "/etc/keyosk/conf.toml"
|
||||||
|
|
||||||
ENV_CONFIG_PATH = "KYSK_CONF_PATH"
|
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 INTERFACE as interface
|
||||||
from keyosk.database._shared import KeyoskBaseModel
|
from keyosk.database._shared import KeyoskBaseModel
|
||||||
from keyosk.database.account import Account
|
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.domain import Domain
|
||||||
from keyosk.database.mappings import AccountACL
|
from keyosk.database.domain import DomainAccessList
|
||||||
from keyosk.database.mappings import AccountAssignment
|
from keyosk.database.domain import DomainPermission
|
||||||
from keyosk.database.mappings import DomainAccessList
|
from keyosk.database.domain_admin import DomainAdmin
|
||||||
from keyosk.database.mappings import DomainAdmin
|
|
||||||
from keyosk.database.mappings import DomainPermission
|
|
||||||
|
|
||||||
|
|
||||||
MODELS: List[Type[KeyoskBaseModel]] = [
|
MODELS: List[Type[KeyoskBaseModel]] = [
|
||||||
Account,
|
Account,
|
||||||
Domain,
|
|
||||||
DomainAccessList,
|
DomainAccessList,
|
||||||
DomainPermission,
|
DomainPermission,
|
||||||
DomainAdmin,
|
DomainAdmin,
|
||||||
AccountACL,
|
Domain,
|
||||||
|
AccountACLEntry,
|
||||||
AccountAssignment,
|
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
|
separate modules, and for somewhat arbitrary reasons the base model was put here and the
|
||||||
init function kept in init.
|
init function kept in init.
|
||||||
"""
|
"""
|
||||||
|
import uuid
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Generator
|
from typing import Generator
|
||||||
from typing import List
|
from typing import List
|
||||||
@ -36,7 +37,9 @@ class KeyoskBaseModel(peewee.Model):
|
|||||||
class Meta: # pylint: disable=missing-docstring,too-few-public-methods
|
class Meta: # pylint: disable=missing-docstring,too-few-public-methods
|
||||||
database = INTERFACE
|
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
|
@staticmethod
|
||||||
def dict_keys() -> List[str]:
|
def dict_keys() -> List[str]:
|
||||||
|
@ -66,7 +66,7 @@ class Account(KeyoskBaseModel):
|
|||||||
:returns: Boolean indicating whether the provided value matches the encrypted
|
:returns: Boolean indicating whether the provided value matches the encrypted
|
||||||
secret
|
secret
|
||||||
"""
|
"""
|
||||||
return passlib.hash.pdkdf2_sha512.verify(
|
return passlib.hash.pbkdf2_sha512.verify(
|
||||||
value, self.encrypted_server_set_secret
|
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
|
: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:
|
def update_server_set_secret(self, length: int = 42) -> str:
|
||||||
"""Update the server set secret
|
"""Update the server set secret
|
||||||
@ -84,7 +84,7 @@ class Account(KeyoskBaseModel):
|
|||||||
:returns: The new value of the server set secret
|
:returns: The new value of the server set secret
|
||||||
"""
|
"""
|
||||||
value = secrets.token_urlsafe(length)
|
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
|
return value
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -5,7 +5,6 @@ from typing import List
|
|||||||
import peewee
|
import peewee
|
||||||
|
|
||||||
from keyosk.database._shared import KeyoskBaseModel
|
from keyosk.database._shared import KeyoskBaseModel
|
||||||
from keyosk.database.domain_admin import DomainAdmin
|
|
||||||
|
|
||||||
|
|
||||||
class Domain(KeyoskBaseModel):
|
class Domain(KeyoskBaseModel):
|
||||||
@ -29,7 +28,7 @@ class Domain(KeyoskBaseModel):
|
|||||||
be valid for
|
be valid for
|
||||||
:attribute lifespan_refresh: Number of seconds an an issued JWT refresh token should
|
:attribute lifespan_refresh: Number of seconds an an issued JWT refresh token should
|
||||||
be valid for
|
be valid for
|
||||||
:attribute administration: Container of additional settings related to the
|
:property administration: Container of additional settings related to the
|
||||||
administration of the domain itself
|
administration of the domain itself
|
||||||
:property access_list_names: List of Access Control Lists under the domain that accounts
|
:property access_list_names: List of Access Control Lists under the domain that accounts
|
||||||
can have permission entries on
|
can have permission entries on
|
||||||
@ -53,7 +52,6 @@ class Domain(KeyoskBaseModel):
|
|||||||
enable_refresh = peewee.BooleanField(null=False)
|
enable_refresh = peewee.BooleanField(null=False)
|
||||||
lifespan_access = peewee.IntegerField(null=False)
|
lifespan_access = peewee.IntegerField(null=False)
|
||||||
lifespan_refresh = peewee.IntegerField(null=False)
|
lifespan_refresh = peewee.IntegerField(null=False)
|
||||||
administration = peewee.ForeignKeyField(DomainAdmin, null=False, unique=True)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def access_list_names(self) -> List[str]:
|
def access_list_names(self) -> List[str]:
|
||||||
@ -65,6 +63,11 @@ class Domain(KeyoskBaseModel):
|
|||||||
"""Return the list of permission names from the backref"""
|
"""Return the list of permission names from the backref"""
|
||||||
return [item.name for item in self._permissions]
|
return [item.name for item in self._permissions]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def administration(self):
|
||||||
|
"""Return administration settings container"""
|
||||||
|
return self._administration[0]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def dict_keys() -> List[str]:
|
def dict_keys() -> List[str]:
|
||||||
return [
|
return [
|
||||||
|
@ -16,8 +16,9 @@ from typing import Tuple
|
|||||||
import peewee
|
import peewee
|
||||||
|
|
||||||
from keyosk.database._shared import KeyoskBaseModel
|
from keyosk.database._shared import KeyoskBaseModel
|
||||||
from keyosk.database.mappings import DomainAccessList
|
from keyosk.database.domain import Domain
|
||||||
from keyosk.database.mappings import DomainPermission
|
from keyosk.database.domain import DomainAccessList
|
||||||
|
from keyosk.database.domain import DomainPermission
|
||||||
|
|
||||||
|
|
||||||
class DomainAdmin(KeyoskBaseModel):
|
class DomainAdmin(KeyoskBaseModel):
|
||||||
@ -55,6 +56,9 @@ class DomainAdmin(KeyoskBaseModel):
|
|||||||
class Meta: # pylint: disable=missing-docstring,too-few-public-methods
|
class Meta: # pylint: disable=missing-docstring,too-few-public-methods
|
||||||
table_name = "domain_admin"
|
table_name = "domain_admin"
|
||||||
|
|
||||||
|
domain = peewee.ForeignKeyField(
|
||||||
|
Domain, unique=True, null=False, backref="_administration"
|
||||||
|
)
|
||||||
access_list = peewee.ForeignKeyField(DomainAccessList, null=True)
|
access_list = peewee.ForeignKeyField(DomainAccessList, null=True)
|
||||||
domain_read = peewee.ForeignKeyField(DomainPermission, null=True)
|
domain_read = peewee.ForeignKeyField(DomainPermission, null=True)
|
||||||
domain_update = peewee.ForeignKeyField(DomainPermission, null=True)
|
domain_update = peewee.ForeignKeyField(DomainPermission, null=True)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
"""Access token model definition"""
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
|
import secrets
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
|
|
||||||
@ -13,42 +15,54 @@ from keyosk.database.domain import Domain
|
|||||||
|
|
||||||
|
|
||||||
class Token(KeyoskBaseModel):
|
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"
|
table_name = "token"
|
||||||
|
|
||||||
account = peewee.ForeignKeyField(Account, backref="tokens")
|
account = peewee.ForeignKeyField(Account, backref="tokens", null=True)
|
||||||
domain = peewee.ForeignKeyField(Domain, backref="tokens")
|
domain = peewee.ForeignKeyField(Domain, backref="tokens", null=True)
|
||||||
issuer = peewee.CharField(null=False)
|
issuer = peewee.CharField(null=False)
|
||||||
issued = peewee.DateTimeField(null=False, default=datetime.datetime.utcnow)
|
issued = peewee.DateTimeField(null=False, default=datetime.datetime.utcnow)
|
||||||
expires = peewee.DateTimeField(null=False)
|
expires = peewee.DateTimeField(null=False)
|
||||||
revoked = peewee.BooleanField(null=False)
|
revoked = peewee.BooleanField(null=False)
|
||||||
|
refresh = peewee.CharField(null=True)
|
||||||
_claims = peewee.CharField(null=False)
|
_claims = peewee.CharField(null=False)
|
||||||
_usage = peewee.CharField(null=False)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def claims(self):
|
def claims(self) -> datatypes.TokenClaims:
|
||||||
|
"""Return the claims dictionary"""
|
||||||
return json.loads(self._claims)
|
return json.loads(self._claims)
|
||||||
|
|
||||||
@claims.setter
|
@claims.setter
|
||||||
def claims(self, value):
|
def claims(self, value: datatypes.TokenClaims):
|
||||||
|
"""Set the claims dictionary"""
|
||||||
self._claims = json.dumps(value)
|
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):
|
def make_public_claims(self):
|
||||||
|
"""Generate the public JWT claims from current state data"""
|
||||||
return {
|
return {
|
||||||
"jti": self.uuid,
|
"jti": self.uuid,
|
||||||
"sub": self.account.username,
|
"sub": self.account.username,
|
||||||
"aud": self.domain.audience,
|
"aud": self.domain.audience,
|
||||||
"iss": self.issuer,
|
"iss": self.issuer,
|
||||||
"exp": int(self.expires.timestamp()),
|
"exp": int(self.expires.timestamp()), # pylint: disable=no-member
|
||||||
"iat": int(self.issued.timestamp()),
|
"iat": int(self.issued.timestamp()), # pylint: disable=no-member
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -58,16 +72,21 @@ class Token(KeyoskBaseModel):
|
|||||||
domain: Domain,
|
domain: Domain,
|
||||||
issuer: str,
|
issuer: str,
|
||||||
lifespan: datetime.timedelta,
|
lifespan: datetime.timedelta,
|
||||||
usage: datatypes.TokenUsage,
|
|
||||||
permissions: Sequence[AccountACLEntry],
|
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(
|
new = cls(
|
||||||
account=account,
|
account=account,
|
||||||
domain=domain,
|
domain=domain,
|
||||||
issuer=issuer,
|
issuer=issuer,
|
||||||
expires=(datetime.datetime.utcnow() + lifespan),
|
expires=(datetime.datetime.utcnow() + lifespan),
|
||||||
usage=usage,
|
|
||||||
revoked=False,
|
revoked=False,
|
||||||
|
refresh=secrets.token_urlsafe(42) if generate_refresh else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
acls = {}
|
acls = {}
|
||||||
@ -96,7 +115,7 @@ class Token(KeyoskBaseModel):
|
|||||||
}
|
}
|
||||||
|
|
||||||
claims = new.make_public_claims()
|
claims = new.make_public_claims()
|
||||||
claims.update({"ksk-usg": new.usage.value, "ksk-pem": bitmasks})
|
claims.update({"ksk-pem": bitmasks})
|
||||||
|
|
||||||
new.claims = claims
|
new.claims = claims
|
||||||
|
|
||||||
|
@ -7,14 +7,7 @@ from typing import Union
|
|||||||
Extras = Dict[str, Union[int, float, bool, str, None]]
|
Extras = Dict[str, Union[int, float, bool, str, None]]
|
||||||
|
|
||||||
|
|
||||||
class TokenUsage(enum.Enum):
|
TokenClaims = Dict[str, Union[str, int, bool, Dict[str, int]]]
|
||||||
"""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"
|
|
||||||
|
|
||||||
|
|
||||||
@enum.unique
|
@enum.unique
|
||||||
|
@ -59,7 +59,7 @@ class EnumItem(msh.fields.Field):
|
|||||||
return self.enum[self._from_pretty_name(value)]
|
return self.enum[self._from_pretty_name(value)]
|
||||||
return self.enum[value]
|
return self.enum[value]
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
raise msh.ValidationError(err)
|
raise msh.ValidationError(str(err))
|
||||||
except (KeyError, AttributeError) as err:
|
except (KeyError, AttributeError) as err:
|
||||||
raise msh.ValidationError(f"No {self.enum} named {err}")
|
raise msh.ValidationError(f"No {self.enum} named {err}")
|
||||||
|
|
||||||
@ -72,11 +72,11 @@ class EnumItem(msh.fields.Field):
|
|||||||
return value.replace("-", "_").upper()
|
return value.replace("-", "_").upper()
|
||||||
|
|
||||||
|
|
||||||
class Path(msh.fields.String):
|
class PathString(msh.fields.String):
|
||||||
"""Translate between a string and a path object"""
|
"""Translate between a string and a path object"""
|
||||||
|
|
||||||
def _serialize(self, value: Union[str, Path], *args, **kwargs) -> str:
|
def _serialize(self, value: Union[str, Path], attr, obj, **kwargs) -> str:
|
||||||
return super()._serialize(str(value), *args, **kwargs)
|
return super()._serialize(str(value), attr, obj, **kwargs)
|
||||||
|
|
||||||
def _deserialize(self, value: str, *args, **kwargs) -> Path:
|
def _deserialize(self, value: str, attr, data, **kwargs) -> Path:
|
||||||
return Path(super()._deserialize(value, *args, **kwargs))
|
return Path(super()._deserialize(value, attr, data, **kwargs))
|
||||||
|
Loading…
Reference in New Issue
Block a user