kiwi-vpn/api/kiwi_vpn_api/db/user.py

155 lines
3.8 KiB
Python
Raw Normal View History

2022-03-27 01:17:48 +00:00
from __future__ import annotations
2022-03-28 00:48:44 +00:00
from typing import Any
2022-03-27 01:17:48 +00:00
from pydantic import root_validator
from sqlalchemy.exc import IntegrityError
2022-03-27 13:47:38 +00:00
from sqlmodel import Field, Relationship, SQLModel
2022-03-27 01:17:48 +00:00
from ..config import Config
2022-03-28 00:50:00 +00:00
from .capability import Capability, UserCapability
2022-03-27 01:17:48 +00:00
from .connection import Connection
2022-03-28 00:43:28 +00:00
from .device import Device
2022-03-27 01:17:48 +00:00
class UserBase(SQLModel):
name: str = Field(primary_key=True)
2022-03-28 01:00:07 +00:00
email: str | None = Field(default=None)
2022-03-27 01:17:48 +00:00
country: str | None = Field(default=None)
state: str | None = Field(default=None)
city: str | None = Field(default=None)
organization: str | None = Field(default=None)
organizational_unit: str | None = Field(default=None)
class User(UserBase, table=True):
password: str
2022-03-27 13:47:38 +00:00
capabilities: list[UserCapability] = Relationship(
back_populates="user",
sa_relationship_kwargs={
"lazy": "joined",
"cascade": "all, delete-orphan",
},
)
2022-03-28 00:43:28 +00:00
devices: list[Device] = Relationship(
back_populates="owner",
)
2022-03-27 01:17:48 +00:00
@classmethod
def create(cls, **kwargs) -> User | None:
"""
Create a new user in the database.
"""
try:
with Connection.session as db:
2022-03-27 01:22:28 +00:00
user = cls.from_orm(UserCreate(**kwargs))
2022-03-27 01:17:48 +00:00
db.add(user)
db.commit()
db.refresh(user)
return user
except IntegrityError:
# user already existed
return None
@classmethod
def get(cls, name: str) -> User | None:
2022-03-27 13:47:18 +00:00
"""
Load user from database by name.
"""
2022-03-27 01:17:48 +00:00
with Connection.session as db:
return db.get(cls, name)
2022-03-27 13:47:18 +00:00
@classmethod
def authenticate(
cls,
name: str,
password: str,
) -> User | None:
"""
Authenticate with name/password against users in database.
"""
2022-03-28 02:23:00 +00:00
crypt_context = Config._.crypto.crypt_context
2022-03-27 13:47:18 +00:00
if (user := cls.get(name)) is None:
# nonexistent user, fake doing password verification
crypt_context.dummy_verify()
return None
if not crypt_context.verify(password, user.password):
# password hash mismatch
return None
2022-03-27 01:17:48 +00:00
2022-03-27 13:47:18 +00:00
return user
def update(self) -> None:
"""
Update this user in the database.
"""
with Connection.session as db:
db.add(self)
db.commit()
db.refresh(self)
def delete(self) -> bool:
"""
Delete this user from the database.
"""
with Connection.session as db:
db.delete(self)
db.commit()
2022-03-27 13:47:38 +00:00
2022-03-28 01:28:49 +00:00
def can(self, capability: Capability) -> bool:
return capability in self.get_capabilities()
2022-03-28 00:48:44 +00:00
def get_capabilities(self) -> set[Capability]:
return set(
capability._
for capability in self.capabilities
)
2022-03-27 13:47:38 +00:00
2022-03-28 00:48:44 +00:00
def set_capabilities(self, capabilities: set[Capability]) -> None:
self.capabilities = [
UserCapability(
2022-03-27 13:47:38 +00:00
user_name=self.name,
capability_name=capability.value,
2022-03-28 00:48:44 +00:00
) for capability in capabilities
]
2022-03-27 13:47:38 +00:00
2022-03-27 01:17:48 +00:00
class UserCreate(UserBase):
password: str | None = Field(default=None)
password_clear: str | None = Field(default=None)
@root_validator
@classmethod
def hash_password(cls, values: dict[str, Any]) -> dict[str, Any]:
if (values.get("password")) is not None:
# password is set
return values
if (password_clear := values.get("password_clear")) is None:
raise ValueError("No password to hash")
2022-03-28 02:15:42 +00:00
if (current_config := Config._) is None:
2022-03-27 01:17:48 +00:00
raise ValueError("Not configured")
2022-03-28 02:23:00 +00:00
values["password"] = current_config.crypto.crypt_context.hash(
2022-03-27 01:17:48 +00:00
password_clear)
return values
class UserRead(UserBase):
pass