diff --git a/packages/sshecret-admin/src/sshecret_admin/api/endpoints/secrets.py b/packages/sshecret-admin/src/sshecret_admin/api/endpoints/secrets.py
index ef50ff1..cce0b62 100644
--- a/packages/sshecret-admin/src/sshecret_admin/api/endpoints/secrets.py
+++ b/packages/sshecret-admin/src/sshecret_admin/api/endpoints/secrets.py
@@ -5,7 +5,6 @@ import logging
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, Query, Security, status
-from sshecret.backend.models import Secret
from sshecret_admin.core.dependencies import AdminDependencies
from sshecret_admin.services import AdminBackend
from sshecret_admin.services.models import (
@@ -13,6 +12,7 @@ from sshecret_admin.services.models import (
ClientSecretGroupList,
SecretCreate,
SecretGroupCreate,
+ SecretListView,
SecretUpdate,
SecretView,
)
@@ -27,7 +27,7 @@ def create_router(dependencies: AdminDependencies) -> APIRouter:
@app.get("/secrets/")
async def get_secret_names(
admin: Annotated[AdminBackend, Depends(dependencies.get_admin_backend)],
- ) -> list[Secret]:
+ ) -> list[SecretListView]:
"""Get Secret Names."""
return await admin.get_secrets()
diff --git a/packages/sshecret-admin/src/sshecret_admin/frontend/templates/secrets/partials/client_secret_details.html.j2 b/packages/sshecret-admin/src/sshecret_admin/frontend/templates/secrets/partials/client_secret_details.html.j2
index 54ce681..3586e5c 100644
--- a/packages/sshecret-admin/src/sshecret_admin/frontend/templates/secrets/partials/client_secret_details.html.j2
+++ b/packages/sshecret-admin/src/sshecret_admin/frontend/templates/secrets/partials/client_secret_details.html.j2
@@ -3,6 +3,8 @@
{% include '/secrets/partials/client_list_inner.html.j2' %}
+{% if secret.secret %}
{% include '/secrets/partials/client_assign_button.html.j2' %}
+{% endif %}
diff --git a/packages/sshecret-admin/src/sshecret_admin/frontend/templates/secrets/partials/group_detail.html.j2 b/packages/sshecret-admin/src/sshecret_admin/frontend/templates/secrets/partials/group_detail.html.j2
index 58e0b57..365913c 100644
--- a/packages/sshecret-admin/src/sshecret_admin/frontend/templates/secrets/partials/group_detail.html.j2
+++ b/packages/sshecret-admin/src/sshecret_admin/frontend/templates/secrets/partials/group_detail.html.j2
@@ -1,7 +1,9 @@
Group {{name}}
- {{ description }}
+ {% if description %}
+ {{ description }}
+ {% endif %}
diff --git a/packages/sshecret-admin/src/sshecret_admin/frontend/templates/secrets/partials/tree_detail.html.j2 b/packages/sshecret-admin/src/sshecret_admin/frontend/templates/secrets/partials/tree_detail.html.j2
index 86e5481..7dd303a 100644
--- a/packages/sshecret-admin/src/sshecret_admin/frontend/templates/secrets/partials/tree_detail.html.j2
+++ b/packages/sshecret-admin/src/sshecret_admin/frontend/templates/secrets/partials/tree_detail.html.j2
@@ -31,6 +31,12 @@
{{secret.name}}
+ {% if secret.description %}
+ {{ secret.description }}
+ {% endif %}
+ {% if not secret.secret %}
+ This secret was created outside of sshecret-admin. It cannot be decrypted, and therefore fewer options are available here.
+ {% endif %}
+ {% if secret.secret %}
@@ -103,6 +110,7 @@
{% endif %}
+{% endif %}
diff --git a/packages/sshecret-admin/src/sshecret_admin/frontend/views/secrets.py b/packages/sshecret-admin/src/sshecret_admin/frontend/views/secrets.py
index 74afb5e..0f44457 100644
--- a/packages/sshecret-admin/src/sshecret_admin/frontend/views/secrets.py
+++ b/packages/sshecret-admin/src/sshecret_admin/frontend/views/secrets.py
@@ -64,6 +64,7 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
current_user: Annotated[LocalUserInfo, Depends(dependencies.get_user_info)],
):
groups = await admin.get_secret_groups()
+ LOG.info("Groups: %s", groups.model_dump_json(indent=2))
return templates.TemplateResponse(
request,
"secrets/index.html.j2",
@@ -73,46 +74,46 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
},
)
- @app.get("/secrets/partial/root_group")
- async def get_root_group(
- request: Request,
- admin: Annotated[AdminBackend, Depends(dependencies.get_admin_backend)],
- ):
- """Get root group."""
- clients = await admin.get_clients()
- return templates.TemplateResponse(
- request,
- "secrets/partials/edit_root.html.j2",
- {
- "group_path_nodes": [],
- "clients": clients,
- },
- )
+ # @app.get("/secrets/partial/root_group")
+ # async def get_root_group(
+ # request: Request,
+ # admin: Annotated[AdminBackend, Depends(dependencies.get_admin_backend)],
+ # ):
+ # """Get root group."""
+ # clients = await admin.get_clients()
+ # return templates.TemplateResponse(
+ # request,
+ # "secrets/partials/edit_root.html.j2",
+ # {
+ # "group_path_nodes": [],
+ # "clients": clients,
+ # },
+ # )
- @app.get("/secrets/partial/secret/{name}")
- async def get_secret_tree_detail_partial(
- request: Request,
- name: str,
- admin: Annotated[AdminBackend, Depends(dependencies.get_admin_backend)],
- ):
- """Get partial secret detail."""
- secret = await admin.get_secret(name)
- groups = await admin.get_secret_groups(flat=True)
- events = await admin.get_audit_log_detailed(limit=10, secret_name=name)
+ # @app.get("/secrets/partial/secret/{name}")
+ # async def get_secret_tree_detail_partial(
+ # request: Request,
+ # name: str,
+ # admin: Annotated[AdminBackend, Depends(dependencies.get_admin_backend)],
+ # ):
+ # """Get partial secret detail."""
+ # secret = await admin.get_secret(name)
+ # groups = await admin.get_secret_groups(flat=True)
+ # events = await admin.get_audit_log_detailed(limit=10, secret_name=name)
- if not secret:
- raise HTTPException(
- status_code=status.HTTP_404_NOT_FOUND, detail="Secret not found"
- )
- return templates.TemplateResponse(
- request,
- "secrets/partials/tree_detail.html.j2",
- {
- "secret": secret,
- "groups": groups,
- "events": events,
- },
- )
+ # if not secret:
+ # raise HTTPException(
+ # status_code=status.HTTP_404_NOT_FOUND, detail="Secret not found"
+ # )
+ # return templates.TemplateResponse(
+ # request,
+ # "secrets/partials/tree_detail.html.j2",
+ # {
+ # "secret": secret,
+ # "groups": groups,
+ # "events": events,
+ # },
+ # )
@app.get("/secrets/group/")
async def show_root_group(
@@ -573,7 +574,7 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
admin: Annotated[AdminBackend, Depends(dependencies.get_admin_backend)],
):
"""Add a secret to a client."""
- await admin.create_client_secret(client, name)
+ await admin.create_client_secret(("id", client), name)
secret = await admin.get_secret(name)
if not secret:
raise HTTPException(
diff --git a/packages/sshecret-admin/src/sshecret_admin/services/admin_backend.py b/packages/sshecret-admin/src/sshecret_admin/services/admin_backend.py
index 7c59777..288da77 100644
--- a/packages/sshecret-admin/src/sshecret_admin/services/admin_backend.py
+++ b/packages/sshecret-admin/src/sshecret_admin/services/admin_backend.py
@@ -26,6 +26,7 @@ from .models import (
ClientSecretGroup,
ClientSecretGroupList,
SecretClientMapping,
+ SecretListView,
SecretGroup,
SecretView,
)
@@ -269,23 +270,32 @@ class AdminBackend:
except Exception as e:
raise BackendUnavailableError() from e
- async def _get_secrets(self) -> list[Secret]:
+ async def _get_secrets(self) -> list[SecretListView]:
"""Get secrets.
This fetches the secret to client mapping from backend, and adds secrets from the password manager.
"""
+ backend_secrets = await self.backend.get_secrets()
with self.password_manager() as password_manager:
- all_secrets = password_manager.get_available_secrets()
+ admin_secrets = password_manager.get_available_secrets()
- secrets = await self.backend.get_secrets()
- backend_secret_names = [secret.name for secret in secrets]
- for secret in all_secrets:
- if secret not in backend_secret_names:
- secrets.append(Secret(name=secret, clients=[]))
+ secrets: dict[str, SecretListView] = {}
+ for secret in backend_secrets:
+ secrets[secret.name] = SecretListView(
+ name=secret.name, unmanaged=True, clients=secret.clients
+ )
- return secrets
+ for secret_name in admin_secrets:
+ if secret_name in secrets:
+ secrets[secret_name].unmanaged = False
+ continue
+ secrets[secret_name] = SecretListView(
+ name=secret_name, unmanaged=False, clients=[]
+ )
- async def get_secrets(self) -> list[Secret]:
+ return list(secrets.values())
+
+ async def get_secrets(self) -> list[SecretListView]:
"""Get secrets from backend."""
try:
return await self._get_secrets()
@@ -385,6 +395,8 @@ class AdminBackend:
)
ungrouped = password_manager.get_ungrouped_secrets()
+ all_admin_secrets = password_manager.get_available_secrets()
+
group_result: list[ClientSecretGroup] = []
for group in all_groups:
# We have to do this recursively.
@@ -401,7 +413,19 @@ class AdminBackend:
mapping.clients = client_mapping.clients
ungrouped_clients.append(mapping)
+ # We need to process unmanaged secrets too.
+ unmanaged_secrets = [
+ secret for secret in all_secrets if secret.name not in all_admin_secrets
+ ]
+ for secret in unmanaged_secrets:
+ ungrouped_clients.append(
+ SecretClientMapping(
+ name=secret.name, unmanaged=True, clients=secret.clients
+ )
+ )
+
result.ungrouped = ungrouped_clients
+
return result
async def get_secret_group(self, name: str) -> ClientSecretGroup | None:
@@ -436,13 +460,13 @@ class AdminBackend:
self, name: str, secret_id: str | None = None
) -> SecretView | None:
"""Get a secret, including the actual unencrypted value and clients."""
+ secret: str | None = None
with self.password_manager() as password_manager:
secret = password_manager.get_secret(name)
secret_group = password_manager.get_entry_group(name)
- if not secret:
- return None
secret_view = SecretView(name=name, secret=secret, group=secret_group)
+
idname: KeySpec = name
if secret_id:
idname = ("id", secret_id)
@@ -529,11 +553,13 @@ class AdminBackend:
except Exception as e:
raise BackendUnavailableError() from e
- async def _create_client_secret(self, client_name: str, secret_name: str) -> None:
+ async def _create_client_secret(
+ self, client_idname: KeySpec, secret_name: str
+ ) -> None:
"""Create client secret."""
- client = await self.get_client(client_name)
+ client = await self.get_client(client_idname)
if not client:
- raise ClientNotFoundError()
+ raise ClientNotFoundError(client_idname)
with self.password_manager() as password_manager:
secret = password_manager.get_secret(secret_name)
@@ -542,12 +568,14 @@ class AdminBackend:
public_key = load_public_key(client.public_key.encode())
encrypted = encrypt_string(secret, public_key)
- await self.backend.create_client_secret(client_name, secret_name, encrypted)
+ await self.backend.create_client_secret(client_idname, secret_name, encrypted)
- async def create_client_secret(self, client_name: str, secret_name: str) -> None:
+ async def create_client_secret(
+ self, client_idname: KeySpec, secret_name: str
+ ) -> None:
"""Create client secret."""
try:
- await self._create_client_secret(client_name, secret_name)
+ await self._create_client_secret(client_idname, secret_name)
except ClientManagementError:
raise
except Exception as e:
diff --git a/packages/sshecret-admin/src/sshecret_admin/services/models.py b/packages/sshecret-admin/src/sshecret_admin/services/models.py
index 6799397..9d20f11 100644
--- a/packages/sshecret-admin/src/sshecret_admin/services/models.py
+++ b/packages/sshecret-admin/src/sshecret_admin/services/models.py
@@ -25,6 +25,7 @@ class SecretListView(BaseModel):
"""Model containing a list of all available secrets."""
name: str
+ unmanaged: bool = False
clients: list[str] = Field(default_factory=list) # Clients that have access to it.
@@ -32,7 +33,7 @@ class SecretView(BaseModel):
"""Model containing a secret, including its clear-text value."""
name: str
- secret: str
+ secret: str | None
group: str | None = None
clients: list[str] = Field(default_factory=list) # Clients that have access to it.
@@ -134,6 +135,7 @@ class SecretClientMapping(BaseModel):
"""Secret name with list of clients."""
name: str # name of secret
+ unmanaged: bool = False
clients: list[ClientReference] = Field(default_factory=list)