Complete admin package restructuring

This commit is contained in:
2025-05-10 08:28:15 +02:00
parent 4f970a3f71
commit 0a427b6a91
80 changed files with 1282 additions and 843 deletions

View File

@ -0,0 +1,237 @@
"""clients view factory."""
# pyright: reportUnusedFunction=false
import ipaddress
import logging
import uuid
from typing import Annotated
from fastapi import APIRouter, Depends, Form, Request, Response
from pydantic import BaseModel, IPvAnyAddress, IPvAnyNetwork
from sshecret.backend import ClientFilter
from sshecret.backend.models import FilterType
from sshecret.crypto import validate_public_key
from sshecret_admin.auth import User
from sshecret_admin.services import AdminBackend
from ..dependencies import FrontendDependencies
LOG = logging.getLogger(__name__)
class ClientUpdate(BaseModel):
id: uuid.UUID
name: str
description: str
public_key: str
sources: str | None = None
class ClientCreate(BaseModel):
name: str
public_key: str
description: str | None
sources: str | None
def create_router(dependencies: FrontendDependencies) -> APIRouter:
"""Create clients router."""
app = APIRouter()
templates = dependencies.templates
@app.get("/clients")
async def get_clients(
request: Request,
current_user: Annotated[User, Depends(dependencies.get_user_from_access_token)],
admin: Annotated[AdminBackend, Depends(dependencies.get_admin_backend)],
) -> Response:
"""Get clients."""
clients = await admin.get_clients()
LOG.info("Clients %r", clients)
return templates.TemplateResponse(
request,
"clients/index.html.j2",
{
"page_title": "Clients",
"clients": clients,
"user": current_user.username,
},
)
@app.post("/clients/query")
async def query_clients(
request: Request,
_current_user: Annotated[
User, Depends(dependencies.get_user_from_access_token)
],
admin: Annotated[AdminBackend, Depends(dependencies.get_admin_backend)],
query: Annotated[str, Form()],
) -> Response:
"""Query for a client."""
query_filter: ClientFilter | None = None
if query:
name = f"%{query}%"
query_filter = ClientFilter(name=name, filter_name=FilterType.LIKE)
clients = await admin.get_clients(query_filter)
return templates.TemplateResponse(
request,
"clients/inner.html.j2",
{
"clients": clients,
},
)
@app.put("/clients/{id}")
async def update_client(
request: Request,
id: str,
_current_user: Annotated[
User, Depends(dependencies.get_user_from_access_token)
],
admin: Annotated[AdminBackend, Depends(dependencies.get_admin_backend)],
client: Annotated[ClientUpdate, Form()],
):
"""Update a client."""
original_client = await admin.get_client(id)
if not original_client:
return templates.TemplateResponse(
request, "fragments/error.html", {"message": "Client not found"}
)
sources: list[IPvAnyAddress | IPvAnyNetwork] = []
if client.sources:
source_str = client.sources.split(",")
for source in source_str:
if "/" in source:
sources.append(ipaddress.ip_network(source.strip()))
else:
sources.append(ipaddress.ip_address(source.strip()))
client_fields = client.model_dump(exclude_unset=True)
del client_fields["sources"]
if sources:
client_fields["policies"] = sources
LOG.info("Fields: %r", client_fields)
updated_client = original_client.model_copy(update=client_fields)
await admin.update_client(updated_client)
clients = await admin.get_clients()
headers = {"Hx-Refresh": "true"}
return templates.TemplateResponse(
request,
"clients/inner.html.j2",
{
"clients": clients,
},
headers=headers,
)
@app.delete("/clients/{id}")
async def delete_client(
request: Request,
id: str,
_current_user: Annotated[
User, Depends(dependencies.get_user_from_access_token)
],
admin: Annotated[AdminBackend, Depends(dependencies.get_admin_backend)],
) -> Response:
"""Delete a client."""
await admin.delete_client(id)
clients = await admin.get_clients()
headers = {"Hx-Refresh": "true"}
return templates.TemplateResponse(
request,
"clients/inner.html.j2",
{
"clients": clients,
},
headers=headers,
)
@app.post("/clients/")
async def create_client(
request: Request,
_current_user: Annotated[
User, Depends(dependencies.get_user_from_access_token)
],
admin: Annotated[AdminBackend, Depends(dependencies.get_admin_backend)],
client: Annotated[ClientCreate, Form()],
) -> Response:
"""Create client."""
sources: list[str] | None = None
if client.sources:
sources = [source.strip() for source in client.sources.split(",")]
await admin.create_client(
client.name, client.public_key.rstrip(), client.description, sources
)
clients = await admin.get_clients()
headers = {"Hx-Refresh": "true"}
return templates.TemplateResponse(
request,
"clients/inner.html.j2",
{
"clients": clients,
},
headers=headers,
)
@app.post("/clients/validate/source")
async def validate_client_source(
request: Request,
_current_user: Annotated[
User, Depends(dependencies.get_user_from_access_token)
],
sources: Annotated[str, Form()],
) -> Response:
"""Validate source."""
source_str = sources.split(",")
for source in source_str:
if "/" in source:
try:
_network = ipaddress.ip_network(source.strip())
except Exception:
return templates.TemplateResponse(
request,
"/clients/field_invalid.html.j2",
{"explanation": f"Invalid network {source.strip()}"},
)
else:
try:
_address = ipaddress.ip_address(source.strip())
except Exception:
return templates.TemplateResponse(
request,
"/clients/field_invalid.html.j2",
{"explanation": f"Invalid address {source.strip()}"},
)
return templates.TemplateResponse(
request,
"/clients/field_valid.html.j2",
)
@app.post("/clients/validate/public_key")
async def validate_client_public_key(
request: Request,
_current_user: Annotated[
User, Depends(dependencies.get_user_from_access_token)
],
public_key: Annotated[str, Form()],
) -> Response:
"""Validate source."""
if validate_public_key(public_key.rstrip()):
return templates.TemplateResponse(
request,
"/clients/field_valid.html.j2",
)
return templates.TemplateResponse(
request,
"/clients/field_invalid.html.j2",
{"explanation": "Invalid value. Not a valid SSH RSA Public Key."},
)
return app