diff --git a/api/kiwi_vpn_api/db/device.py b/api/kiwi_vpn_api/db/device.py index 7dc46de..46750cd 100644 --- a/api/kiwi_vpn_api/db/device.py +++ b/api/kiwi_vpn_api/db/device.py @@ -23,7 +23,6 @@ class DeviceBase(SQLModel): name: str type: str - expiry: datetime | None class DeviceCreate(DeviceBase): @@ -37,11 +36,13 @@ class DeviceRead(DeviceBase): Representation of a device read via the API """ - approved: bool - owner_name: str | None + id: int | None = Field(primary_key=True) + approved: bool | None = Field(default=None) + expiry: datetime | None = Field(default=None) + owner_name: str = Field(foreign_key="user.name") -class Device(DeviceBase, table=True): +class Device(DeviceRead, table=True): """ Representation of `device` table """ @@ -51,10 +52,6 @@ class Device(DeviceBase, table=True): "name", ),) - id: int | None = Field(primary_key=True) - approved: bool = Field(default=False) - owner_name: str | None = Field(foreign_key="user.name") - # no idea, but "User" (in quotes) doesn't work here # might be a future problem? owner: User = Relationship( @@ -74,8 +71,7 @@ class Device(DeviceBase, table=True): try: with Connection.session as db: - new_device = cls.from_orm(device) - new_device.owner = owner + new_device = cls.from_orm(device, {"owner_name": owner.name}) db.add(new_device) db.commit() diff --git a/api/kiwi_vpn_api/db/user.py b/api/kiwi_vpn_api/db/user.py index f619b52..2f2c50d 100644 --- a/api/kiwi_vpn_api/db/user.py +++ b/api/kiwi_vpn_api/db/user.py @@ -281,3 +281,23 @@ class User(UserBase, table=True): # deny be default return False + + def can_issue(self, device: Device) -> bool: + """ + Check if this user can issue a certificate without approval. + """ + + return ( + device.approved in (None, False) + and (self.is_admin or self.has_tag(TagValue.issue)) + ) + + def can_renew(self, device: Device) -> bool: + """ + Check if this user can renew a certificate without approval. + """ + + return ( + device.approved is True + and (self.is_admin or self.has_tag(TagValue.renew)) + ) diff --git a/api/kiwi_vpn_api/routers/device.py b/api/kiwi_vpn_api/routers/device.py index 3d9ae71..7f12fdc 100644 --- a/api/kiwi_vpn_api/routers/device.py +++ b/api/kiwi_vpn_api/routers/device.py @@ -2,6 +2,8 @@ /device endpoints. """ +from datetime import datetime + from fastapi import APIRouter, Depends, HTTPException, status from ..db import Connection, Device, DeviceCreate, DeviceRead, User @@ -106,7 +108,20 @@ async def request_certificate( db.add(device) dn = DistinguishedName.build(device) - easy_rsa.issue( - dn=dn, - cert_type=CertificateType.server, - ) + if current_user.can_issue(device): + device.approved = True + + if (cert := easy_rsa.issue( + dn=dn, + cert_type=CertificateType.server, + )) is not None: + assert (expiry := cert.get_notAfter()) is not None + + date_format, encoding = "%Y%m%d%H%M%SZ", "ascii" + expiry = datetime.strptime( + expiry.decode(encoding), + date_format, + ) + device.expiry = expiry + + db.commit() diff --git a/api/plan.md b/api/plan.md index 09120c9..7504e6a 100644 --- a/api/plan.md +++ b/api/plan.md @@ -26,6 +26,11 @@ - approved: bool - expiry +## Device status +- created (approved = NULL): device has been newly created +- requested (approved = false): certificate has been requested (issue or renew) +- issued (approved = true): certificate has been granted (may be expired) + ## Permissions - admin cannot "admin" itself (to prevent self decapitation) - admin can "edit", "admin" and "create" everything else @@ -37,5 +42,5 @@ - admin: add or remove tag, delete, generate password ### Device -- edit: change type, delete +- edit: change type, delete, request - admin: approve