feat(v2): initialize allowlisted repositories
This commit is contained in:
@@ -16,7 +16,8 @@ from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
|
||||
from backup_tool.cli import build_alembic_config
|
||||
from backup_tool.config import Settings
|
||||
from backup_tool.db.engine import SchemaNotCurrentError, assert_schema_current, create_engine
|
||||
from backup_tool.db.models import ApiToken, AuditEvent, IdempotencyRecord, Secret, User
|
||||
from backup_tool.db.models import ApiToken, AuditEvent, IdempotencyRecord, Repository, Secret, User
|
||||
from backup_tool.repository import RepositoryError, initialize
|
||||
from backup_tool.security.auth import (
|
||||
hash_password,
|
||||
hash_token,
|
||||
@@ -76,6 +77,18 @@ class UserPatch(BaseModel):
|
||||
state: str
|
||||
|
||||
|
||||
class RepositoryInput(BaseModel):
|
||||
name: str = Field(min_length=1, max_length=255)
|
||||
relative_path: str = Field(min_length=1, max_length=1024)
|
||||
compression: str = "none"
|
||||
encryption: str = "none"
|
||||
|
||||
|
||||
class RepositoryPatch(BaseModel):
|
||||
compression: str | None = None
|
||||
encryption: str | None = None
|
||||
|
||||
|
||||
def _etag(user: User) -> str:
|
||||
return f'"{user.id}:{user.updated_at.isoformat()}"'
|
||||
|
||||
@@ -442,6 +455,69 @@ def create_app(settings: Settings) -> FastAPI:
|
||||
response.headers["ETag"] = _etag(user)
|
||||
return {"id": user.id, "username": user.username, "state": user.state}
|
||||
|
||||
@app.post("/api/v2/repositories", status_code=201)
|
||||
async def create_repository(
|
||||
input_: RepositoryInput,
|
||||
request: Request,
|
||||
db: Annotated[AsyncSession, Depends(session)],
|
||||
identity: Annotated[tuple[User, set[str], bool], Depends(require)],
|
||||
) -> dict[str, Any]:
|
||||
user, scopes, _ = identity
|
||||
if "*" not in scopes and "admin:write" not in scopes:
|
||||
raise Problem(403, "insufficient_scope", "Required scope is missing.")
|
||||
try:
|
||||
initialized = initialize(
|
||||
settings, input_.relative_path, input_.compression, input_.encryption
|
||||
)
|
||||
except RepositoryError as error:
|
||||
raise Problem(409, "repository_invalid", str(error)) from error
|
||||
repository = Repository(
|
||||
name=input_.name,
|
||||
root=str(initialized.root),
|
||||
format_version=initialized.format_version,
|
||||
compression=initialized.compression,
|
||||
encryption=initialized.encryption,
|
||||
)
|
||||
db.add(repository)
|
||||
await db.flush()
|
||||
await audit(db, request, "create", "repository", repository.id, "success", user.id)
|
||||
await db.commit()
|
||||
return {
|
||||
"id": repository.id,
|
||||
"name": repository.name,
|
||||
"format_version": repository.format_version,
|
||||
"compression": repository.compression,
|
||||
"encryption": repository.encryption,
|
||||
}
|
||||
|
||||
@app.get("/api/v2/repositories/{repository_id}")
|
||||
async def get_repository(
|
||||
repository_id: str,
|
||||
db: Annotated[AsyncSession, Depends(session)],
|
||||
_: Annotated[tuple[User, set[str], bool], Depends(actor)],
|
||||
) -> dict[str, Any]:
|
||||
repository = await db.get(Repository, repository_id)
|
||||
if repository is None:
|
||||
raise Problem(404, "resource_not_found", "Repository was not found.")
|
||||
return {
|
||||
"id": repository.id,
|
||||
"name": repository.name,
|
||||
"format_version": repository.format_version,
|
||||
"compression": repository.compression,
|
||||
"encryption": repository.encryption,
|
||||
"state": repository.state,
|
||||
}
|
||||
|
||||
@app.patch("/api/v2/repositories/{repository_id}")
|
||||
async def patch_repository(
|
||||
repository_id: str,
|
||||
input_: RepositoryPatch,
|
||||
_: Annotated[tuple[User, set[str], bool], Depends(require)],
|
||||
) -> None:
|
||||
if input_.compression is not None or input_.encryption is not None:
|
||||
raise Problem(409, "repository_policy_immutable", "Repository policy is immutable.")
|
||||
raise Problem(422, "validation_failed", "No mutable fields supplied.")
|
||||
|
||||
@app.get("/api/v2/audit")
|
||||
async def list_audit(
|
||||
db: Annotated[AsyncSession, Depends(session)],
|
||||
|
||||
Reference in New Issue
Block a user