Documentation
¶
Overview ¶
Package access_binding — AccessBindingService.
Особенность: Create strict — дубль активного grant'а (5-tuple WHERE revoked_at IS NULL) поднимает 23505 из partial UNIQUE access_bindings_active_grant_uniq (migration 0003) → gRPC AlreadyExists с фиксированным текстом «these permissions are already granted to <subject_id> on <res_type>:<res_id>». Идемпотентный ON CONFLICT-upsert удален. Each Create/Delete emits outbox-event to invalidate authz-cache.
Index ¶
- Constants
- type CreateAccessBindingUseCase
- func (u *CreateAccessBindingUseCase) Execute(ctx context.Context, b domain.AccessBinding) (*operations.Operation, error)
- func (u *CreateAccessBindingUseCase) WithReconciler(r SelectorReconciler) *CreateAccessBindingUseCase
- func (u *CreateAccessBindingUseCase) WithRelationStore(relations clients.RelationStore, logger *slog.Logger) *CreateAccessBindingUseCase
- type DeleteAccessBindingUseCase
- type ExpandAccessUseCase
- type GetAccessBindingUseCase
- func (u *GetAccessBindingUseCase) Execute(ctx context.Context, id domain.AccessBindingID) (domain.AccessBinding, error)
- func (u *GetAccessBindingUseCase) WithRelationQueries(q clients.RelationQueries) *GetAccessBindingUseCase
- func (u *GetAccessBindingUseCase) WithRelationStore(relations clients.RelationStore, logger *slog.Logger) *GetAccessBindingUseCase
- type Handler
- func (h *Handler) Create(ctx context.Context, req *iamv1.CreateAccessBindingRequest) (*operationpb.Operation, error)
- func (h *Handler) Delete(ctx context.Context, req *iamv1.DeleteAccessBindingRequest) (*operationpb.Operation, error)
- func (h *Handler) ExpandAccess(ctx context.Context, req *iamv1.ExpandAccessRequest) (*iamv1.ExpandAccessResponse, error)
- func (h *Handler) Get(ctx context.Context, req *iamv1.GetAccessBindingRequest) (*iamv1.AccessBinding, error)
- func (h *Handler) List(ctx context.Context, req *iamv1.ListAccessBindingsRequest) (*iamv1.ListAccessBindingsResponse, error)
- func (h *Handler) ListAssignableRoles(ctx context.Context, req *iamv1.ListAssignableRolesRequest) (*iamv1.ListAssignableRolesResponse, error)
- func (h *Handler) ListByAccount(ctx context.Context, req *iamv1.ListAccessBindingsByAccountRequest) (*iamv1.ListAccessBindingsResponse, error)
- func (h *Handler) ListByRole(ctx context.Context, req *iamv1.ListAccessBindingsByRoleRequest) (*iamv1.ListAccessBindingsResponse, error)
- func (h *Handler) ListByScope(ctx context.Context, req *iamv1.ListAccessBindingsByScopeRequest) (*iamv1.ListAccessBindingsResponse, error)
- func (h *Handler) ListBySubject(ctx context.Context, req *iamv1.ListAccessBindingsBySubjectRequest) (*iamv1.ListAccessBindingsResponse, error)
- func (h *Handler) ListOperations(ctx context.Context, req *iamv1.ListAccessBindingOperationsRequest) (*iamv1.ListAccessBindingOperationsResponse, error)
- func (h *Handler) ListSubjectPrivileges(ctx context.Context, req *iamv1.ListSubjectPrivilegesRequest) (*iamv1.ListSubjectPrivilegesResponse, error)
- func (h *Handler) Revoke(ctx context.Context, req *iamv1.RevokeAccessBindingRequest) (*operationpb.Operation, error)
- func (h *Handler) Update(ctx context.Context, req *iamv1.UpdateAccessBindingRequest) (*operationpb.Operation, error)
- func (h *Handler) WithExpandAccess(uc *ExpandAccessUseCase) *Handler
- func (h *Handler) WithList(uc *ListUseCase) *Handler
- func (h *Handler) WithListAssignableRoles(uc *ListAssignableRolesUseCase) *Handler
- func (h *Handler) WithListByRole(uc *ListByRoleUseCase) *Handler
- func (h *Handler) WithListOperations(uc *shared.ListOperationsUseCase) *Handler
- func (h *Handler) WithRevoke(uc *RevokeAccessBindingUseCase) *Handler
- func (h *Handler) WithUpdate(uc *UpdateAccessBindingUseCase) *Handler
- type ListAssignableRolesUseCase
- type ListByAccountUseCase
- func (u *ListByAccountUseCase) Execute(ctx context.Context, accountID string, f repoab.AccountPageFilter) ([]domain.AccessBinding, string, error)
- func (u *ListByAccountUseCase) WithRelationQueries(q clients.RelationQueries) *ListByAccountUseCase
- func (u *ListByAccountUseCase) WithRelationStore(relations clients.RelationStore, logger *slog.Logger) *ListByAccountUseCase
- type ListByRoleUseCase
- type ListByScopeUseCase
- func (u *ListByScopeUseCase) Execute(ctx context.Context, resourceType domain.ResourceType, resourceID string, ...) ([]domain.AccessBinding, string, error)
- func (u *ListByScopeUseCase) WithRelationQueries(q clients.RelationQueries) *ListByScopeUseCase
- func (u *ListByScopeUseCase) WithRelationStore(relations clients.RelationStore, logger *slog.Logger) *ListByScopeUseCase
- type ListBySubjectUseCase
- func (u *ListBySubjectUseCase) Execute(ctx context.Context, subjectType domain.SubjectType, ...) ([]domain.AccessBinding, string, error)
- func (u *ListBySubjectUseCase) WithRelationQueries(q clients.RelationQueries) *ListBySubjectUseCase
- func (u *ListBySubjectUseCase) WithRelationStore(relations clients.RelationStore, logger *slog.Logger) *ListBySubjectUseCase
- type ListPage
- type ListSubjectPrivilegesUseCase
- func (u *ListSubjectPrivilegesUseCase) Execute(ctx context.Context, subjectType domain.SubjectType, ...) ([]domain.SubjectPrivilege, string, error)
- func (u *ListSubjectPrivilegesUseCase) WithRelationQueries(q clients.RelationQueries) *ListSubjectPrivilegesUseCase
- func (u *ListSubjectPrivilegesUseCase) WithRelationStore(relations clients.RelationStore, logger *slog.Logger) *ListSubjectPrivilegesUseCase
- type ListUseCase
- func (u *ListUseCase) Execute(ctx context.Context, f repoab.ListFilter) (ListPage, error)
- func (u *ListUseCase) WithClusterAdmins(r clusterAdminReader) *ListUseCase
- func (u *ListUseCase) WithListScanRecorder(rec shared.ListScanRecorder) *ListUseCase
- func (u *ListUseCase) WithRelationQueries(q clients.RelationQueries) *ListUseCase
- func (u *ListUseCase) WithRelationStore(relations clients.RelationStore) *ListUseCase
- type ObjectForwardReconciler
- type Principal
- type PrincipalLister
- type Reader
- type Repo
- type RevokeAccessBindingUseCase
- type RoleMembershipFanout
- type RoleTupleReconciler
- type SelectorReconciler
- type UpdateAccessBindingUseCase
- func (u *UpdateAccessBindingUseCase) Execute(ctx context.Context, id domain.AccessBindingID, mask []string, ...) (*operations.Operation, error)
- func (u *UpdateAccessBindingUseCase) WithObjectReconciler(r ObjectForwardReconciler) *UpdateAccessBindingUseCase
- func (u *UpdateAccessBindingUseCase) WithRelationStore(relations clients.RelationStore, logger *slog.Logger) *UpdateAccessBindingUseCase
- type Writer
Constants ¶
const MinExpiresIn = 5 * time.Minute
MinExpiresIn — the shortest lifetime AccessBinding.Create accepts. A binding's access is materialised asynchronously within a bounded window, so anything shorter risks being revoked before it is ever usable; such a request is refused by name rather than issued as a grant that cannot be delivered.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type CreateAccessBindingUseCase ¶
type CreateAccessBindingUseCase struct {
// contains filtered or unexported fields
}
func NewCreateAccessBindingUseCase ¶
func NewCreateAccessBindingUseCase(r Repo, opsRepo operations.Repo) *CreateAccessBindingUseCase
func (*CreateAccessBindingUseCase) Execute ¶
func (u *CreateAccessBindingUseCase) Execute(ctx context.Context, b domain.AccessBinding) (*operations.Operation, error)
func (*CreateAccessBindingUseCase) WithReconciler ¶
func (u *CreateAccessBindingUseCase) WithReconciler(r SelectorReconciler) *CreateAccessBindingUseCase
WithReconciler wires the γ selector reconciler (post-commit membership materialization). nil-safe.
func (*CreateAccessBindingUseCase) WithRelationStore ¶
func (u *CreateAccessBindingUseCase) WithRelationStore(relations clients.RelationStore, logger *slog.Logger) *CreateAccessBindingUseCase
WithRelationStore wires the door to the rights decision. Grant tuples are emitted via fga_outbox (not through this client), but the door is required on the READ side by requireGrantAuthority to resolve delegated-admin grant authority (FGA `admin`/`system_admin` on the scope object). Logger is used for failure diagnostics.
type DeleteAccessBindingUseCase ¶
type DeleteAccessBindingUseCase struct {
// contains filtered or unexported fields
}
func NewDeleteAccessBindingUseCase ¶
func NewDeleteAccessBindingUseCase(r Repo, opsRepo operations.Repo) *DeleteAccessBindingUseCase
func (*DeleteAccessBindingUseCase) Execute ¶
func (u *DeleteAccessBindingUseCase) Execute(ctx context.Context, id domain.AccessBindingID) (*operations.Operation, error)
func (*DeleteAccessBindingUseCase) WithRelationStore ¶
func (u *DeleteAccessBindingUseCase) WithRelationStore(relations clients.RelationStore, logger *slog.Logger) *DeleteAccessBindingUseCase
WithRelationStore подключает ДВЕРЬ РЕШЕНИЯ. Она нужна на READ-стороне: `requireGrantAuthority` спрашивает, вправе ли вызывающий снимать эту выдачу. Синхронного applier'а снятия за ней больше нет — см. разбор в шапке файла. Logger остаётся для диагностики самой проверки прав.
type ExpandAccessUseCase ¶
type ExpandAccessUseCase struct {
// contains filtered or unexported fields
}
func NewExpandAccessUseCase ¶
func NewExpandAccessUseCase(l PrincipalLister) *ExpandAccessUseCase
func (*ExpandAccessUseCase) Execute ¶
func (u *ExpandAccessUseCase) Execute(ctx context.Context, objectType, objectID, relation string, maxResults int) ([]Principal, bool, error)
Execute resolves <relation> on <objectType>:<objectID> into concrete principals. maxResults<=0 → default (1000); capped at 10000.
truncated=true means the answer is a LOWER BOUND, for either reason: the grant store cut its own enumeration at its server-side ceiling (no continuation token exists, so the rest is unreachable — narrow the query), or the resolved set exceeded maxResults and was trimmed here.
func (*ExpandAccessUseCase) WithGrantAuthority ¶
func (u *ExpandAccessUseCase) WithGrantAuthority(repo Repo, relations clients.RelationStore, logger *slog.Logger) *ExpandAccessUseCase
WithGrantAuthority wires the per-object authority gate. repo resolves the owner-path for hierarchy scopes (account/project); relations resolves the delegated-admin FGA path for every scope. Mirrors the WithRelationStore wiring on Create/Delete/ListByScope. Logger is used for failure diagnostics.
type GetAccessBindingUseCase ¶
type GetAccessBindingUseCase struct {
// contains filtered or unexported fields
}
func NewGetAccessBindingUseCase ¶
func NewGetAccessBindingUseCase(r Repo) *GetAccessBindingUseCase
func (*GetAccessBindingUseCase) Execute ¶
func (u *GetAccessBindingUseCase) Execute(ctx context.Context, id domain.AccessBindingID) (domain.AccessBinding, error)
func (*GetAccessBindingUseCase) WithRelationQueries ¶
func (u *GetAccessBindingUseCase) WithRelationQueries(q clients.RelationQueries) *GetAccessBindingUseCase
WithRelationQueries wires the FGA ListObjects port so a label-selector grant (viewer ∪ v_list on iam_access_binding) lets its grantee Get the labeled binding — the D-6 additive path, consistent with the gateway already Checking v_get on AB.Get. nil → self/granted-floor only.
func (*GetAccessBindingUseCase) WithRelationStore ¶
func (u *GetAccessBindingUseCase) WithRelationStore(relations clients.RelationStore, logger *slog.Logger) *GetAccessBindingUseCase
WithRelationStore wires the FGA client so the resource-scope grant-authority check (requireGrantAuthority) can resolve delegated admins — notably cluster-scope bindings, whose authority is `system_admin@cluster` in FGA (no DB owner).
type Handler ¶
type Handler struct {
iamv1.UnimplementedAccessBindingServiceServer
// contains filtered or unexported fields
}
func NewHandler ¶
func NewHandler(c *CreateAccessBindingUseCase, d *DeleteAccessBindingUseCase, g *GetAccessBindingUseCase, lbs *ListByScopeUseCase, lbsub *ListBySubjectUseCase, lba *ListByAccountUseCase, lsp *ListSubjectPrivilegesUseCase) *Handler
func (*Handler) Create ¶
func (h *Handler) Create(ctx context.Context, req *iamv1.CreateAccessBindingRequest) (*operationpb.Operation, error)
func (*Handler) Delete ¶
func (h *Handler) Delete(ctx context.Context, req *iamv1.DeleteAccessBindingRequest) (*operationpb.Operation, error)
func (*Handler) ExpandAccess ¶
func (h *Handler) ExpandAccess(ctx context.Context, req *iamv1.ExpandAccessRequest) (*iamv1.ExpandAccessResponse, error)
ExpandAccess — sync effective-principal audit "who can do <relation> on <object>". Resolves group usersets to concrete principals.
func (*Handler) Get ¶
func (h *Handler) Get(ctx context.Context, req *iamv1.GetAccessBindingRequest) (*iamv1.AccessBinding, error)
func (*Handler) List ¶
func (h *Handler) List(ctx context.Context, req *iamv1.ListAccessBindingsRequest) (*iamv1.ListAccessBindingsResponse, error)
List — the unified read (redesign-2026 F11). Page format is validated FIRST (page_size + page_token), BEFORE the use-case's listauthz visibility short-circuit, so a garbage token / page_size>1000 is INVALID_ARGUMENT regardless of grant state. Then the optional whitelist filter is parsed (unknown key → INVALID_ARGUMENT).
func (*Handler) ListAssignableRoles ¶
func (h *Handler) ListAssignableRoles(ctx context.Context, req *iamv1.ListAssignableRolesRequest) (*iamv1.ListAssignableRolesResponse, error)
ListAssignableRoles — sync read of the roles valid for binding on (resource_type, resource_id), each annotated with a server-computed scope_group. Thin transport: parse → use-case → format. resource_type/id validation, existence + grant-authority, and the isRoleAssignable filter all live in the use-case.
func (*Handler) ListByAccount ¶
func (h *Handler) ListByAccount(ctx context.Context, req *iamv1.ListAccessBindingsByAccountRequest) (*iamv1.ListAccessBindingsResponse, error)
func (*Handler) ListByRole ¶
func (h *Handler) ListByRole(ctx context.Context, req *iamv1.ListAccessBindingsByRoleRequest) (*iamv1.ListAccessBindingsResponse, error)
ListByRole — sync audit "who holds role R". Each row carries the dual subjects[]/legacy projection; the use-case enforces the per-row grant-authority scope-filter.
func (*Handler) ListByScope ¶
func (h *Handler) ListByScope(ctx context.Context, req *iamv1.ListAccessBindingsByScopeRequest) (*iamv1.ListAccessBindingsResponse, error)
func (*Handler) ListBySubject ¶
func (h *Handler) ListBySubject(ctx context.Context, req *iamv1.ListAccessBindingsBySubjectRequest) (*iamv1.ListAccessBindingsResponse, error)
func (*Handler) ListOperations ¶
func (h *Handler) ListOperations(ctx context.Context, req *iamv1.ListAccessBindingOperationsRequest) (*iamv1.ListAccessBindingOperationsResponse, error)
ListOperations — sync read of the operations recorded for the access binding (resource_id=acb-…: Create + Delete ops). Malformed id → InvalidArgument (first statement); well-formed-but-no-ops → empty list, not NotFound (parity). Viewer-tier authz is enforced by the api-gateway permission-catalog.
func (*Handler) ListSubjectPrivileges ¶
func (h *Handler) ListSubjectPrivileges(ctx context.Context, req *iamv1.ListSubjectPrivilegesRequest) (*iamv1.ListSubjectPrivilegesResponse, error)
ListSubjectPrivileges — sync, enriched read of a subject's DIRECT privileges role_name is resolved server-side via the repo JOIN; authz is "self OR account-admin of the subject's home Account" (use-case).
func (*Handler) Revoke ¶
func (h *Handler) Revoke(ctx context.Context, req *iamv1.RevokeAccessBindingRequest) (*operationpb.Operation, error)
Revoke — soft-revoke of the binding (F10 IAM-1-28): the row is retained with status ACTIVE→REVOKED (audit-retention), the emitted FGA-tuple set is removed. Contrast with Delete (hard row-removal). Thin transport: parse → use-case → format.
func (*Handler) Update ¶
func (h *Handler) Update(ctx context.Context, req *iamv1.UpdateAccessBindingRequest) (*operationpb.Operation, error)
Update — mutate the {deletion_protection, labels} set on a binding (T3.3-IMM-01): clear/toggle deletion_protection so a protected owner-binding can subsequently be deleted (C-03), and set own-resource labels (D-6 label-selectability). Any other mask path → INVALID_ARGUMENT (update_mask discipline enforced in the use-case).
func (*Handler) WithExpandAccess ¶
func (h *Handler) WithExpandAccess(uc *ExpandAccessUseCase) *Handler
WithExpandAccess wires the effective-principal audit "who can do X".
func (*Handler) WithList ¶
func (h *Handler) WithList(uc *ListUseCase) *Handler
WithListOperations wires the per-resource operation-listing use-case. Mirrors the core resources. WithList wires the unified List use-case (redesign-2026 F11).
func (*Handler) WithListAssignableRoles ¶
func (h *Handler) WithListAssignableRoles(uc *ListAssignableRolesUseCase) *Handler
WithListAssignableRoles wires the assignable-roles read use-case.
func (*Handler) WithListByRole ¶
func (h *Handler) WithListByRole(uc *ListByRoleUseCase) *Handler
WithListByRole wires the audit read "who holds role R".
func (*Handler) WithListOperations ¶
func (h *Handler) WithListOperations(uc *shared.ListOperationsUseCase) *Handler
func (*Handler) WithRevoke ¶
func (h *Handler) WithRevoke(uc *RevokeAccessBindingUseCase) *Handler
WithRevoke wires the soft-revoke use-case (F10 IAM-1-28).
func (*Handler) WithUpdate ¶
func (h *Handler) WithUpdate(uc *UpdateAccessBindingUseCase) *Handler
WithUpdate wires the P6 deletion_protection Update use-case (C-03).
type ListAssignableRolesUseCase ¶
type ListAssignableRolesUseCase struct {
// contains filtered or unexported fields
}
func NewListAssignableRolesUseCase ¶
func NewListAssignableRolesUseCase(r Repo) *ListAssignableRolesUseCase
func (*ListAssignableRolesUseCase) Execute ¶
func (u *ListAssignableRolesUseCase) Execute(ctx context.Context, resourceType, resourceID string, f reporole.ListFilter) ([]domain.AssignableRole, string, error)
func (*ListAssignableRolesUseCase) WithRelationStore ¶
func (u *ListAssignableRolesUseCase) WithRelationStore(relations clients.RelationStore, logger *slog.Logger) *ListAssignableRolesUseCase
WithRelationStore wires the FGA client so the delegated-admin grant-authority path (and the cluster-scope authority, which has no DB owner) resolves. When unset (nil) the use-case falls back to owner-only authority and denies the FGA path — same contract as ListByScope.
type ListByAccountUseCase ¶
type ListByAccountUseCase struct {
// contains filtered or unexported fields
}
func NewListByAccountUseCase ¶
func NewListByAccountUseCase(r Repo) *ListByAccountUseCase
func (*ListByAccountUseCase) Execute ¶
func (u *ListByAccountUseCase) Execute( ctx context.Context, accountID string, f repoab.AccountPageFilter, ) ([]domain.AccessBinding, string, error)
Execute — D-6 visibility on the account-audit list: an account-admin (owner / FGA-admin / cluster-admin) sees EVERY binding in the account (the existing admin floor, NOT shrunk). A non-admin caller sees only the bindings made visible by a label-selector grant (viewer ∪ v_list on iam_access_binding) — the additive union floor, resolved PER-OBJECT over the page rather than by the server-capped ListObjects enumeration it replaces (internal/authzfilter). Anonymous → rejected. FGA error → UNAVAILABLE.
func (*ListByAccountUseCase) WithRelationQueries ¶
func (u *ListByAccountUseCase) WithRelationQueries(q clients.RelationQueries) *ListByAccountUseCase
WithRelationQueries wires the FGA ListObjects port for the D-6 viewer ∪ v_list union floor. nil → account-admin floor only.
func (*ListByAccountUseCase) WithRelationStore ¶
func (u *ListByAccountUseCase) WithRelationStore(relations clients.RelationStore, logger *slog.Logger) *ListByAccountUseCase
WithRelationStore wires the FGA client for delegated-admin authority check. When unset (unit tests / degraded mode) only owner-based access is allowed.
type ListByRoleUseCase ¶
type ListByRoleUseCase struct {
// contains filtered or unexported fields
}
func NewListByRoleUseCase ¶
func NewListByRoleUseCase(r Repo) *ListByRoleUseCase
func (*ListByRoleUseCase) Execute ¶
func (u *ListByRoleUseCase) Execute(ctx context.Context, roleID string, f repoab.ListByRoleFilter) ([]domain.AccessBinding, string, error)
func (*ListByRoleUseCase) WithRelationStore ¶
func (u *ListByRoleUseCase) WithRelationStore(relations clients.RelationStore, logger *slog.Logger) *ListByRoleUseCase
WithRelationStore wires the FGA client for the per-row delegated-admin scope filter. When unset (unit tests / degraded mode) only owner-based access passes.
type ListByScopeUseCase ¶
type ListByScopeUseCase struct {
// contains filtered or unexported fields
}
func NewListByScopeUseCase ¶
func NewListByScopeUseCase(r Repo) *ListByScopeUseCase
func (*ListByScopeUseCase) Execute ¶
func (u *ListByScopeUseCase) Execute(ctx context.Context, resourceType domain.ResourceType, resourceID string, f repoab.PageFilter) ([]domain.AccessBinding, string, error)
Execute — D-6 visibility: a grant-authority on the scope (owner / FGA-admin / cluster-admin) enumerates ALL bindings on the scope (the existing granted-floor, NOT shrunk). A non-authority caller still sees the bindings on the scope made visible to them by a label-selector grant (viewer ∪ v_list on iam_access_binding) — the additive union floor, resolved PER-OBJECT over the page (the previous ListObjects enumeration was silently capped at 1000 objects of the type in the store, which denied a legitimate grantee outright — internal/authzfilter). Anonymous → rejected. FGA error → UNAVAILABLE.
func (*ListByScopeUseCase) WithRelationQueries ¶
func (u *ListByScopeUseCase) WithRelationQueries(q clients.RelationQueries) *ListByScopeUseCase
WithRelationQueries wires the FGA ListObjects port for the D-6 viewer ∪ v_list union floor (label-selectable binding visibility). nil → granted-floor only.
func (*ListByScopeUseCase) WithRelationStore ¶
func (u *ListByScopeUseCase) WithRelationStore(relations clients.RelationStore, logger *slog.Logger) *ListByScopeUseCase
WithRelationStore wires the FGA client for the scope-authority check.
type ListBySubjectUseCase ¶
type ListBySubjectUseCase struct {
// contains filtered or unexported fields
}
func NewListBySubjectUseCase ¶
func NewListBySubjectUseCase(r Repo) *ListBySubjectUseCase
func (*ListBySubjectUseCase) Execute ¶
func (u *ListBySubjectUseCase) Execute(ctx context.Context, subjectType domain.SubjectType, subjectID domain.SubjectID, f repoab.PageFilter) ([]domain.AccessBinding, string, error)
func (*ListBySubjectUseCase) WithRelationQueries ¶
func (u *ListBySubjectUseCase) WithRelationQueries(q clients.RelationQueries) *ListBySubjectUseCase
WithRelationQueries wires the per-object question the PAGE is narrowed with.
func (*ListBySubjectUseCase) WithRelationStore ¶
func (u *ListBySubjectUseCase) WithRelationStore(relations clients.RelationStore, logger *slog.Logger) *ListBySubjectUseCase
WithRelationStore wires the rights model for the cluster-admin and delegated account-admin admission lanes.
type ListPage ¶
type ListPage struct {
// Bindings — выдачи этой страницы, уже суженные вердиктом.
Bindings []domain.AccessBinding
// NextPageToken — продолжение обхода ВЫДАЧ. Соседние виды сопровождают
// страницу и по страницам не режутся: их объём ограничен её содержимым
// (членство) либо мал по построению (кластерные администраторы).
NextPageToken string
// Memberships — состав ТЕХ групп, что названы субъектами выдач на этой
// странице. Членство в группе без выдачи доступа не даёт и к вопросу «кто
// имеет доступ» не относится; видимость наследуется от выдачи, которую
// вызывающий и так видит.
Memberships []domain.GroupMember
// ClusterAdmins — все действующие кластерные администраторы, и ТОЛЬКО тому,
// кто сам кластерный администратор. Верхний ярус супер-доступа обязан быть
// виден целиком тому, кто им распоряжается, и не обязан — арендатору:
// перечисление имён администраторов облака арендатору не адресовано.
ClusterAdmins []domain.ClusterAdminEntry
// IncompleteMembershipGroups — группы, чей состав в этом ответе НЕПОЛОН.
//
// Состав ограничен сверху (членство неограниченно by construction — у него
// для того и есть свой пагинированный глагол), и упереться в предел законно.
// Незаконно — промолчать об этом: усечённое членство читается вызывающим как
// факт о доступе, а «в группе больше никого» и «дальше мы не читали» — разные
// утверждения.
IncompleteMembershipGroups []domain.GroupID
}
ListPage — одна страница ПОЛНОГО перечисления поверхности выдач.
Три вида, три поля, и это не «список разнородного»: у членства нет ни роли, ни области, ни срока, у кластерного администратора — свой порядок выдачи, и втискивание их в форму выдачи потребовало бы пустых полей, которые читатель принимает за «не задано», а не за «неприменимо».
type ListSubjectPrivilegesUseCase ¶
type ListSubjectPrivilegesUseCase struct {
// contains filtered or unexported fields
}
func NewListSubjectPrivilegesUseCase ¶
func NewListSubjectPrivilegesUseCase(r Repo) *ListSubjectPrivilegesUseCase
func (*ListSubjectPrivilegesUseCase) Execute ¶
func (u *ListSubjectPrivilegesUseCase) Execute(ctx context.Context, subjectType domain.SubjectType, subjectID domain.SubjectID, f repoab.PageFilter) ([]domain.SubjectPrivilege, string, error)
func (*ListSubjectPrivilegesUseCase) WithRelationQueries ¶
func (u *ListSubjectPrivilegesUseCase) WithRelationQueries(q clients.RelationQueries) *ListSubjectPrivilegesUseCase
WithRelationQueries wires the per-object question the PAGE is narrowed with.
func (*ListSubjectPrivilegesUseCase) WithRelationStore ¶
func (u *ListSubjectPrivilegesUseCase) WithRelationStore(relations clients.RelationStore, logger *slog.Logger) *ListSubjectPrivilegesUseCase
WithRelationStore wires the FGA client so the account-admin authz path (FGA `admin` on the subject's home Account) can resolve delegated admins who are not the account owner. When unset (nil) the use-case falls back to owner-only authority and denies delegated admins.
type ListUseCase ¶
type ListUseCase struct {
// contains filtered or unexported fields
}
func NewListUseCase ¶
func NewListUseCase(r Repo) *ListUseCase
func (*ListUseCase) Execute ¶
func (u *ListUseCase) Execute(ctx context.Context, f repoab.ListFilter) (ListPage, error)
Execute reads the page from the iam database by cursor and returns the subset of it visible to the caller. The predicate fields on f (subject/role/scope/ scopeId) narrow the page at the SQL layer; visibility is applied to the rows that page yields.
func (*ListUseCase) WithClusterAdmins ¶
func (u *ListUseCase) WithClusterAdmins(r clusterAdminReader) *ListUseCase
WithClusterAdmins провязывает чтение соседней поверхности кластерных администраторов (#914, решение 2). nil-safe: непровязанный порт вида не даёт.
func (*ListUseCase) WithListScanRecorder ¶
func (u *ListUseCase) WithListScanRecorder(rec shared.ListScanRecorder) *ListUseCase
WithRelationQueries wires the FGA ListObjects port (viewer ∪ v_list floor). WithListScanRecorder провязывает съём стоимости страницы (#653).
func (*ListUseCase) WithRelationQueries ¶
func (u *ListUseCase) WithRelationQueries(q clients.RelationQueries) *ListUseCase
func (*ListUseCase) WithRelationStore ¶
func (u *ListUseCase) WithRelationStore(relations clients.RelationStore) *ListUseCase
WithRelationStore wires the FGA Check port for the D-9 cluster-admin super-gate. After the access-cascade contraction a cluster super-admin holds NO per-object viewer/v_list tuple on iam_access_binding (helpers.go requireGrantAuthority Path 0), so the per-object push-down alone would hand them an empty page — diverging from Get / ListByScope / ListByAccount / ListByRole, which all run requireGrantAuthority. nil-safe: an unwired gate does not fire.
No logger parameter (unlike the sibling builders). This used to be justified by "IsClusterAdmin swallows the FGA error, so there is nothing to diagnose" — that is no longer true: the failure of this question now refuses the request (UNAVAILABLE), so it is reported to the caller rather than buried, which is a stronger outcome than a log line and still needs no field here.
type ObjectForwardReconciler ¶
type ObjectForwardReconciler interface {
// ReconcileObjectForward is the ADDITIVE forward fast-path for one object: it
// materializes ONLY that object's per-object tuples across the matching bindings
// while holding NO advisory lock at all (neither EXCLUSIVE nor SHARE, no O(scope)
// recompute). It transparently
// delegates to the FULL ReconcileObject when the object already has members
// (delete-stale guard) — which is the branch a REVOCATION takes.
ReconcileObjectForward(ctx context.Context, objectType, objectID string) error
}
ObjectForwardReconciler — narrow post-commit port: re-materialize the per-object access of ONE iam-native object across the bindings whose selectors match it.
Deliberately NOT the package's SelectorReconciler (create.go): that port carries the binding-membership passes and the nothing-stale object entry point, none of which this path may use — a label-clear on an EXISTING binding must go through the guard-bearing forward (see doUpdate). It declares only the one method this path calls; the FULL ReconcileObject is driven by the reconcile worker off the co-committed event, never through here. Implemented by reconcile.Reconciler (the SAME single materialization path the reconcile worker and the cross-service RegisterResource drive). nil-safe: when unwired, the co-committed reconcile event + the periodic sweep remain the at-least-once backstop.
type Principal ¶
type Principal struct {
Type domain.SubjectType
ID domain.SubjectID
}
Principal — a concrete grantee resolved by ExpandAccess (USER or SERVICE_ACCOUNT; never a GROUP).
type PrincipalLister ¶
type PrincipalLister interface {
ListUsers(ctx context.Context, objectType, objectID, relation string, userTypes []string) (principals []string, storeTruncated bool, err error)
}
PrincipalLister — narrow port: resolve the CONCRETE principals (FGA-prefixed "user:…" / "service_account:…") that hold object+relation, traversing the full authorization graph (computed usersets + scope_grant indirection + group memberships). Implemented by the decision door over the relational form.
The second result is the SOURCE's truncation signal — whether the answer is a prefix is a fact only the source's reply can carry, because measuring the returned length against anything the use-case itself chose cannot detect a cut made upstream. It USED TO BE produced: the external engine bounded its own answer and offered no continuation. The form does not produce it — its enumeration is paged and continuable, so an incomplete answer that cannot be asked further does not arise — and it reads false. That false is HONEST, not a stub, and the field stays because the caller reads it and because the next source may truncate again.
type Reader ¶
type Reader = kanamerepo.Reader
type Repo ¶
type Repo = kanamerepo.Repository
type RevokeAccessBindingUseCase ¶
type RevokeAccessBindingUseCase struct {
// contains filtered or unexported fields
}
func NewRevokeAccessBindingUseCase ¶
func NewRevokeAccessBindingUseCase(r Repo, opsRepo operations.Repo) *RevokeAccessBindingUseCase
func (*RevokeAccessBindingUseCase) Execute ¶
func (u *RevokeAccessBindingUseCase) Execute(ctx context.Context, id domain.AccessBindingID) (*operations.Operation, error)
func (*RevokeAccessBindingUseCase) WithRelationStore ¶
func (u *RevokeAccessBindingUseCase) WithRelationStore(relations clients.RelationStore, logger *slog.Logger) *RevokeAccessBindingUseCase
WithRelationStore wires the decision door (read-side grant-authority; synchronous revoke applier). Logger diagnoses sync-removal failures.
type RoleMembershipFanout ¶
type RoleMembershipFanout struct {
// contains filtered or unexported fields
}
RoleMembershipFanout — the role.RulesMembershipFanout implementation.
func NewRoleMembershipFanout ¶
func NewRoleMembershipFanout(repo kanamerepo.Repository, reconciler membershipReconciler) *RoleMembershipFanout
NewRoleMembershipFanout constructs the fan-out over the repo + the reconciler.
func (*RoleMembershipFanout) CountActiveBindings ¶
func (f *RoleMembershipFanout) CountActiveBindings(ctx context.Context, roleID domain.RoleID) (int, error)
CountActiveBindings returns the number of ACTIVE/PENDING bindings of a role (the bound-check). Read-only, pool-scoped.
func (*RoleMembershipFanout) ReconcileActiveBindings ¶
func (f *RoleMembershipFanout) ReconcileActiveBindings(ctx context.Context, roleID domain.RoleID) error
ReconcileActiveBindings re-materializes the role.rules membership of every ACTIVE binding of the role. Each binding is reconciled in its OWN writer-tx (the reconciler opens it), so a slow fan-out does not hold one long lock; the per- binding diff is idempotent (a re-run converges). The list is read once (pool-scoped); a binding revoked mid-fan-out is a no-op in ReconcileBinding (LoadBinding sees !Active). Bounded by the sync count-check upstream.
A failing binding does NOT abort the rest — see the loop below.
type RoleTupleReconciler ¶
type RoleTupleReconciler struct{}
RoleTupleReconciler — the Role.Update reconcile fan-out.
func NewRoleTupleReconciler ¶
func NewRoleTupleReconciler() *RoleTupleReconciler
NewRoleTupleReconciler constructs the reconciler (stateless — all state lives in the writer-tx passed to ReconcileRoleTuples).
func (*RoleTupleReconciler) ReconcileRoleTuples ¶
func (r *RoleTupleReconciler) ReconcileRoleTuples(ctx context.Context, w kanamerepo.Writer, roleID domain.RoleID, newRole domain.Role) error
ReconcileRoleTuples reconciles every ACTIVE binding of roleID from its stored emitted-set (old) to the tuples derived from newRole (new), inside the caller's writer-tx (the same tx as the role UPDATE — atomic, ban #10).
Per binding:
- derive newTuples = buildBindingTuples(binding, newRole) — the thin-binding scope-anchor / per-rule scope_grant projection,
- read oldTuples = SelectEmittedTuples(binding),
- diff: removed = old\new, added = new\old,
- EmitRelationDelete(removed) + EmitRelationWrite(added) + ReplaceEmittedTuples(binding, newTuples).
Bounded: the fan-out iterates only the ACTIVE bindings of the SINGLE mutated role (ListActiveByRole), not all bindings. Idempotent: an unchanged tier yields removed=added=∅ (no fga_outbox rows, the ledger replace is a no-op swap) — so a re-run / replayed Operation does not double-emit.
type SelectorReconciler ¶
type SelectorReconciler interface {
// ReconcileBindingForward is the ADDITIVE create-path fast-path (sub-phase IAM-FMB):
// it materializes THIS freshly-created binding's per-object membership additively while
// holding NO advisory lock at all (neither EXCLUSIVE nor SHARE, no FOR UPDATE, no
// delete-stale); the throughput fix
// for a mass-binding-create burst. It transparently delegates to the FULL
// ReconcileBinding if the binding already has members (delete-stale guard).
ReconcileBindingForward(ctx context.Context, bindingID domain.AccessBindingID) error
// ReconcileBinding is the FULL EXCLUSIVE path (Role.Update fan-out + sweep backstop);
// still consumed by the reconcile machinery, not by the create hot-path.
ReconcileBinding(ctx context.Context, bindingID domain.AccessBindingID) error
// ReconcileObjectForwardNoStale is the ADDITIVE forward fast-path for the freshly-created
// binding-AS-OBJECT (iam.accessBinding): it materializes ONLY that new object's
// per-object owner/admin tuples across the matching bindings while holding NO
// advisory lock at all (neither EXCLUSIVE nor SHARE, no O(scope) recompute); the throughput fix for the owner-tuple
// materialization lag under a parallel create burst.
//
// The …NoStale variant asserts what only this call site knows: the object id was minted
// in the writer-tx that has just committed, so it never existed before and carries
// nothing stale. That matters because the SIBLING pass above
// (ReconcileBindingForward) may already have written a member row ON THIS VERY
// OBJECT — an anchor/`*.*` role in scope covers iam.accessBinding, i.e. the row just
// created — and the plain ReconcileObjectForward reads such rows as "this object
// existed before" and falls back to the FULL EXCLUSIVE ReconcileObject, the very
// serialization this fast-path exists to avoid (measured: account-admin verbs ~67s
// after create). The delete-stale guard is untouched for every other caller.
ReconcileObjectForwardNoStale(ctx context.Context, objectType, objectID string) error
// ReconcileObject is the FULL EXCLUSIVE object-fan-out (async at-least-once backstop —
// delete-stale / audit / sweep); still driven by the reconcile worker off the
// co-committed reconcile-outbox event, not by the create hot-path.
ReconcileObject(ctx context.Context, objectType, objectID string) error
}
SelectorReconciler — narrow port (γ): materialize a selector binding's membership right after Create commits, so the membership + per-object FGA tuples are observable when the Operation reports done (γ-01). Implemented by the reconcile.Reconciler. Optional — nil-safe: when unwired the periodic sweep (D12) still materializes it, just not synchronously.
rbac-contract-a-fix (C-01b): the port ALSO exposes ReconcileObject — the freshly-created access_binding is itself an iam-native OBJECT (iam.accessBinding) whose per-object access (owner / account-admin admin/v_*) must be materialized under the flat model (the `from <scope>` ACCESS cascade on iam_access_binding is gone). ReconcileBinding materializes THIS binding's own grant membership; ReconcileObject materializes every OTHER binding's per-object access ON this new binding-object. Both run synchronously post-commit so a GET right after the Operation reports done does not race the async drain.
type UpdateAccessBindingUseCase ¶
type UpdateAccessBindingUseCase struct {
// contains filtered or unexported fields
}
func NewUpdateAccessBindingUseCase ¶
func NewUpdateAccessBindingUseCase(r Repo, opsRepo operations.Repo) *UpdateAccessBindingUseCase
func (*UpdateAccessBindingUseCase) Execute ¶
func (u *UpdateAccessBindingUseCase) Execute(ctx context.Context, id domain.AccessBindingID, mask []string, deletionProtection bool, labels domain.Labels) (*operations.Operation, error)
Execute — sync validate (id + update_mask discipline + grant-authority) → Operation → worker (deletion_protection + labels в одной writer-tx). AB mutable set = {deletion_protection, labels} (T3.3-IMM-01).
func (*UpdateAccessBindingUseCase) WithObjectReconciler ¶
func (u *UpdateAccessBindingUseCase) WithObjectReconciler(r ObjectForwardReconciler) *UpdateAccessBindingUseCase
WithObjectReconciler wires the post-commit per-object materializer used on a LABEL change (parity with the cross-service RegisterResource re-register path — see doUpdate). Optional; nil keeps the queue-only behaviour. It takes no logger on purpose: WithRelationStore already supplies one, and a second setter carrying it would silently nil it whenever the two are wired in the other order.
func (*UpdateAccessBindingUseCase) WithRelationStore ¶
func (u *UpdateAccessBindingUseCase) WithRelationStore(relations clients.RelationStore, logger *slog.Logger) *UpdateAccessBindingUseCase
WithRelationStore wires the door to the rights decision used by requireGrantAuthority (delegated-admin authority path), mirroring Create/Delete.
type Writer ¶
type Writer = kanamerepo.Writer
Source Files
¶
- create.go
- delete.go
- delta_input.go
- expand_access.go
- get.go
- handler.go
- helpers.go
- iface.go
- list.go
- list_assignable_roles.go
- list_by_account.go
- list_by_role.go
- list_by_scope.go
- list_by_subject.go
- list_subject_privileges.go
- revoke.go
- revoke_set.go
- revoke_subject_fanout.go
- role_tuple_reconciler.go
- rules_membership_fanout.go
- scope_coordinate.go
- structural_gates.go
- subject_proto.go
- subject_read_authority.go
- tuples.go
- update.go