diff --git a/api/kiwi_vpn_api/db/__init__.py b/api/kiwi_vpn_api/db/__init__.py deleted file mode 100644 index 80b582e..0000000 --- a/api/kiwi_vpn_api/db/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from . import models, schemata -from .connection import Connection - -__all__ = ["Connection", "models", "schemata"] diff --git a/api/kiwi_vpn_api/db/connection.py b/api/kiwi_vpn_api/db/connection.py deleted file mode 100644 index 97592e1..0000000 --- a/api/kiwi_vpn_api/db/connection.py +++ /dev/null @@ -1,75 +0,0 @@ -""" -Utilities for handling SQLAlchemy database connections. -""" - -from typing import Generator - -from sqlalchemy.engine import Engine -from sqlalchemy.orm import Session, sessionmaker - -from .models import ORMBaseModel - - -class SessionManager: - """ - Simple context manager for an ORM session. - """ - - __session: Session - - def __init__(self, session: Session) -> None: - self.__session = session - - def __enter__(self) -> Session: - return self.__session - - def __exit__(self, *args) -> None: - self.__session.close() - - -class Connection: - """ - Namespace for the database connection. - """ - - engine: Engine | None = None - session_local: sessionmaker | None = None - - @classmethod - def connect(cls, engine: Engine) -> None: - """ - Connect ORM to a database engine. - """ - - cls.engine = engine - cls.session_local = sessionmaker( - autocommit=False, autoflush=False, bind=engine, - ) - ORMBaseModel.metadata.create_all(bind=engine) - - @classmethod - def use(cls) -> SessionManager | None: - """ - Create an ORM session using a context manager. - """ - - if cls.session_local is None: - return None - - return SessionManager(cls.session_local()) - - @classmethod - async def get(cls) -> Generator[Session | None, None, None]: - """ - Create an ORM session using a FastAPI compatible async generator. - """ - - if cls.session_local is None: - yield None - - else: - db = cls.session_local() - try: - yield db - finally: - db.close() diff --git a/api/kiwi_vpn_api/db/models.py b/api/kiwi_vpn_api/db/models.py deleted file mode 100644 index fc41d6f..0000000 --- a/api/kiwi_vpn_api/db/models.py +++ /dev/null @@ -1,82 +0,0 @@ -""" -SQLAlchemy representation of database contents. -""" - -from __future__ import annotations - -from sqlalchemy import (Column, DateTime, ForeignKey, Integer, String, - UniqueConstraint) -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import Session, relationship - -ORMBaseModel = declarative_base() - - -class UserCapability(ORMBaseModel): - __tablename__ = "user_capabilities" - - user_name = Column( - String, - ForeignKey("users.name"), - primary_key=True, - index=True, - ) - capability = Column(String, primary_key=True) - - -class User(ORMBaseModel): - __tablename__ = "users" - - name = Column(String, primary_key=True, index=True) - password = Column(String, nullable=False) - email = Column(String) - - country = Column(String(2)) - state = Column(String) - city = Column(String) - organization = Column(String) - organizational_unit = Column(String) - - capabilities: list[UserCapability] = relationship( - "UserCapability", lazy="joined", cascade="all, delete-orphan" - ) - devices: list[Device] = relationship( - "Device", lazy="select", back_populates="owner" - ) - - @classmethod - def from_db( - cls, - db: Session, - name: str, - ) -> User | None: - """ - Load user from database by name. - """ - - return ( - db - .query(cls) - .filter(cls.name == name) - .first() - ) - - -class Device(ORMBaseModel): - __tablename__ = "devices" - - id = Column(Integer, primary_key=True, autoincrement=True) - - owner_name = Column(String, ForeignKey("users.name")) - name = Column(String) - type = Column(String) - expiry = Column(DateTime) - - owner: User = relationship( - "User", lazy="joined", back_populates="devices" - ) - - UniqueConstraint( - owner_name, - name, - ) diff --git a/api/kiwi_vpn_api/db/schemata/__init__.py b/api/kiwi_vpn_api/db/schemata/__init__.py deleted file mode 100644 index 5a991aa..0000000 --- a/api/kiwi_vpn_api/db/schemata/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .device import Device, DeviceBase, DeviceCreate -from .user import User, UserBase, UserCreate -from .user_capability import UserCapability - -__all__ = ["Device", "DeviceBase", "DeviceCreate", - "User", "UserBase", "UserCreate", "UserCapability"] diff --git a/api/kiwi_vpn_api/db/schemata/device.py b/api/kiwi_vpn_api/db/schemata/device.py deleted file mode 100644 index 8e47f7e..0000000 --- a/api/kiwi_vpn_api/db/schemata/device.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -Pydantic representation of database contents. -""" - -from __future__ import annotations - -from datetime import datetime - -from pydantic import BaseModel -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import Session - -from .. import models - - -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 - return None - - 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 diff --git a/api/kiwi_vpn_api/db/schemata/user.py b/api/kiwi_vpn_api/db/schemata/user.py deleted file mode 100644 index b31e31e..0000000 --- a/api/kiwi_vpn_api/db/schemata/user.py +++ /dev/null @@ -1,164 +0,0 @@ -""" -Pydantic representation of database contents. -""" - -from __future__ import annotations - -from typing import Any - -from passlib.context import CryptContext -from pydantic import BaseModel, Field, validator -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import Session - -from .. import models -from .device import Device -from .user_capability import UserCapability - - -class UserBase(BaseModel): - name: str - email: str - - capabilities: list[UserCapability] = [] - - country: str | None = Field(default=None, repr=False) - state: str | None = Field(default=None, repr=False) - city: str | None = Field(default=None, repr=False) - organization: str | None = Field(default=None, repr=False) - organizational_unit: str | None = Field(default=None, repr=False) - - -class UserCreate(UserBase): - password: str - - -class User(UserBase): - devices: list[Device] = Field( - default=[], repr=False - ) - - class Config: - orm_mode = True - - @validator("capabilities", pre=True) - @classmethod - def unify_capabilities(cls, value: list[Any]) -> list[UserCapability]: - """ - Import the capabilities from various formats - """ - - return [ - UserCapability.from_value(capability) - for capability in value - ] - - @classmethod - def from_db( - cls, - db: Session, - name: str, - ) -> User | None: - """ - Load user from database by name. - """ - - if (db_user := models.User.from_db(db, name)) is None: - return None - - return cls.from_orm(db_user) - - @classmethod - def create( - cls, - db: Session, - user: UserCreate, - crypt_context: CryptContext, - ) -> User | None: - """ - Create a new user in the database. - """ - - try: - db_user = models.User( - name=user.name, - password=crypt_context.hash(user.password), - email=user.email, - capabilities=[ - capability.model - for capability in user.capabilities - ], - ) - - db.add(db_user) - db.commit() - db.refresh(db_user) - - return cls.from_orm(db_user) - - except IntegrityError: - # user already existed - return None - - def is_admin(self) -> bool: - return UserCapability.admin in self.capabilities - - def authenticate( - self, - db: Session, - password: str, - crypt_context: CryptContext, - ) -> User | None: - """ - Authenticate with name/password against users in database. - """ - - if (db_user := models.User.from_db(db, self.name)) is None: - # nonexistent user, fake doing password verification - crypt_context.dummy_verify() - return False - - if not crypt_context.verify(password, db_user.password): - # password hash mismatch - return False - - self.from_orm(db_user) - - return True - - def update( - self, - db: Session, - ) -> None: - """ - Update this user in the database. - """ - - if (db_user := models.User.from_db(db, self.name)) is None: - return None - - for capability in db_user.capabilities: - db.delete(capability) - - db_user.capabilities = [ - capability.model - for capability in self.capabilities - ] - - db.commit() - - def delete( - self, - db: Session, - ) -> bool: - """ - Delete this user from the database. - """ - - if (db_user := models.User.from_db(db, self.name)) is None: - # nonexistent user - return False - - db.delete(db_user) - db.commit() - return True diff --git a/api/kiwi_vpn_api/db/schemata/user_capability.py b/api/kiwi_vpn_api/db/schemata/user_capability.py deleted file mode 100644 index 28e1870..0000000 --- a/api/kiwi_vpn_api/db/schemata/user_capability.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -Pydantic representation of database contents. -""" - -from __future__ import annotations - -from enum import Enum - -from .. import models - - -class UserCapability(Enum): - admin = "admin" - login = "login" - issue = "issue" - renew = "renew" - - def __repr__(self) -> str: - return self.value - - @classmethod - def from_value(cls, value) -> UserCapability: - """ - Create UserCapability from various formats - """ - - if isinstance(value, cls): - # value is already a UserCapability, use that - return value - - elif isinstance(value, models.UserCapability): - # create from db format - return cls(value.capability) - - else: - # create from string representation - return cls(str(value)) - - @property - def model(self) -> models.UserCapability: - return models.UserCapability( - capability=self.value, - )