Compare commits
No commits in common. "821d72a7736ddaed552e486cb5be487de4182f20" and "3b79efaa802d7225618bc7365e05ded7e364dd85" have entirely different histories.
821d72a773
...
3b79efaa80
7 changed files with 19 additions and 64 deletions
|
|
@ -143,7 +143,8 @@ class User(UserBase, table=True):
|
||||||
# password hash mismatch
|
# password hash mismatch
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not (user.has_tag(TagValue.login) or user.is_admin):
|
if not (user.has_tag(TagValue.login)
|
||||||
|
or user.has_tag(TagValue.admin)):
|
||||||
# no login permission
|
# no login permission
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -168,8 +169,7 @@ class User(UserBase, table=True):
|
||||||
db.delete(self)
|
db.delete(self)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
@property
|
def _get_tags(self) -> Iterable[TagValue]:
|
||||||
def __tags(self) -> Iterable[TagValue]:
|
|
||||||
"""
|
"""
|
||||||
Return the tags of this user.
|
Return the tags of this user.
|
||||||
"""
|
"""
|
||||||
|
|
@ -184,15 +184,7 @@ class User(UserBase, table=True):
|
||||||
Check if this user has a tag.
|
Check if this user has a tag.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return tag in self.__tags
|
return tag in self._get_tags()
|
||||||
|
|
||||||
@property
|
|
||||||
def is_admin(self) -> bool:
|
|
||||||
"""
|
|
||||||
Shorthand for checking if this user has the `admin` tag.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return TagValue.admin in self.__tags
|
|
||||||
|
|
||||||
def add_tags(
|
def add_tags(
|
||||||
self,
|
self,
|
||||||
|
|
@ -204,7 +196,7 @@ class User(UserBase, table=True):
|
||||||
|
|
||||||
self.tags = [
|
self.tags = [
|
||||||
tag._(self)
|
tag._(self)
|
||||||
for tag in (set(self.__tags) | set(tags))
|
for tag in (set(self._get_tags()) | set(tags))
|
||||||
]
|
]
|
||||||
|
|
||||||
def remove_tags(
|
def remove_tags(
|
||||||
|
|
@ -217,7 +209,7 @@ class User(UserBase, table=True):
|
||||||
|
|
||||||
self.tags = [
|
self.tags = [
|
||||||
tag._(self)
|
tag._(self)
|
||||||
for tag in (set(self.__tags) - set(tags))
|
for tag in (set(self._get_tags()) - set(tags))
|
||||||
]
|
]
|
||||||
|
|
||||||
def can_edit(
|
def can_edit(
|
||||||
|
|
@ -229,7 +221,7 @@ class User(UserBase, table=True):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# admin can "edit" everything
|
# admin can "edit" everything
|
||||||
if self.is_admin:
|
if self.has_tag(TagValue.admin):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# user can "edit" itself
|
# user can "edit" itself
|
||||||
|
|
@ -248,7 +240,7 @@ class User(UserBase, table=True):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# only admin can "admin" anything
|
# only admin can "admin" anything
|
||||||
if not self.is_admin:
|
if not self.has_tag(TagValue.admin):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# admin canot "admin itself"!
|
# admin canot "admin itself"!
|
||||||
|
|
@ -272,7 +264,7 @@ class User(UserBase, table=True):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# admin can "create" everything
|
# admin can "create" everything
|
||||||
if self.is_admin:
|
if self.has_tag(TagValue.admin):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# user can only create devices for itself
|
# user can only create devices for itself
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ from __future__ import annotations
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum, auto
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from OpenSSL import crypto
|
from OpenSSL import crypto
|
||||||
|
|
@ -98,19 +97,6 @@ class DistinguishedName(BaseModel):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class CertificateType(Enum):
|
|
||||||
"""
|
|
||||||
Possible types of certificates
|
|
||||||
"""
|
|
||||||
|
|
||||||
ca = auto()
|
|
||||||
client = auto()
|
|
||||||
server = auto()
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return self._name_
|
|
||||||
|
|
||||||
|
|
||||||
class EasyRSA:
|
class EasyRSA:
|
||||||
"""
|
"""
|
||||||
Represents an EasyRSA PKI.
|
Represents an EasyRSA PKI.
|
||||||
|
|
@ -239,7 +225,6 @@ class EasyRSA:
|
||||||
Path("ca.crt"),
|
Path("ca.crt"),
|
||||||
Config._.crypto.ca_expiry_days,
|
Config._.crypto.ca_expiry_days,
|
||||||
|
|
||||||
"--dn-mode=cn_only",
|
|
||||||
"--req-cn=kiwi-vpn-ca",
|
"--req-cn=kiwi-vpn-ca",
|
||||||
|
|
||||||
"build-ca",
|
"build-ca",
|
||||||
|
|
@ -251,17 +236,13 @@ class EasyRSA:
|
||||||
|
|
||||||
def issue(
|
def issue(
|
||||||
self,
|
self,
|
||||||
cert_type: CertificateType = CertificateType.client,
|
cert_type: str = "client",
|
||||||
dn: DistinguishedName = DistinguishedName.build(),
|
dn: DistinguishedName = DistinguishedName.build(),
|
||||||
) -> crypto.X509 | None:
|
) -> crypto.X509:
|
||||||
"""
|
"""
|
||||||
Issue a client or server certificate
|
Issue a client or server certificate
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not (cert_type is CertificateType.client
|
|
||||||
or cert_type is CertificateType.server):
|
|
||||||
return None
|
|
||||||
|
|
||||||
return self.__build_cert(
|
return self.__build_cert(
|
||||||
Path(f"issued/{dn.common_name}.crt"),
|
Path(f"issued/{dn.common_name}.crt"),
|
||||||
Config._.crypto.cert_expiry_days,
|
Config._.crypto.cert_expiry_days,
|
||||||
|
|
@ -281,7 +262,7 @@ if __name__ == "__main__":
|
||||||
easy_rsa.init_pki()
|
easy_rsa.init_pki()
|
||||||
|
|
||||||
ca = easy_rsa.build_ca()
|
ca = easy_rsa.build_ca()
|
||||||
server = easy_rsa.issue(CertificateType.server)
|
server = easy_rsa.issue("server")
|
||||||
client = None
|
client = None
|
||||||
|
|
||||||
# check if configured
|
# check if configured
|
||||||
|
|
@ -294,7 +275,7 @@ if __name__ == "__main__":
|
||||||
db.add(device)
|
db.add(device)
|
||||||
dn = DistinguishedName.build(device)
|
dn = DistinguishedName.build(device)
|
||||||
|
|
||||||
client = easy_rsa.issue(dn=dn)
|
client = easy_rsa.issue("client", dn)
|
||||||
|
|
||||||
date_format, encoding = "%Y%m%d%H%M%SZ", "ascii"
|
date_format, encoding = "%Y%m%d%H%M%SZ", "ascii"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,6 @@ class Responses:
|
||||||
"description": "Operation not permitted",
|
"description": "Operation not permitted",
|
||||||
"content": None,
|
"content": None,
|
||||||
}
|
}
|
||||||
ENTRY_ADDED = {
|
|
||||||
"description": "Entry added to database",
|
|
||||||
"content": None,
|
|
||||||
}
|
|
||||||
ENTRY_EXISTS = {
|
ENTRY_EXISTS = {
|
||||||
"description": "Entry exists in database",
|
"description": "Entry exists in database",
|
||||||
"content": None,
|
"content": None,
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ async def set_config(
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# check permissions
|
# check permissions
|
||||||
if not current_user.is_admin:
|
if not current_user.has_tag(TagValue.admin):
|
||||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
# update config file, reconnect to database
|
# update config file, reconnect to database
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ router = APIRouter(prefix="/device", tags=["device"])
|
||||||
@router.post(
|
@router.post(
|
||||||
"/{user_name}",
|
"/{user_name}",
|
||||||
responses={
|
responses={
|
||||||
status.HTTP_201_CREATED: Responses.ENTRY_ADDED,
|
status.HTTP_200_OK: Responses.OK,
|
||||||
status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED,
|
status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED,
|
||||||
status.HTTP_401_UNAUTHORIZED: Responses.NEEDS_USER,
|
status.HTTP_401_UNAUTHORIZED: Responses.NEEDS_USER,
|
||||||
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
|
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
|
||||||
|
|
@ -22,7 +22,6 @@ router = APIRouter(prefix="/device", tags=["device"])
|
||||||
status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS,
|
status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS,
|
||||||
},
|
},
|
||||||
response_model=DeviceRead,
|
response_model=DeviceRead,
|
||||||
status_code=status.HTTP_201_CREATED,
|
|
||||||
)
|
)
|
||||||
async def add_device(
|
async def add_device(
|
||||||
device: DeviceCreate,
|
device: DeviceCreate,
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,7 @@
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
|
||||||
from ..config import Config
|
from ..config import Config
|
||||||
from ..db import User
|
from ._common import Responses, get_current_config
|
||||||
from ..easyrsa import CertificateType, EasyRSA
|
|
||||||
from ._common import Responses, get_current_config, get_current_user
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/service", tags=["service"])
|
router = APIRouter(prefix="/service", tags=["service"])
|
||||||
|
|
||||||
|
|
@ -22,14 +20,5 @@ router = APIRouter(prefix="/service", tags=["service"])
|
||||||
)
|
)
|
||||||
async def init_pki(
|
async def init_pki(
|
||||||
_: Config = Depends(get_current_config),
|
_: Config = Depends(get_current_config),
|
||||||
current_user: User = Depends(get_current_user),
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
pass
|
||||||
if not current_user.is_admin:
|
|
||||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
|
||||||
|
|
||||||
easy_rsa = EasyRSA()
|
|
||||||
|
|
||||||
easy_rsa.init_pki()
|
|
||||||
easy_rsa.build_ca()
|
|
||||||
easy_rsa.issue(CertificateType.server)
|
|
||||||
|
|
|
||||||
|
|
@ -63,14 +63,13 @@ async def get_current_user_route(
|
||||||
@router.post(
|
@router.post(
|
||||||
"",
|
"",
|
||||||
responses={
|
responses={
|
||||||
status.HTTP_201_CREATED: Responses.ENTRY_ADDED,
|
status.HTTP_200_OK: Responses.OK,
|
||||||
status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED,
|
status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED,
|
||||||
status.HTTP_401_UNAUTHORIZED: Responses.NEEDS_USER,
|
status.HTTP_401_UNAUTHORIZED: Responses.NEEDS_USER,
|
||||||
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
|
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
|
||||||
status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS,
|
status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS,
|
||||||
},
|
},
|
||||||
response_model=UserRead,
|
response_model=UserRead,
|
||||||
status_code=status.HTTP_201_CREATED,
|
|
||||||
)
|
)
|
||||||
async def add_user(
|
async def add_user(
|
||||||
user: UserCreate,
|
user: UserCreate,
|
||||||
|
|
@ -128,12 +127,11 @@ async def remove_user(
|
||||||
@router.post(
|
@router.post(
|
||||||
"/{user_name}/tags",
|
"/{user_name}/tags",
|
||||||
responses={
|
responses={
|
||||||
status.HTTP_201_CREATED: Responses.ENTRY_ADDED,
|
status.HTTP_200_OK: Responses.OK,
|
||||||
status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED,
|
status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED,
|
||||||
status.HTTP_401_UNAUTHORIZED: Responses.NEEDS_USER,
|
status.HTTP_401_UNAUTHORIZED: Responses.NEEDS_USER,
|
||||||
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
|
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
|
||||||
},
|
},
|
||||||
status_code=status.HTTP_201_CREATED,
|
|
||||||
)
|
)
|
||||||
async def extend_tags(
|
async def extend_tags(
|
||||||
tags: list[TagValue],
|
tags: list[TagValue],
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue