Standardize IDs, fix group APIs, fix tests
This commit is contained in:
@ -11,9 +11,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.sql import Select
|
||||
from sshecret_backend import audit
|
||||
from sshecret_backend.api.common import (
|
||||
FlexID,
|
||||
IdType,
|
||||
RelaxedId,
|
||||
create_new_client_version,
|
||||
query_active_clients,
|
||||
get_client_by_id,
|
||||
@ -22,6 +19,8 @@ from sshecret_backend.api.common import (
|
||||
reload_client_with_relationships,
|
||||
)
|
||||
from sshecret_backend.models import Client, ClientAccessPolicy
|
||||
|
||||
from sshecret.backend.identifiers import FlexID, IdType, RelaxedId
|
||||
from .schemas import (
|
||||
ClientListParams,
|
||||
ClientCreate,
|
||||
@ -91,7 +90,7 @@ class ClientOperations:
|
||||
) -> Client | None:
|
||||
"""Get client."""
|
||||
if client.type is IdType.ID:
|
||||
client_id = uuid.UUID(client.value)
|
||||
client_id = _id(client.value)
|
||||
else:
|
||||
client_id = await self.get_client_id(
|
||||
client, version=version, include_deleted=include_deleted
|
||||
|
||||
@ -21,7 +21,8 @@ from sshecret_backend.api.clients.schemas import (
|
||||
)
|
||||
from sshecret_backend.api.clients import operations
|
||||
from sshecret_backend.api.clients.operations import ClientOperations
|
||||
from sshecret_backend.api.common import FlexID
|
||||
|
||||
from sshecret.backend.identifiers import FlexID
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -2,13 +2,10 @@
|
||||
|
||||
import re
|
||||
import logging
|
||||
from typing import Self
|
||||
import uuid
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
|
||||
import bcrypt
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import Select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.future import select
|
||||
@ -20,41 +17,6 @@ RE_UUID = re.compile(
|
||||
"^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$"
|
||||
)
|
||||
|
||||
RelaxedId = uuid.UUID | str
|
||||
|
||||
|
||||
class IdType(Enum):
|
||||
"""Id type."""
|
||||
|
||||
ID = "id"
|
||||
NAME = "name"
|
||||
|
||||
|
||||
class FlexID(BaseModel):
|
||||
"""Flexible identifier."""
|
||||
|
||||
type: IdType
|
||||
value: RelaxedId
|
||||
|
||||
@classmethod
|
||||
def id(cls, id: RelaxedId) -> Self:
|
||||
"""Construct from ID."""
|
||||
return cls(type=IdType.ID, value=id)
|
||||
|
||||
@classmethod
|
||||
def name(cls, name: str) -> Self:
|
||||
"""Construct from name."""
|
||||
return cls(type=IdType.NAME, value=name)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, value: str) -> Self:
|
||||
"""Convert from path string."""
|
||||
if value.startswith("id:"):
|
||||
return cls.id(value[3:])
|
||||
elif value.startswith("name:"):
|
||||
return cls.name(value[5:])
|
||||
return cls.name(value)
|
||||
|
||||
|
||||
@dataclass
|
||||
class NewClientVersion:
|
||||
|
||||
@ -10,8 +10,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
from sshecret_backend import audit
|
||||
from sshecret_backend.api.common import (
|
||||
FlexID,
|
||||
IdType,
|
||||
get_client_by_id,
|
||||
resolve_client_id,
|
||||
)
|
||||
@ -24,9 +22,9 @@ from sshecret_backend.api.secrets.schemas import (
|
||||
)
|
||||
from sshecret_backend.models import Client, ClientSecret
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
from sshecret.backend.identifiers import FlexID, IdType, RelaxedId
|
||||
|
||||
RelaxedId = uuid.UUID | str
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _id(id: RelaxedId) -> uuid.UUID:
|
||||
@ -85,6 +83,8 @@ class ClientSecretOperations:
|
||||
return None
|
||||
|
||||
client = await get_client_by_id(self.session, client_id)
|
||||
if client and (client.is_deleted and not self.include_deleted):
|
||||
return None
|
||||
self.client = client
|
||||
return client
|
||||
|
||||
@ -199,15 +199,20 @@ class ClientSecretOperations:
|
||||
|
||||
|
||||
async def resolve_client_secret_mapping(
|
||||
session: AsyncSession,
|
||||
session: AsyncSession, include_deleted_clients: bool = False
|
||||
) -> list[ClientSecretDetailList]:
|
||||
"""Resolve mapping of clients to secrets."""
|
||||
"""Resolve mapping of clients to secrets.
|
||||
|
||||
If a secret is not deleted, but the client is, the secret is returned with
|
||||
no clients attached.
|
||||
"""
|
||||
result = await session.execute(
|
||||
select(ClientSecret)
|
||||
.join(ClientSecret.client)
|
||||
.options(selectinload(ClientSecret.client))
|
||||
.where(Client.is_active.is_not(False))
|
||||
.where(ClientSecret.deleted.is_not(True))
|
||||
.where(Client.is_system.is_not(True))
|
||||
)
|
||||
client_secrets: dict[str, ClientSecretDetailList] = {}
|
||||
for secret in result.scalars().all():
|
||||
@ -216,6 +221,9 @@ async def resolve_client_secret_mapping(
|
||||
client_secrets[secret.name].ids.append(str(secret.id))
|
||||
if not secret.client:
|
||||
continue
|
||||
if secret.client.is_deleted and not include_deleted_clients:
|
||||
continue
|
||||
|
||||
client_secrets[secret.name].clients.append(
|
||||
ClientReference(id=str(secret.client.id), name=secret.client.name)
|
||||
)
|
||||
@ -224,7 +232,10 @@ async def resolve_client_secret_mapping(
|
||||
|
||||
|
||||
async def resolve_client_secret_clients(
|
||||
session: AsyncSession, name: str, include_deleted: bool = False
|
||||
session: AsyncSession,
|
||||
name: str,
|
||||
include_deleted: bool = False,
|
||||
include_deleted_clients: bool = False,
|
||||
) -> ClientSecretDetailList | None:
|
||||
"""Resolve client association to a secret."""
|
||||
statement = (
|
||||
@ -243,6 +254,8 @@ async def resolve_client_secret_clients(
|
||||
clients = ClientSecretDetailList(name=name)
|
||||
clients.ids.append(str(client_secret.id))
|
||||
if client_secret.client and not client_secret.client.is_system:
|
||||
if client_secret.client.is_deleted and not include_deleted_clients:
|
||||
continue
|
||||
clients.clients.append(
|
||||
ClientReference(
|
||||
id=str(client_secret.client.id), name=client_secret.client.name
|
||||
|
||||
@ -8,7 +8,6 @@ from typing import Annotated
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from sshecret_backend.api.common import FlexID
|
||||
from sshecret_backend.types import AsyncDBSessionDep
|
||||
from sshecret_backend.api.secrets.operations import (
|
||||
ClientSecretOperations,
|
||||
@ -22,6 +21,8 @@ from sshecret_backend.api.secrets.schemas import (
|
||||
ClientSecretResponse,
|
||||
)
|
||||
|
||||
from sshecret.backend.identifiers import FlexID
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
"""CLI and main entry point."""
|
||||
|
||||
import code
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
@ -16,10 +15,6 @@ from sqlalchemy.orm import Session
|
||||
from .db import create_api_token, get_engine
|
||||
from .models import (
|
||||
APIClient,
|
||||
AuditLog,
|
||||
Client,
|
||||
ClientAccessPolicy,
|
||||
ClientSecret,
|
||||
SubSystem,
|
||||
)
|
||||
from .settings import BackendSettings
|
||||
@ -128,26 +123,3 @@ def cli_run(host: str, port: int, dev: bool, workers: int | None) -> None:
|
||||
uvicorn.run(
|
||||
"sshecret_backend.main:app", host=host, port=port, reload=dev, workers=workers
|
||||
)
|
||||
|
||||
|
||||
@cli.command("repl")
|
||||
@click.pass_context
|
||||
def cli_repl(ctx: click.Context) -> None:
|
||||
"""Run an interactive console."""
|
||||
settings = cast(BackendSettings, ctx.obj)
|
||||
engine = get_engine(settings.db_url, True)
|
||||
|
||||
with Session(engine) as session:
|
||||
locals = {
|
||||
"session": session,
|
||||
"select": select,
|
||||
"Client": Client,
|
||||
"ClientSecret": ClientSecret,
|
||||
"ClientAccessPolicy": ClientAccessPolicy,
|
||||
"APIClient": APIClient,
|
||||
"AuditLog": AuditLog,
|
||||
}
|
||||
|
||||
console = code.InteractiveConsole(locals=locals, local_exit=True)
|
||||
banner = "Sshecret-backend REPL.\nUse 'session' to interact with the database."
|
||||
console.interact(banner=banner, exitmsg="Bye!")
|
||||
|
||||
Reference in New Issue
Block a user