plan models + schemas

This commit is contained in:
Jörn-Michael Miehe 2022-03-25 23:03:56 +00:00
parent 1ed3b587c7
commit 02225cdf09
3 changed files with 124 additions and 147 deletions

View file

@ -4,12 +4,10 @@ SQLAlchemy representation of database contents.
from __future__ import annotations from __future__ import annotations
import datetime from sqlalchemy import (Column, DateTime, ForeignKey, Integer, String,
from sqlalchemy import (Boolean, Column, DateTime, ForeignKey, Integer, String,
UniqueConstraint) UniqueConstraint)
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session, relationship from sqlalchemy.orm import relationship
ORMBaseModel = declarative_base() ORMBaseModel = declarative_base()
@ -56,7 +54,7 @@ class Device(ORMBaseModel):
owner_name = Column(String, ForeignKey("users.name")) owner_name = Column(String, ForeignKey("users.name"))
name = Column(String) name = Column(String)
type = Column(String) type = Column(String)
expiry = Column(DateTime, default=datetime.datetime.now) expiry = Column(DateTime)
owner: User = relationship( owner: User = relationship(
"User", lazy="joined", back_populates="distinguished_names" "User", lazy="joined", back_populates="distinguished_names"

View file

@ -10,124 +10,12 @@ from enum import Enum
from typing import Any from typing import Any
from passlib.context import CryptContext from passlib.context import CryptContext
from pydantic import BaseModel, Field, constr, validator from pydantic import BaseModel, Field, validator
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from . import models from . import models
##########
# table: distinguished_names
##########
class DistinguishedNameBase(BaseModel):
cn_only: bool
country: constr(max_length=2) | None
state: str | None
city: str | None
organization: str | None
organizational_unit: str | None
email: str | None
common_name: str
class DistinguishedNameCreate(DistinguishedNameBase):
pass
class DistinguishedName(DistinguishedNameBase):
class Config:
orm_mode = True
@classmethod
def create(
cls,
db: Session,
dn: DistinguishedNameCreate,
owner: User,
) -> User | None:
"""
Create a new distinguished name in the database.
"""
try:
db_owner = models.User.load(
db=db,
name=owner.name,
)
dn = models.DistinguishedName(
cn_only=dn.cn_only,
country=dn.country,
state=dn.state,
city=dn.city,
organization=dn.organization,
organizational_unit=dn.organizational_unit,
email=dn.email,
common_name=dn.common_name,
owner=db_owner,
)
db.add(dn)
db.commit()
db.refresh(dn)
return cls.from_orm(dn)
except IntegrityError:
# distinguished name already existed
pass
def delete(
self,
db: Session,
) -> bool:
"""
Delete this distinguished name from the database.
"""
db_dn = models.DistinguishedName(**dict(self))
db.refresh(db_dn)
# .load(
# db=db,
# country=self.country,
# state=self.state,
# city=self.city,
# organization=self.organization,
# organizational_unit=self.organizational_unit,
# email=self.email,
# common_name=self.common_name,
# )
if db_dn is None:
# nonexistent user
return False
db.delete(db_dn)
db.commit()
return True
##########
# table: certificates
##########
class CertificateBase(BaseModel):
expiry: datetime
class CertificateCreate(CertificateBase):
pass
class Certificate(CertificateBase):
distinguished_name: DistinguishedName
class Config:
orm_mode = True
########## ##########
# table: user_capabilities # table: user_capabilities
########## ##########
@ -135,6 +23,9 @@ class Certificate(CertificateBase):
class UserCapability(Enum): class UserCapability(Enum):
admin = "admin" admin = "admin"
login = "login"
issue = "issue"
renew = "renew"
def __repr__(self) -> str: def __repr__(self) -> str:
return self.value return self.value
@ -157,6 +48,13 @@ class UserCapability(Enum):
# create from string representation # create from string representation
return cls(str(value)) return cls(str(value))
@property
def model(self) -> models.UserCapability:
return models.UserCapability(
capability=self.value,
)
########## ##########
# table: users # table: users
########## ##########
@ -165,19 +63,23 @@ class UserCapability(Enum):
class UserBase(BaseModel): class UserBase(BaseModel):
name: str name: str
country: str
state: str
city: str
organization: str
organizational_unit: str
email: str
capabilities: list[UserCapability] = []
class UserCreate(UserBase): class UserCreate(UserBase):
password: str password: str
class User(UserBase): class User(UserBase):
capabilities: list[UserCapability] = [] devices: list[Device] = Field(
distinguished_names: list[DistinguishedName] = Field(
default=[], repr=False
)
certificates: list[Certificate] = Field(
default=[], repr=False default=[], repr=False
) )
@ -206,8 +108,8 @@ class User(UserBase):
Load user from database by name. Load user from database by name.
""" """
if (db_user := models.User.load(db, name)) is None: db_user = models.User(name=name)
return None db.refresh(db_user)
return cls.from_orm(db_user) return cls.from_orm(db_user)
@ -223,17 +125,20 @@ class User(UserBase):
""" """
try: try:
user = models.User( db_user = models.User(
name=user.name, name=user.name,
password=crypt_context.hash(user.password), password=crypt_context.hash(user.password),
capabilities=[], capabilities=[
capability.model
for capability in user.capabilities
],
) )
db.add(user) db.add(db_user)
db.commit() db.commit()
db.refresh(user) db.refresh(db_user)
return cls.from_orm(user) return cls.from_orm(db_user)
except IntegrityError: except IntegrityError:
# user already existed # user already existed
@ -252,7 +157,10 @@ class User(UserBase):
Authenticate with name/password against users in database. Authenticate with name/password against users in database.
""" """
if (db_user := models.User.load(db, self.name)) is None: db_user = models.User(name=self.name)
db.refresh(db_user)
if db_user is None:
# nonexistent user, fake doing password verification # nonexistent user, fake doing password verification
crypt_context.dummy_verify() crypt_context.dummy_verify()
return False return False
@ -273,18 +181,16 @@ class User(UserBase):
Update this user in the database. Update this user in the database.
""" """
old_dbuser = models.User.load(db, self.name) db_user = models.User(name=self.name)
old_user = self.from_orm(old_dbuser) db.refresh(db_user)
for capability in self.capabilities: for capability in db_user.capabilities:
if capability not in old_user.capabilities: db.delete(capability)
old_dbuser.capabilities.append(
models.UserCapability(capability=capability.value)
)
for capability in old_dbuser.capabilities: db_user.capabilities = [
if UserCapability.from_value(capability) not in self.capabilities: capability.model
db.delete(capability) for capability in self.capabilities
]
db.commit() db.commit()
@ -296,10 +202,84 @@ class User(UserBase):
Delete this user from the database. Delete this user from the database.
""" """
if (db_user := models.User.load(db, self.name)) is None: db_user = models.User(name=self.name)
db.refresh(db_user)
if db_user is None:
# nonexistent user # nonexistent user
return False return False
db.delete(db_user) db.delete(db_user)
db.commit() db.commit()
return True return True
##########
# table: devices
##########
class DeviceBase(BaseModel):
name: str
type: str
expiry: datetime
class DeviceCreate(DeviceBase):
owner_name: str
class Device(DeviceBase):
class Config:
orm_mode = True
@classmethod
def create(
cls,
db: Session,
device: DeviceCreate,
) -> Device | None:
"""
Create a new device in the database.
"""
try:
db_device = models.Device(
owner_name=device.owner_name,
name=device.name,
type=device.type,
expiry=device.expiry,
)
db.add(db_device)
db.commit()
db.refresh(db_device)
return cls.from_orm(db_device)
except IntegrityError:
# device already existed
pass
def delete(
self,
db: Session,
) -> bool:
"""
Delete this device from the database.
"""
db_device = models.Device(
# owner_name=
name=self.name,
)
db.refresh(db_device)
if db_device is None:
# nonexistent device
return False
db.delete(db_device)
db.commit()
return True

View file

@ -16,11 +16,10 @@
## User caps ## User caps
- admin: administrator - admin: administrator
- login: can log into the web interface - login: can log into the web interface
- certify: can certify own devices without approval - issue: can certify own devices without approval
- renew: can renew certificates for own devices - renew: can renew certificates for own devices
## Device props ## Device props
- name - name
- type (icon) - type (icon)
- is active - expiry
- certified until