0.2.42: external Administrator role with entity-scoped management
The external fund_administrator role (relabeled Administrator) now signs
into the full admin interface, fenced to the funds and SPVs granted to
it via EntityAccess:
- Partners, capital accounts, documents (upload and delete), entity
edits, and eNAV imports for its own funds only; no fund creation,
valuation sign-off, audit log, or investor view.
- Scoped user management: sees and manages only investors tied to its
funds; creates investor accounts only; updates preserve grants on
funds outside its scope.
- New DELETE /api/users/{id} (in-app Delete user button) with the
cascade cleanup factored out of the CLI; Service Admin and self are
protected, and an Administrator can only delete an investor who
belongs solely to its funds.
- Internal fund_admin relabeled 'Staff (all funds)' and dropped from
the create picker to end the two-similar-names confusion.
- Version badge removed from the UI (sidebar and portal header); the
build version now logs to the browser console instead.
- deploy/.startos (signing key) added to .gitignore.
This commit is contained in:
@@ -92,3 +92,34 @@ require_approver = require_role(UserRole.approver) # final sign-off — Managin
|
||||
require_audit_reader = require_role(UserRole.approver, UserRole.cfo, UserRole.operations)
|
||||
# Account administration (create/manage users, documents, capital accounts)
|
||||
require_internal_admin = require_role(UserRole.approver, UserRole.cfo, UserRole.operations)
|
||||
|
||||
# Internal admins plus the external Administrator (fund_administrator). Every endpoint
|
||||
# using this gate must also call check_administrator_scope for the entity it touches —
|
||||
# the role alone says nothing about WHICH entities an Administrator may manage.
|
||||
require_admin = require_role(
|
||||
UserRole.approver, UserRole.cfo, UserRole.operations, UserRole.fund_administrator
|
||||
)
|
||||
# Entity-record writers: internal writers plus the external Administrator (scope-checked).
|
||||
require_entity_writer = require_role(
|
||||
UserRole.fund_admin, UserRole.cfo, UserRole.approver, UserRole.operations,
|
||||
UserRole.fund_administrator,
|
||||
)
|
||||
|
||||
|
||||
def require_internal_or_administrator(user: User = Depends(get_current_user)) -> User:
|
||||
"""Read gate for admin screens: any internal role, or the external Administrator.
|
||||
|
||||
Investors are blocked; Administrator calls must still be scope-checked per entity.
|
||||
"""
|
||||
if user.role == UserRole.investor:
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
return user
|
||||
|
||||
|
||||
def check_administrator_scope(user: User, entity_id: int, session: Session) -> None:
|
||||
"""403 when an external Administrator touches an entity outside their grants.
|
||||
|
||||
Internal roles pass through untouched — their reach is decided by the route's gate.
|
||||
"""
|
||||
if user.role == UserRole.fund_administrator and not can_access_entity(user, entity_id, session):
|
||||
raise HTTPException(status_code=403, detail="No access to this entity")
|
||||
|
||||
Reference in New Issue
Block a user