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
import datetime
from sqlalchemy import (Boolean, Column, DateTime, ForeignKey, Integer, String,
from sqlalchemy import (Column, DateTime, ForeignKey, Integer, String,
UniqueConstraint)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session, relationship
from sqlalchemy.orm import relationship
ORMBaseModel = declarative_base()
@ -56,7 +54,7 @@ class Device(ORMBaseModel):
owner_name = Column(String, ForeignKey("users.name"))
name = Column(String)
type = Column(String)
expiry = Column(DateTime, default=datetime.datetime.now)
expiry = Column(DateTime)
owner: User = relationship(
"User", lazy="joined", back_populates="distinguished_names"

View file

@ -10,124 +10,12 @@ from enum import Enum
from typing import Any
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.orm import Session
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
##########
@ -135,6 +23,9 @@ class Certificate(CertificateBase):
class UserCapability(Enum):
admin = "admin"
login = "login"
issue = "issue"
renew = "renew"
def __repr__(self) -> str:
return self.value
@ -157,6 +48,13 @@ class UserCapability(Enum):
# create from string representation
return cls(str(value))
@property
def model(self) -> models.UserCapability:
return models.UserCapability(
capability=self.value,
)
##########
# table: users
##########
@ -165,19 +63,23 @@ class UserCapability(Enum):
class UserBase(BaseModel):
name: str
country: str
state: str
city: str
organization: str
organizational_unit: str
email: str
capabilities: list[UserCapability] = []
class UserCreate(UserBase):
password: str
class User(UserBase):
capabilities: list[UserCapability] = []
distinguished_names: list[DistinguishedName] = Field(
default=[], repr=False
)
certificates: list[Certificate] = Field(
devices: list[Device] = Field(
default=[], repr=False
)
@ -206,8 +108,8 @@ class User(UserBase):
Load user from database by name.
"""
if (db_user := models.User.load(db, name)) is None:
return None
db_user = models.User(name=name)
db.refresh(db_user)
return cls.from_orm(db_user)
@ -223,17 +125,20 @@ class User(UserBase):
"""
try:
user = models.User(
db_user = models.User(
name=user.name,
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.refresh(user)
db.refresh(db_user)
return cls.from_orm(user)
return cls.from_orm(db_user)
except IntegrityError:
# user already existed
@ -252,7 +157,10 @@ class User(UserBase):
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
crypt_context.dummy_verify()
return False
@ -273,18 +181,16 @@ class User(UserBase):
Update this user in the database.
"""
old_dbuser = models.User.load(db, self.name)
old_user = self.from_orm(old_dbuser)
db_user = models.User(name=self.name)
db.refresh(db_user)
for capability in self.capabilities:
if capability not in old_user.capabilities:
old_dbuser.capabilities.append(
models.UserCapability(capability=capability.value)
)
for capability in db_user.capabilities:
db.delete(capability)
for capability in old_dbuser.capabilities:
if UserCapability.from_value(capability) not in self.capabilities:
db.delete(capability)
db_user.capabilities = [
capability.model
for capability in self.capabilities
]
db.commit()
@ -296,10 +202,84 @@ class User(UserBase):
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
return False
db.delete(db_user)
db.commit()
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
- admin: administrator
- 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
## Device props
- name
- type (icon)
- is active
- certified until
- expiry