Documentation
¶
Overview ¶
authz.go: the authorization decision in front of every dashboard intent.
The REST API refuses a caller with no identity, then asks the engine whether that caller holds the permission the route maps to (see api/auth.go, defaultAuthorize). The dashboard contract path is a second door onto the same mutations, and it had neither step: an empty `requires` predicate allows everyone in Forge's contract transport, including a request with no user at all, and the dashboard's own auth middleware never blocks. So this file puts the REST model on the contract path.
Every intent in manifest.yaml declares `requires: { warden: warden.engine }`. Forge's transport runs that delegate after the (empty) boolean predicate and before the dispatcher, and treats an error or a non-allow decision as a 403. The delegate below refuses anything it cannot positively identify and authorize.
Package contract wires warden into the Forge dashboard's contract path. It registers the `warden` contributor with the dashboard's contract registry and answers the intents the React plugin reads.
Warden continues to expose its templ pages through DashboardContributor while this package grows; the templ dashboard is retired once every surface has an equivalent here. See warden/MIGRATION.md for the accounting.
handlers_assignments.go: the assignment surface.
Assignments are create-and-delete only, because assignment.Store exposes no update. So there is no patch input here and no edit form on the page: changing who holds a role means deleting one binding and making another.
The one thing this file cares about more than the rest is expiry. The engine's resolution path filters expired assignments, so an expired row grants nothing. The LISTING path does not filter them, so they are visible. A page that showed them without saying so would be reporting access that does not exist, which is why every projected row carries Expired.
handlers_checklogs.go: the check log list and one check's detail.
There is no purge intent here. Retention runs through maintenance.run, which purges only the caller's tenant's entries older than the configured retention window (PurgeCheckLogsForTenant), never an arbitrary range.
handlers_config.go: the read-only view of the engine's configuration.
Warden's Config comes from Forge config, not from a store, so there is no write path and no settings intent. The templ dashboard's settings panel had no inputs either: it showed some of these values as text and badges.
handlers_graphs.go: the two graph views, the schema graph and the rooted relation expansion.
The schema graph is drawn from resource types alone: every relation a type declares names the subject types it accepts, and each one is an edge. It says what the schema allows, not what is stored.
The expansion is the opposite. It runs the ReBAC walker's own traversal from one object and relation with no target (Engine.ExpandRelation), so it shows what is stored and reachable from there, under the engine's graph budget. It reads tuples only, so it needs no resource type grant.
handlers_maintenance.go: the two operational commands.
These are the only writes warden's config surface has. maintenance.run purges the caller's tenant's expired assignments and, when CheckLogRetention is positive, that tenant's check log entries past it. Cache invalidation is the manual version of what a write would do automatically.
handlers_namespaces.go: the derived namespace list.
There is no namespace entity in warden. No table, no CRUD, no create. A namespace exists only as a string on rows, so this scans the entity tables for distinct values. Check log rows count too, because a leaf namespace where checks run may hold no role, grant or assignment of its own, but only when the path is valid: Check never validates one. The tenant root ("") is always present, because it is a real place where things live and the filter needs it as an option distinct from "all namespaces".
handlers_overview.go: the entity counters and the recent-checks list.
handlers_permissions.go: the permission surface.
One thing about permissions matters more than the rest: the RBAC evaluator matches a request against Resource + ":" + Action, and never against Name. So Name is a label, and a permission whose name disagrees with its resource and action is invisible to every check that looks for it by name while appearing correct in every list. This file refuses to write that state.
handlers_playground.go: the policy playground's explain intent.
playground.batchCheck runs several checks as dry runs at one namespace and reports each outcome.
playground.explain runs one authorization check as a dry run and reports what each model did. The check it builds writes no check log row, fires no hooks and reads no cache, so it is a query: a viewer who may run checks may run it. Authorizing the call is a dry run too, as for every query.
handlers_policies.go: the policy surface, reads and writes.
A policy row carries two kinds of fact. The stored fields say what was written. analysePolicy says what the policy will do, and the two disagree more often than they should: an allow whose condition throws looks exactly like a working allow, and a deny with a broken condition applies to every check. So the state, failsClosed, neverApplies and matchesEverything flags on every row come from the analysis, computed at read time, never stored and never guessed by a page.
The writes hold two rules the REST API does not. Every write is validated by collectPolicyIssues, so the dashboard cannot store a condition that cannot behave the way it reads. And a create is always stored inactive: a new policy with no matchers matches every check in its namespace and below, so storing it active would make an empty allow grant everything. Activation is an explicit policies.setActive.
handlers_relations.go: the relation surface.
A tuple is object#relation@subject, Zanzibar style: document:readme#viewer@user:bob.
Two things about tuples that differ from everything else in this contract.
They are create-and-delete only. relation.Store exposes no update, so changing a tuple means deleting one and writing another. There is no patch input here and no edit form on the page.
Their namespace cascades at check time but not in the list. Like roles and policies, a tuple stored at a namespace is in scope for a check in that namespace and in every namespace below it: the engine passes AncestorNamespaces(checkNamespace) to every tuple lookup a check makes, in the direct check, the expression evaluator and the graph walker alike (see evaluateReBAC in engine.go and TestReBAC_NamespaceCascade). The list filter, by contrast, is an exact match on purpose, so it shows what is stored in one namespace. A namespace's listing therefore does NOT show a parent's tuples that also apply there, and a page must say so rather than let the listing read as everything in effect.
handlers_resourcetypes.go: the ReBAC schema surface.
A resource type declares its relations (viewer, editor, parent) and the permissions derived from them (read = viewer or editor or parent->read).
Those expressions are a real language with a real parser, dsl.CompileExpr, which returns diagnostics carrying Pos{Line, Col}. Nothing calls it on a write. api/resourcetype_handler.go copies Expression straight through, and the store stores whatever it is handed. An invalid expression therefore saves without complaint and fails at CHECK time, where evaluateReBAC logs a warning and treats it as no match. The permission silently never grants, and nothing an operator can see says why.
So this file validates on write, twice. First the expression must parse. Second, every relation it names must be one the type declares, which the parser cannot know and which is the other way to write an expression that can never match.
handlers_roles.go: the role surface.
Roles carry more than the templ dashboard ever showed: a namespace, the system and default flags, a member cap, and parent-slug inheritance. Slugs are unique per (tenant, namespace), which is why the namespace is on every read and every write.
handlers_schema.go: the schema as Warden source. schema.export writes a tenant's state as canonical source, schema.plan reports what applying an edited copy would do without doing it, and schema.apply does it, but only as planned: it re-plans and refuses unless the digest the operator saw still matches.
Both wrap the dsl package. The dashboard never reads a file, so anything the file loader would supply is refused instead: imports, because there is nothing to resolve them against, and a `tenant` that is not the caller's, because the dashboard applies to the caller's tenant only and a source that says otherwise is either a mistake or an attempt to aim at another tenant. Variable placeholders (`${NAME}`) are refused by the lexer itself: it has no `$` token, so the parser reports the character where it stands.
The two intents are gated on read of warden:role, which intentPolicies enforces, and they return warden:permission, warden:policy, warden:resourcetype and warden:relation rows as source. The handler asks for those reads itself, as subjects.detail does, so a caller who may read roles cannot read the rest through this door.
handlers_subjects.go: what one subject can do at a namespace, and why.
immutable.go: the system-entity guard.
READ THIS BEFORE REMOVING IT AS REDUNDANT. It is not redundant. No store checks IsSystem on update or delete, and the engine does not check it. The rule lives in role.CheckWritable and permission.CheckWritable, which this file and the REST API (api/role_handler.go, api/permission_handler.go) both call, so the two refuse the same writes in the same words. Without these two functions this dashboard will let an operator rename a system role, change its parent, or delete it, and the store will do it.
The guard belongs here rather than in each handler so that the plans after this one cannot add a role or permission write that forgets it.
schema.plan and schema.apply write through dsl.Apply, which cannot call these two. They set dsl.ApplyOptions.ProtectSystem instead, which refuses the same writes with the same sentence, as a diagnostic at the declaration. That sentence names a role by its slug, which is what the source declares, where this guard and REST name it by its name.
members.go: the member-cap guards.
READ THIS BEFORE REMOVING THEM AS REDUNDANT. No store enforces Role.MaxMembers: none counts a role's members before inserting an assignment, and none compares a new cap with the members a role already has. warden defines ErrMaxMembersExceeded, and nothing returns it.
The cap is enforced above the store, in package assignment, and every warden write path calls it. Assigning a subject goes through assignment.CheckMemberCap here (guardMemberCap) and in the REST assignment create. Lowering a cap goes through assignment.CheckCapLowering here (guardCapLowering), in the REST role update and in a DSL apply. The one deliberate exception is BootstrapAdmin (extension/bootstrap.go), the break-glass path that assigns the bootstrap admin role whatever its cap says, so a capped bootstrap role can end up over its cap. Code that writes through the store directly checks nothing.
mutate.go: what every dashboard write does after the store call.
The REST handlers in api/ emit a typed hook and an audit event for each mutation, with the caller as the actor. A dashboard write used to skip all of it: the store changed, and nothing else heard about it. The consequences were an audit plugin that never saw dashboard changes, a cache that kept serving an ALLOW for a grant that was gone until its TTL ran out, and CreatedBy/UpdatedBy left empty.
This helper duplicates the small emit pattern from api/role_handler.go and api/permission_handler.go instead of sharing it. Sharing would need a third package both api/ and extension/contract import, for about ten lines, and the two call sites differ in what they have in hand (a forge context there, a Principal here).
paging.go: the one paging shape every list intent in this contract uses.
Warden's stores all expose ListX(filter) plus CountX(filter) with Limit and Offset, and none of them offers a cursor. So this contract pages by offset everywhere, and there is no second convention to translate between.
policy_analysis.go: what a policy will actually do, decided before any check runs.
Every rule here mirrors warden's evaluator (evaluator.go) and is tested against it in policy_analysis_test.go by running each case through warden.NewConditionEvaluator. If the engine changes, that test fails, rather than a page quietly describing behaviour the engine no longer has. One rule mirrors the engine instead: requiredFields is the set of fields engine.go's prepareCheck refuses when empty, so it follows Check, not the evaluator.
Why this exists: nothing validates a policy on write (policy.Validate has no non-test caller, and the REST create stores whatever it is sent), and several shapes store fine while doing something other than what they read as. `context.ip not_in "10.0.0.0/8"` with a string instead of a list is always true, so on a deny it denies everyone. `gt` against a non-number is always false. `action.verb` never resolves, so `neq` on it is always true.
policy_validate.go: refuse, on write, the policy shapes that store fine but cannot behave the way they read.
Nothing below the contract does this: policy.Validate has no non-test caller. And policy.ValidateCondition on its own is not enough, because it accepts fields the evaluator never resolves (bare "action", "action.x") and says nothing about the always-true and always-false shapes classifyCondition finds.
Index ¶
- Constants
- func Register(d *dispatcher.Dispatcher, reg dashcontract.Registry, ...) error
- type AckResponse
- type AssignmentCreateInput
- type AssignmentDeleteInput
- type AssignmentSummary
- type AssignmentsListInput
- type AssignmentsListResponse
- type CacheInvalidateInput
- type CacheInvalidateResult
- type CheckLogDetail
- type CheckLogDetailInput
- type CheckLogLossView
- type CheckLogMatch
- type CheckLogSummary
- type CheckLogsListInput
- type CheckLogsListResponse
- type ConditionIssue
- type ConditionProblem
- type ConditionReason
- type ConfigDetail
- type Deps
- type ExpiringInput
- type ExpiringResponse
- type ExpressionDiagnostic
- type MaintenanceResult
- type NamespacesResponse
- type OverviewStats
- type PageMeta
- type PageRequest
- type PermissionCreateInput
- type PermissionDefDTO
- type PermissionDeleteInput
- type PermissionDetail
- type PermissionDetailInput
- type PermissionRef
- type PermissionSummary
- type PermissionUpdateInput
- type PermissionsListInput
- type PermissionsListResponse
- type PlaygroundBatchInput
- type PlaygroundBatchItem
- type PlaygroundBatchResponse
- type PlaygroundBatchResult
- type PlaygroundExplainInput
- type PlaygroundExplainResponse
- type PlaygroundLane
- type PoliciesListInput
- type PoliciesListResponse
- type PolicyCondition
- type PolicyConditionView
- type PolicyCreateInput
- type PolicyDeleteInput
- type PolicyDetail
- type PolicyDetailInput
- type PolicyDraft
- type PolicyIssues
- type PolicySetActiveInput
- type PolicySubject
- type PolicySummary
- type PolicyUpdateInput
- type PolicyValidateResponse
- type RecentChecksInput
- type RecentChecksResponse
- type RelationCreateInput
- type RelationDefDTO
- type RelationDeleteInput
- type RelationExpandEdge
- type RelationExpandInput
- type RelationExpandNode
- type RelationExpandResponse
- type RelationSummary
- type RelationsListInput
- type RelationsListResponse
- type ResourceTypeCreateInput
- type ResourceTypeDeleteInput
- type ResourceTypeDetail
- type ResourceTypeDetailInput
- type ResourceTypeGraphEdge
- type ResourceTypeGraphInput
- type ResourceTypeGraphNode
- type ResourceTypeGraphResponse
- type ResourceTypeSummary
- type ResourceTypeUpdateInput
- type ResourceTypesListInput
- type ResourceTypesListResponse
- type RoleCreateInput
- type RoleDeleteInput
- type RoleDetail
- type RoleDetailInput
- type RolePermissionInput
- type RoleSetPermissionsInput
- type RoleSummary
- type RoleUpdateInput
- type RolesListInput
- type RolesListResponse
- type SchemaApplyInput
- type SchemaApplyResponse
- type SchemaDiagnostic
- type SchemaExportInput
- type SchemaExportResponse
- type SchemaPlanInput
- type SchemaPlanResponse
- type SubjectAssignment
- type SubjectDetailInput
- type SubjectDetailResponse
- type SubjectPermission
- type SubjectPolicy
- type SubjectRelation
- type SubjectRole
Constants ¶
const ( StateActive = "active" StateInactive = "inactive" StateScheduled = "scheduled" StateExpired = "expired" StateNever = "never" )
Policy states. "never" is a window whose end precedes its start: under EffectiveAt it cannot be in effect at any instant.
Variables ¶
This section is empty.
Functions ¶
func Register ¶
func Register( d *dispatcher.Dispatcher, reg dashcontract.Registry, wreg dashcontract.WardenRegistry, deps Deps, ) error
Register loads the embedded manifest, validates it, registers the `warden` contributor with reg, and binds the handlers against deps.
Types ¶
type AckResponse ¶
type AckResponse struct {
ID string `json:"id,omitempty"`
}
AckResponse is what a command returns when the only thing worth reporting is which row it touched. ID is empty for a delete.
type AssignmentCreateInput ¶
type AssignmentCreateInput struct {
RoleID string `json:"roleId"`
SubjectKind string `json:"subjectKind"`
SubjectID string `json:"subjectId"`
NamespacePath string `json:"namespacePath,omitempty"`
ResourceType string `json:"resourceType,omitempty"`
ResourceID string `json:"resourceId,omitempty"`
ExpiresAt string `json:"expiresAt,omitempty"`
}
AssignmentCreateInput binds a subject to a role.
ExpiresAt is an RFC3339 string rather than a time, because that is what the wire carries and parsing it here lets a malformed value be a BAD_REQUEST naming the field instead of a JSON decode failure naming nothing.
type AssignmentDeleteInput ¶
type AssignmentDeleteInput struct {
ID string `json:"id"`
}
AssignmentDeleteInput names the binding to remove.
type AssignmentSummary ¶
type AssignmentSummary struct {
ID string `json:"id"`
NamespacePath string `json:"namespacePath"`
RoleID string `json:"roleId"`
RoleSlug string `json:"roleSlug"`
RoleName string `json:"roleName"`
SubjectKind string `json:"subjectKind"`
SubjectID string `json:"subjectId"`
ResourceType string `json:"resourceType,omitempty"`
ResourceID string `json:"resourceId,omitempty"`
ExpiresAt string `json:"expiresAt,omitempty"`
Expired bool `json:"expired"`
GrantedBy string `json:"grantedBy,omitempty"`
CreatedAt string `json:"createdAt"`
}
AssignmentSummary is one row of the assignments list.
RoleSlug is denormalised deliberately. A row that named only a role id would be unreadable, and resolving it per row in the page would be a request per row.
Expired is computed rather than stored, because the store has no such column: it is ExpiresAt compared against now, using the same test the engine applies when it resolves roles.
type AssignmentsListInput ¶
type AssignmentsListInput struct {
PageRequest
NamespacePath *string `json:"namespacePath,omitempty"`
RoleID string `json:"roleId,omitempty"`
SubjectKind string `json:"subjectKind,omitempty"`
SubjectID string `json:"subjectId,omitempty"`
ResourceType string `json:"resourceType,omitempty"`
ResourceID string `json:"resourceId,omitempty"`
}
AssignmentsListInput filters the assignments list.
type AssignmentsListResponse ¶
type AssignmentsListResponse struct {
PageMeta
Items []AssignmentSummary `json:"items"`
}
AssignmentsListResponse is the paged reply.
type CacheInvalidateInput ¶
type CacheInvalidateInput struct {
SubjectKind string `json:"subjectKind,omitempty"`
SubjectID string `json:"subjectId,omitempty"`
}
CacheInvalidateInput selects what to flush. Both fields empty flushes the whole tenant; both set flushes one subject. One without the other is an error rather than a silent widening to the tenant.
type CacheInvalidateResult ¶
type CacheInvalidateResult struct {
Scope string `json:"scope"` // "tenant" or "subject"
}
CacheInvalidateResult reports which scope was flushed, so the page can say what it did rather than "done".
type CheckLogDetail ¶
type CheckLogDetail struct {
CheckLogSummary
AppID string `json:"appId,omitempty"`
MatchedBy []CheckLogMatch `json:"matchedBy"`
Obligations []string `json:"obligations"`
RequestIP string `json:"requestIp,omitempty"`
RequestID string `json:"requestId,omitempty"`
TraceID string `json:"traceId,omitempty"`
}
CheckLogDetail is the checkLogs.detail reply: the summary plus what an auditor needs to reconstruct the decision without replaying the check.
type CheckLogDetailInput ¶
type CheckLogDetailInput struct {
ID string `json:"id"`
}
CheckLogDetailInput is the checkLogs.detail request.
type CheckLogLossView ¶
type CheckLogLossView struct {
QueueFull uint64 `json:"queueFull"`
WriteFailed uint64 `json:"writeFailed"`
Since string `json:"since"`
}
CheckLogLossView is the engine's count of decided checks that left no row. It is per server process and covers every tenant that process serves, because the writer's queue is shared.
type CheckLogMatch ¶
type CheckLogMatch struct {
Source string `json:"source"`
RuleID string `json:"ruleId,omitempty"`
Detail string `json:"detail,omitempty"`
}
CheckLogMatch is one rule that contributed to a decision.
type CheckLogSummary ¶
type CheckLogSummary struct {
ID string `json:"id"`
NamespacePath string `json:"namespacePath"`
SubjectKind string `json:"subjectKind"`
SubjectID string `json:"subjectId"`
Action string `json:"action"`
ResourceType string `json:"resourceType"`
ResourceID string `json:"resourceId"`
Decision string `json:"decision"`
Reason string `json:"reason,omitempty"`
EvalTimeNs int64 `json:"evalTimeNs"`
Cached bool `json:"cached"`
Error string `json:"error,omitempty"`
CreatedAt string `json:"createdAt"`
}
CheckLogSummary is one row of the recent-checks list and of the check log page. Cached and Error are carried because they are this page's scan signal: allow versus deny volume depends on the deployment's posture, so neither can be the thing colour keys off.
type CheckLogsListInput ¶
type CheckLogsListInput struct {
PageRequest
// NamespacePath is a pointer because the root namespace is the empty
// string: nil means every namespace, "" means the root only.
NamespacePath *string `json:"namespacePath,omitempty"`
SubjectKind string `json:"subjectKind,omitempty"`
SubjectID string `json:"subjectId,omitempty"`
Action string `json:"action,omitempty"`
ResourceType string `json:"resourceType,omitempty"`
ResourceID string `json:"resourceId,omitempty"`
Decision string `json:"decision,omitempty"`
Cached *bool `json:"cached,omitempty"`
// After and Before are RFC 3339 instants, both inclusive. Empty means
// unbounded.
After string `json:"after,omitempty"`
Before string `json:"before,omitempty"`
}
CheckLogsListInput is the checkLogs.list request. Every filter is optional and they combine with AND.
type CheckLogsListResponse ¶
type CheckLogsListResponse struct {
PageMeta
Items []CheckLogSummary `json:"items"`
// NotRecorded is absent when check logging is off.
NotRecorded *CheckLogLossView `json:"notRecorded,omitempty"`
}
CheckLogsListResponse is the checkLogs.list reply.
type ConditionIssue ¶
ConditionIssue marks one condition row.
type ConditionProblem ¶
type ConditionProblem string
ConditionProblem is a condition's outcome when it does not depend on the check being made.
const ( ProblemNone ConditionProblem = "" ProblemThrows ConditionProblem = "throws" // evaluation errors: a deny fails closed, an allow is skipped ProblemAlwaysFalse ConditionProblem = "alwaysFalse" // the policy can never apply ProblemAlwaysTrue ConditionProblem = "alwaysTrue" // the condition restricts nothing )
type ConditionReason ¶
type ConditionReason string
ConditionReason says why a condition's outcome is fixed.
const ( ReasonNone ConditionReason = "" ReasonUnknownOperator ConditionReason = "unknownOperator" ReasonInvalidRegex ConditionReason = "invalidRegex" ReasonUnresolvableField ConditionReason = "unresolvableField" ReasonNotAList ConditionReason = "notAList" ReasonEmptyList ConditionReason = "emptyList" ReasonNotANumber ConditionReason = "notANumber" ReasonNoValidCIDR ConditionReason = "noValidCIDR" ReasonNotATime ConditionReason = "notATime" ReasonAlwaysPresent ConditionReason = "alwaysPresent" ReasonMatchesAnything ConditionReason = "matchesAnything" )
type ConfigDetail ¶
type ConfigDetail struct {
MaxGraphDepth int `json:"maxGraphDepth"`
MaxGraphVisited int `json:"maxGraphVisited"`
MaxGraphFanout int `json:"maxGraphFanout"`
MaxBatchChecks int `json:"maxBatchChecks"`
CacheTTLSeconds int `json:"cacheTtlSeconds"`
CacheMaxSize int `json:"cacheMaxSize"`
RBACEnabled bool `json:"rbacEnabled"`
ABACEnabled bool `json:"abacEnabled"`
ReBACEnabled bool `json:"rebacEnabled"`
CheckLogEnabled bool `json:"checkLogEnabled"`
RequireTenant bool `json:"requireTenant"`
EvaluateAllModels bool `json:"evaluateAllModels"`
CheckLogQueueSize int `json:"checkLogQueueSize"`
// CheckLogRetentionSeconds and MaintenanceIntervalSeconds carry the
// durations in whole seconds, rounded up, so a positive duration never
// reads as 0, and 0 means off: the engine purges no check log entries
// (or runs no maintenance loop). The engine reads a 0 or negative
// duration as off, and both are sent as 0, so the wire never carries a
// negative number. CheckLogRetentionHours and MaintenanceIntervalMin
// stay for clients older than the seconds fields. They are rounded down
// and also 0 when off, so there a retention under an hour reads 0, the
// same as off.
CheckLogRetentionSeconds int64 `json:"checkLogRetentionSeconds"`
MaintenanceIntervalSeconds int64 `json:"maintenanceIntervalSeconds"`
CheckLogRetentionHours int64 `json:"checkLogRetentionHours"`
MaintenanceIntervalMin int64 `json:"maintenanceIntervalMinutes"`
// Plugins is the name of every plugin in the engine's registry when the
// request was served, sorted. It can include plugins warden registers on
// its own, such as the cache invalidators and the audit log sink, beside
// any a caller passed in. Never nil: an engine with no registry sends [].
Plugins []string `json:"plugins"`
}
ConfigDetail is the config.detail response.
Durations are flattened to whole units because the wire carries JSON and a Go duration serialises as a nanosecond integer nobody can read. The page formats from these.
type Deps ¶
type Deps struct {
// Engine is the live warden engine. Required.
Engine *warden.Engine
// DefaultTenantID is the tenant every dashboard request is scoped to
// when the principal carries no tenant claim. Required for any
// deployment that wants the dashboard to answer at all today, because
// nothing populates Principal.Claims yet.
//
// Leave it empty in a multi-tenant deployment. Every read then refuses
// with PERMISSION_DENIED, which is correct: a dashboard that cannot
// tell which tenant it is looking at must not guess, and the empty
// string would match every tenant's rows rather than none.
DefaultTenantID string
}
Deps bundles what the contract handlers need at registration time.
type ExpiringInput ¶
type ExpiringInput struct {
WithinHours int `json:"withinHours,omitempty"`
Limit int `json:"limit,omitempty"`
}
ExpiringInput asks what lapses within a horizon.
type ExpiringResponse ¶
type ExpiringResponse struct {
Items []AssignmentSummary `json:"items"`
}
ExpiringResponse is the reply. Not paged: this is a "what needs attention" feed with a caller-set limit, not a browsable collection, so it carries no PageMeta. Compare overview.recentChecks in the spine plan.
type ExpressionDiagnostic ¶
type ExpressionDiagnostic struct {
Permission string `json:"permission"`
Line int `json:"line"`
Col int `json:"col"`
Message string `json:"message"`
}
ExpressionDiagnostic is one problem found in a permission expression. Line and Col are 1-based and relative to the expression text, which is what lets an editor mark the exact column.
type MaintenanceResult ¶
type MaintenanceResult struct {
AssignmentsPurged int64 `json:"assignmentsPurged"`
CheckLogsPurged int64 `json:"checkLogsPurged"`
}
MaintenanceResult is the maintenance.run reply.
Both counters are always present, including at zero, because zero is a real outcome: the run succeeded and there was nothing to purge. A page that could not distinguish that from a failure would have to guess.
type NamespacesResponse ¶
type NamespacesResponse struct {
Namespaces []string `json:"namespaces"`
}
NamespacesResponse is the namespaces.list reply. Paths are sorted, with the tenant root first because the empty string sorts first anyway and the filter renders it at the top.
type OverviewStats ¶
type OverviewStats struct {
Roles int64 `json:"roles"`
Permissions int64 `json:"permissions"`
Assignments int64 `json:"assignments"`
Relations int64 `json:"relations"`
Policies int64 `json:"policies"`
ResourceTypes int64 `json:"resourceTypes"`
}
OverviewStats is the overview.stats response: one counter per entity kind in a single payload, so the six stat tiles cost one request.
type PageMeta ¶
type PageMeta struct {
Total int64 `json:"total"`
Limit int `json:"limit"`
Offset int `json:"offset"`
}
PageMeta is embedded in every list intent's response, beside its items.
Total is the count matching the filter rather than the number of rows returned, which is what a pager needs to know how many pages exist. Limit and Offset are echoed back as clamped, so a client that asked for a million rows can see what it actually got.
type PageRequest ¶
PageRequest is embedded in every list intent's input.
func (PageRequest) Clamp ¶
func (p PageRequest) Clamp() (limit, offset int)
Clamp returns a limit and offset safe to hand to a store filter. An unset or non-positive limit becomes the default, an oversized one is capped, and a negative offset becomes zero rather than reaching SQL as OFFSET -1.
type PermissionCreateInput ¶
type PermissionCreateInput struct {
Name string `json:"name,omitempty"`
Resource string `json:"resource"`
Action string `json:"action"`
NamespacePath string `json:"namespacePath,omitempty"`
Description string `json:"description,omitempty"`
}
PermissionCreateInput creates a permission. Name may be omitted, in which case it is derived as resource:action.
type PermissionDefDTO ¶
PermissionDefDTO mirrors resourcetype.PermissionDef on the wire.
type PermissionDeleteInput ¶
type PermissionDeleteInput struct {
ID string `json:"id"`
}
PermissionDeleteInput names the permission to remove.
type PermissionDetail ¶
type PermissionDetail struct {
PermissionSummary
GrantedBy []RoleSummary `json:"grantedBy"`
}
PermissionDetail is one permission plus the roles that grant it, which is the question an operator opens this page to answer.
type PermissionDetailInput ¶
type PermissionDetailInput struct {
ID string `json:"id"`
}
PermissionDetailInput names one permission.
type PermissionRef ¶
type PermissionRef struct {
Name string `json:"name"`
NamespacePath string `json:"namespacePath,omitempty"`
}
PermissionRef names one permission by its natural key.
The junction is keyed by (namespacePath, name), not by id, because that is what the DSL declares and what survives a re-apply. An empty NamespacePath means the tenant root, which is a real namespace rather than an absent value.
type PermissionSummary ¶
type PermissionSummary struct {
ID string `json:"id"`
NamespacePath string `json:"namespacePath"`
Name string `json:"name"`
Resource string `json:"resource"`
Action string `json:"action"`
Description string `json:"description,omitempty"`
IsSystem bool `json:"isSystem"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
PermissionSummary is one row of the permissions list, and one grant on a role's detail page. The same shape serves both so the role page can link each grant to its own permission row.
type PermissionUpdateInput ¶
type PermissionUpdateInput struct {
ID string `json:"id"`
Description *string `json:"description,omitempty"`
}
PermissionUpdateInput patches a permission. Resource and action are absent deliberately: changing either would change what the permission means without changing which roles grant it, silently altering every check that resolves through it. Delete and recreate instead.
type PermissionsListInput ¶
type PermissionsListInput struct {
PageRequest
NamespacePath *string `json:"namespacePath,omitempty"`
Resource string `json:"resource,omitempty"`
Action string `json:"action,omitempty"`
Search string `json:"search,omitempty"`
IsSystem *bool `json:"isSystem,omitempty"`
}
PermissionsListInput filters the permissions list.
type PermissionsListResponse ¶
type PermissionsListResponse struct {
PageMeta
Items []PermissionSummary `json:"items"`
}
PermissionsListResponse is the paged reply.
type PlaygroundBatchInput ¶
type PlaygroundBatchInput struct {
// NamespacePath applies to every item. "" is the root.
NamespacePath string `json:"namespacePath"`
Items []PlaygroundBatchItem `json:"items"`
}
PlaygroundBatchInput is the playground.batchCheck request.
There is no tenant field, on purpose. The tenant is the caller's, resolved by tenantFrom.
type PlaygroundBatchItem ¶
type PlaygroundBatchItem struct {
SubjectKind string `json:"subjectKind"`
SubjectID string `json:"subjectId"`
Action string `json:"action"`
ResourceType string `json:"resourceType"`
ResourceID string `json:"resourceId,omitempty"`
}
PlaygroundBatchItem is one check of a batch.
type PlaygroundBatchResponse ¶
type PlaygroundBatchResponse struct {
// Results is in item order, one per item.
Results []PlaygroundBatchResult `json:"results"`
}
PlaygroundBatchResponse is the playground.batchCheck reply.
type PlaygroundBatchResult ¶
type PlaygroundBatchResult struct {
// Decision is "error" when the item's check failed, with Error set.
Decision string `json:"decision"`
Allowed bool `json:"allowed"`
Reason string `json:"reason,omitempty"`
Error string `json:"error,omitempty"`
}
PlaygroundBatchResult is the outcome of one item.
type PlaygroundExplainInput ¶
type PlaygroundExplainInput struct {
SubjectKind string `json:"subjectKind"`
SubjectID string `json:"subjectId"`
Action string `json:"action"`
ResourceType string `json:"resourceType"`
ResourceID string `json:"resourceId,omitempty"`
// NamespacePath is where the check runs. "" is the tenant root. Always
// explicit: a playground check is at a namespace, never "any".
NamespacePath string `json:"namespacePath"`
Context map[string]any `json:"context,omitempty"`
SubjectAttributes map[string]any `json:"subjectAttributes,omitempty"`
ResourceAttributes map[string]any `json:"resourceAttributes,omitempty"`
}
PlaygroundExplainInput is the playground.explain request.
There is no tenant field, on purpose. The tenant is the caller's, resolved by tenantFrom, so a request can never ask about another tenant's data.
type PlaygroundExplainResponse ¶
type PlaygroundExplainResponse struct {
// Decision is "error" when a model failed, with Error set; otherwise
// the merged decision.
Decision string `json:"decision"`
Allowed bool `json:"allowed"`
Reason string `json:"reason,omitempty"`
Error string `json:"error,omitempty"`
MatchedBy []CheckLogMatch `json:"matchedBy"`
Obligations []string `json:"obligations"`
EvalTimeNs int64 `json:"evalTimeNs"`
// Lanes is always three, in pipeline order: rbac, rebac, abac.
Lanes []PlaygroundLane `json:"lanes"`
}
PlaygroundExplainResponse is the playground.explain reply.
type PlaygroundLane ¶
type PlaygroundLane struct {
// Model is "rbac", "rebac" or "abac".
Model string `json:"model"`
// State is one of warden.LaneState's values.
State string `json:"state"`
Decision string `json:"decision,omitempty"`
Reason string `json:"reason,omitempty"`
MatchedBy []CheckLogMatch `json:"matchedBy"`
WalkTruncated bool `json:"walkTruncated,omitempty"`
ExpressionError string `json:"expressionError,omitempty"`
Error string `json:"error,omitempty"`
}
PlaygroundLane is one authorization model's part in an explained check.
type PoliciesListInput ¶
type PoliciesListInput struct {
PageRequest
NamespacePath *string `json:"namespacePath,omitempty"`
Effect string `json:"effect,omitempty"`
IsActive *bool `json:"isActive,omitempty"`
Search string `json:"search,omitempty"`
}
PoliciesListInput filters the policy list. NamespacePath is a pointer for the same reason it is on every other list: nil means every namespace and "" means the root namespace only.
type PoliciesListResponse ¶
type PoliciesListResponse struct {
PageMeta
Items []PolicySummary `json:"items"`
}
PoliciesListResponse is the paged reply.
type PolicyCondition ¶
type PolicyCondition struct {
ID string `json:"id,omitempty"`
Field string `json:"field"`
Operator string `json:"operator"`
Value any `json:"value,omitempty"`
}
PolicyCondition is one condition on the wire. Value's JSON type follows the operator: a list for in, not_in and ip_in_cidr (ip_in_cidr also accepts one string), a number for gt, lt, gte and lte, an RFC3339 string for time_after and time_before, absent for exists and not_exists, and a string otherwise.
type PolicyConditionView ¶
type PolicyConditionView struct {
PolicyCondition
Problem string `json:"problem,omitempty"`
Reason string `json:"reason,omitempty"`
}
PolicyConditionView is a stored condition plus what it will do.
type PolicyCreateInput ¶
type PolicyCreateInput struct {
PolicyDraft
NamespacePath string `json:"namespacePath,omitempty"`
}
PolicyCreateInput creates a policy. It is always stored INACTIVE: a new policy with no matchers matches every check in its namespace and below, so storing it active would make an empty allow grant everything, or an empty deny lock everyone out, the moment it is saved. Activation is an explicit policies.setActive. The REST create honours isActive; this contract deliberately does not.
type PolicyDeleteInput ¶
type PolicyDeleteInput struct {
ID string `json:"id"`
}
PolicyDeleteInput names the policy to remove.
type PolicyDetail ¶
type PolicyDetail struct {
PolicySummary
Subjects []PolicySubject `json:"subjects"`
Actions []string `json:"actions"`
Resources []string `json:"resources"`
Conditions []PolicyConditionView `json:"conditions"`
Obligations []string `json:"obligations"`
NotBefore string `json:"notBefore,omitempty"`
NotAfter string `json:"notAfter,omitempty"`
SubjectsUnrestricted bool `json:"subjectsUnrestricted"`
ActionsUnrestricted bool `json:"actionsUnrestricted"`
ResourcesUnrestricted bool `json:"resourcesUnrestricted"`
HasRoleMatcher bool `json:"hasRoleMatcher"`
// DecidingCondition is the index of the condition that makes the
// policy fail closed or never apply. Absent when neither.
DecidingCondition *int `json:"decidingCondition,omitempty"`
CreatedBy string `json:"createdBy,omitempty"`
UpdatedBy string `json:"updatedBy,omitempty"`
CreatedAt string `json:"createdAt"`
}
PolicyDetail is one policy, everything the rule block renders.
type PolicyDetailInput ¶
type PolicyDetailInput struct {
ID string `json:"id"`
}
PolicyDetailInput names the policy to read, by id.
type PolicyDraft ¶
type PolicyDraft struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Effect string `json:"effect"`
Priority int `json:"priority"`
NotBefore string `json:"notBefore,omitempty"`
NotAfter string `json:"notAfter,omitempty"`
Subjects []PolicySubject `json:"subjects"`
Actions []string `json:"actions"`
Resources []string `json:"resources"`
Conditions []PolicyCondition `json:"conditions"`
Obligations []string `json:"obligations"`
}
PolicyDraft is the editable body of a policy.
type PolicyIssues ¶
type PolicyIssues struct {
Fields map[string]string `json:"fields"`
Conditions []ConditionIssue `json:"conditions"`
}
PolicyIssues is every reason a draft cannot be saved. Fields is keyed by wire name: name, effect, window, subjects, actions, resources, obligations. Conditions carries one entry per bad row, so a page can mark the exact row.
type PolicySetActiveInput ¶
PolicySetActiveInput turns a policy on or off and changes nothing else.
type PolicySubject ¶
type PolicySubject struct {
Kind string `json:"kind,omitempty"`
ID string `json:"id,omitempty"`
Role string `json:"role,omitempty"`
}
PolicySubject mirrors policy.SubjectMatch. Its three fields are AND-ed.
type PolicySummary ¶
type PolicySummary struct {
ID string `json:"id"`
NamespacePath string `json:"namespacePath"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Effect string `json:"effect"`
Priority int `json:"priority"`
IsActive bool `json:"isActive"`
State string `json:"state"`
FailsClosed bool `json:"failsClosed"`
NeverApplies bool `json:"neverApplies"`
MatchesEverything bool `json:"matchesEverything"`
Version int `json:"version"`
UpdatedAt string `json:"updatedAt"`
}
PolicySummary is one row of the policies list.
State, FailsClosed, NeverApplies and MatchesEverything are computed by analysePolicy, because the stored fields do not say them and a page that guessed would guess wrong: an allow whose condition throws looks exactly like a working allow.
type PolicyUpdateInput ¶
type PolicyUpdateInput struct {
ID string `json:"id"`
ExpectedVersion *int `json:"expectedVersion,omitempty"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
Effect *string `json:"effect,omitempty"`
Priority *int `json:"priority,omitempty"`
NotBefore *string `json:"notBefore,omitempty"`
NotAfter *string `json:"notAfter,omitempty"`
Subjects *[]PolicySubject `json:"subjects,omitempty"`
Actions *[]string `json:"actions,omitempty"`
Resources *[]string `json:"resources,omitempty"`
Conditions *[]PolicyCondition `json:"conditions,omitempty"`
Obligations *[]string `json:"obligations,omitempty"`
}
PolicyUpdateInput patches a policy. Namespace is not patchable. An empty string on NotBefore or NotAfter clears that bound.
ExpectedVersion is the version the caller loaded. When it is present and older than the stored version, the update is refused as stale and nothing is written, so an editor cannot save over a change it never saw. A version above the stored one, or a negative one, was never stored, so it is refused as bad input. Absent, the update is still guarded against a write that lands between this handler's own read and write, but not against anything older.
type PolicyValidateResponse ¶
type PolicyValidateResponse struct {
Valid bool `json:"valid"`
// MatchesEverything is the same analysis the policy list applies, run over
// the submitted draft: true when, saved as it stands, the policy would
// apply to every check. It lets an editor confirm before an active policy
// is saved into that shape, without repeating the analysis in the page.
MatchesEverything bool `json:"matchesEverything"`
PolicyIssues
}
PolicyValidateResponse is policies.validate's reply.
type RecentChecksInput ¶
type RecentChecksInput struct {
Limit int `json:"limit,omitempty"`
}
RecentChecksInput caps the list. Zero or negative means the default of 10.
type RecentChecksResponse ¶
type RecentChecksResponse struct {
Checks []CheckLogSummary `json:"checks"`
}
RecentChecksResponse is the overview.recentChecks reply.
type RelationCreateInput ¶
type RelationCreateInput struct {
NamespacePath string `json:"namespacePath,omitempty"`
ObjectType string `json:"objectType"`
ObjectID string `json:"objectId"`
Relation string `json:"relation"`
SubjectType string `json:"subjectType"`
SubjectID string `json:"subjectId"`
SubjectRelation string `json:"subjectRelation,omitempty"`
}
RelationCreateInput writes one tuple. Every part of the triple is required: a tuple missing any of them matches nothing and is reachable by no filter, so it is silent junk rather than a partial grant.
type RelationDefDTO ¶
type RelationDefDTO struct {
Name string `json:"name"`
AllowedSubjects []string `json:"allowedSubjects"`
}
RelationDefDTO mirrors resourcetype.RelationDef on the wire.
type RelationDeleteInput ¶
type RelationDeleteInput struct {
ID string `json:"id"`
}
RelationDeleteInput names the tuple to remove, by id.
The store also offers DeleteRelationTuple, which takes the whole natural key. By-id is used here because the list hands the page an id, and an id cannot be half-right the way an eight-part key can.
type RelationExpandEdge ¶
type RelationExpandEdge struct {
From string `json:"from"`
To string `json:"to"`
NamespacePath string `json:"namespacePath"`
}
RelationExpandEdge is one tuple, between node keys.
type RelationExpandInput ¶
type RelationExpandInput struct {
ObjectType string `json:"objectType"`
ObjectID string `json:"objectId"`
Relation string `json:"relation"`
NamespacePath string `json:"namespacePath"` // "" is the root
// PathToType and PathToID, when both set, ask for the walk's path to
// that subject.
PathToType string `json:"pathToType,omitempty"`
PathToID string `json:"pathToId,omitempty"`
}
RelationExpandInput names the object and relation to expand.
type RelationExpandNode ¶
type RelationExpandNode struct {
Key string `json:"key"` // "type:id#relation" or "type:id"
Type string `json:"type"`
ID string `json:"id"`
Relation string `json:"relation,omitempty"`
Depth int `json:"depth"`
// Walked is true when every one of the node's tuples is drawn: the walk
// took the node off its queue and went through all of its tuples, and
// none of its edges was left out by the node cap. It is false for a node
// reached but never walked (a frontier node after a stop, the node that
// tripped the fanout limit, every single subject) and for a walked node
// with an edge to a node the cap dropped. Any edges drawn from a node
// with walked false are not all of its tuples.
Walked bool `json:"walked"`
// Capped is true when the node cap removed any of this node's outgoing
// edges, because the node at the other end was left out, and false
// otherwise. It tells the two reasons for walked false apart: a node
// that was never walked has capped false, and a node the walk did go
// through, whose edges the cap then cut, has capped true. Together with
// the node's drawn edges it says how much of the node is shown.
Capped bool `json:"capped"`
}
RelationExpandNode is one node the walk reached.
type RelationExpandResponse ¶
type RelationExpandResponse struct {
Nodes []RelationExpandNode `json:"nodes"`
Edges []RelationExpandEdge `json:"edges"`
Stop string `json:"stop"` // complete | depth | visited | fanout
// Limit is the limit that stopped the walk, as the walk ran with it: the
// engine's own walker's when ExactWalk is true, Config's otherwise. 0
// when the walk completed.
Limit int `json:"limit"`
// ExactWalk is false when the engine's graph walker is not the built-in
// one: Check walks with that walker, so this expansion shows what the
// built-in walk would find, which may differ.
ExactWalk bool `json:"exactWalk"`
// TruncatedNodes is how many nodes were left out past the node cap, 0
// when none. Edges that touch a left-out node are left out too, and a
// kept node that lost an edge that way reports walked false.
TruncatedNodes int `json:"truncatedNodes"`
// Path is the node keys of the walk's path to PathTo, root first, or
// empty when not asked for or not reached.
Path []string `json:"path"`
}
RelationExpandResponse is the expansion.
type RelationSummary ¶
type RelationSummary struct {
ID string `json:"id"`
NamespacePath string `json:"namespacePath"`
ObjectType string `json:"objectType"`
ObjectID string `json:"objectId"`
Relation string `json:"relation"`
SubjectType string `json:"subjectType"`
SubjectID string `json:"subjectId"`
SubjectRelation string `json:"subjectRelation,omitempty"`
CreatedBy string `json:"createdBy,omitempty"`
CreatedAt string `json:"createdAt"`
// Undeclared is set when the resource type governing the tuple's object
// type, from the tuple's own namespace, does not declare it: the reason
// (resourcetype.UndeclaredTupleError.Reason) a write of this tuple would
// be refused now. A tuple stored before the write check existed, by a
// writer that skips it, or before its resource type changed can carry
// one. It is absent when the tuple conforms, when no resource type
// governs its object type, and on every row when the response says
// MarksWithheld.
//
// It judges the tuple by the write check's rule and no other. A check
// reads stored tuples as they are, without consulting the resource
// type's relations or allowed subjects: the direct check and the graph
// walker read no resource type, and the expression evaluator reads only
// its permissions. So a marked tuple still counts.
Undeclared string `json:"undeclared,omitempty"`
}
RelationSummary is one tuple.
type RelationsListInput ¶
type RelationsListInput struct {
PageRequest
NamespacePath *string `json:"namespacePath,omitempty"`
ObjectType string `json:"objectType,omitempty"`
ObjectID string `json:"objectId,omitempty"`
Relation string `json:"relation,omitempty"`
SubjectType string `json:"subjectType,omitempty"`
SubjectID string `json:"subjectId,omitempty"`
SubjectRelation string `json:"subjectRelation,omitempty"`
}
RelationsListInput filters the tuple list. Every field the store's filter offers is here, because tracing a tuple means narrowing on whichever end you happen to know.
type RelationsListResponse ¶
type RelationsListResponse struct {
PageMeta
Items []RelationSummary `json:"items"`
// MarksWithheld is true when the caller may not read resource types
// (read on warden:resourcetype), so no row was checked against its
// resource type and none carries Undeclared. A mark's reason names the
// type's relations or allowed subjects, which resourceTypes.* keeps
// behind that grant, and this list must not be a way around it. An
// unmarked row then says nothing about whether the tuple conforms.
MarksWithheld bool `json:"marksWithheld"`
}
RelationsListResponse is the paged reply.
type ResourceTypeCreateInput ¶
type ResourceTypeCreateInput struct {
Name string `json:"name"`
NamespacePath string `json:"namespacePath,omitempty"`
Description string `json:"description,omitempty"`
Relations []RelationDefDTO `json:"relations,omitempty"`
Permissions []PermissionDefDTO `json:"permissions,omitempty"`
}
ResourceTypeCreateInput creates a resource type.
type ResourceTypeDeleteInput ¶
type ResourceTypeDeleteInput struct {
ID string `json:"id"`
}
ResourceTypeDeleteInput names the resource type to remove.
type ResourceTypeDetail ¶
type ResourceTypeDetail struct {
ResourceTypeSummary
Relations []RelationDefDTO `json:"relations"`
Permissions []PermissionDefDTO `json:"permissions"`
CreatedBy string `json:"createdBy,omitempty"`
UpdatedBy string `json:"updatedBy,omitempty"`
}
ResourceTypeDetail is one type with its full definitions. Both slices are always non-nil so the JSON carries [] rather than null: a page doing data.relations.length on null throws.
type ResourceTypeDetailInput ¶
type ResourceTypeDetailInput struct {
ID string `json:"id"`
}
ResourceTypeDetailInput names one resource type.
type ResourceTypeGraphEdge ¶
type ResourceTypeGraphEdge struct {
From string `json:"from"` // resource type name
// FromID is the owning type's id. Two types can share a name in
// different namespaces, and From alone cannot tell them apart.
FromID string `json:"fromId"`
Relation string `json:"relation"` // the RelationDef's name
To string `json:"to"` // allowed subject type
// ToRelation is the subject set's relation for "type#rel", else "".
ToRelation string `json:"toRelation,omitempty"`
// Declared is false when To names no resource type in the graph (a
// subject kind like "user", or a type outside the namespace filter).
Declared bool `json:"declared"`
// ToIDs is the id of every returned type whose name is To, in node
// order. It is empty (never null) when the edge is undeclared.
ToIDs []string `json:"toIds"`
}
ResourceTypeGraphEdge is one allowed subject of one relation.
type ResourceTypeGraphInput ¶
type ResourceTypeGraphInput struct {
NamespacePath *string `json:"namespacePath,omitempty"`
}
ResourceTypeGraphInput filters the schema graph.
type ResourceTypeGraphNode ¶
type ResourceTypeGraphNode struct {
ID string `json:"id"`
NamespacePath string `json:"namespacePath"`
Name string `json:"name"`
Relations []RelationDefDTO `json:"relations"`
Permissions []PermissionDefDTO `json:"permissions"`
}
ResourceTypeGraphNode is one resource type with its definitions.
type ResourceTypeGraphResponse ¶
type ResourceTypeGraphResponse struct {
Nodes []ResourceTypeGraphNode `json:"nodes"`
Edges []ResourceTypeGraphEdge `json:"edges"`
Truncated bool `json:"truncated"` // more than 500 types
}
ResourceTypeGraphResponse is the schema graph.
type ResourceTypeSummary ¶
type ResourceTypeSummary struct {
ID string `json:"id"`
NamespacePath string `json:"namespacePath"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
RelationCount int `json:"relationCount"`
PermissionCount int `json:"permissionCount"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
ResourceTypeSummary is one row of the resource types list. It carries counts rather than the definitions themselves: a row showing every relation and expression of every type is a wall of text, and the detail page is where the definitions live.
type ResourceTypeUpdateInput ¶
type ResourceTypeUpdateInput struct {
ID string `json:"id"`
Description *string `json:"description,omitempty"`
Relations *[]RelationDefDTO `json:"relations,omitempty"`
Permissions *[]PermissionDefDTO `json:"permissions,omitempty"`
}
ResourceTypeUpdateInput patches a resource type.
Every field but the id is a pointer. nil means "leave it alone"; a pointer to an empty slice means "remove them all". Name and namespace are absent on purpose: tuples name a type by its name, so renaming one strands every tuple that used the old name.
type ResourceTypesListInput ¶
type ResourceTypesListInput struct {
PageRequest
NamespacePath *string `json:"namespacePath,omitempty"`
Search string `json:"search,omitempty"`
}
ResourceTypesListInput filters the resource types list.
type ResourceTypesListResponse ¶
type ResourceTypesListResponse struct {
PageMeta
Items []ResourceTypeSummary `json:"items"`
}
ResourceTypesListResponse is the paged reply.
type RoleCreateInput ¶
type RoleCreateInput struct {
Name string `json:"name"`
Slug string `json:"slug"`
NamespacePath string `json:"namespacePath,omitempty"`
Description string `json:"description,omitempty"`
ParentSlug string `json:"parentSlug,omitempty"`
MaxMembers int `json:"maxMembers,omitempty"`
IsDefault bool `json:"isDefault,omitempty"`
}
RoleCreateInput creates a role. Non-pointer fields because a create has no "leave this alone": every field is either given or defaulted.
type RoleDeleteInput ¶
type RoleDeleteInput struct {
ID string `json:"id"`
}
RoleDeleteInput names the role to remove.
type RoleDetail ¶
type RoleDetail struct {
RoleSummary
Permissions []PermissionSummary `json:"permissions"`
Children []RoleSummary `json:"children"`
CreatedBy string `json:"createdBy,omitempty"`
UpdatedBy string `json:"updatedBy,omitempty"`
}
RoleDetail is one role with everything its page shows.
type RoleDetailInput ¶
type RoleDetailInput struct {
ID string `json:"id"`
}
RoleDetailInput names one role.
type RolePermissionInput ¶
type RolePermissionInput struct {
RoleID string `json:"roleId"`
PermissionName string `json:"permissionName"`
PermissionNamespacePath string `json:"permissionNamespacePath,omitempty"`
}
RolePermissionInput attaches or detaches one grant.
type RoleSetPermissionsInput ¶
type RoleSetPermissionsInput struct {
RoleID string `json:"roleId"`
Permissions []PermissionRef `json:"permissions"`
}
RoleSetPermissionsInput replaces a role's whole grant set.
An empty Permissions slice is a real instruction: it revokes everything. That is why the field is not a pointer; there is no "leave the set alone" case for an intent whose only job is to replace it.
type RoleSummary ¶
type RoleSummary struct {
ID string `json:"id"`
NamespacePath string `json:"namespacePath"`
Name string `json:"name"`
Slug string `json:"slug"`
Description string `json:"description,omitempty"`
ParentSlug string `json:"parentSlug,omitempty"`
IsSystem bool `json:"isSystem"`
IsDefault bool `json:"isDefault"`
MaxMembers int `json:"maxMembers,omitempty"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
RoleSummary is one row of the roles list.
type RoleUpdateInput ¶
type RoleUpdateInput struct {
ID string `json:"id"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
ParentSlug *string `json:"parentSlug,omitempty"`
MaxMembers *int `json:"maxMembers,omitempty"`
IsDefault *bool `json:"isDefault,omitempty"`
}
RoleUpdateInput patches a role.
Every optional field is a pointer, and the distinction is load-bearing: nil means "leave this alone", and a pointer to the zero value means "set it to empty". A non-pointer field cannot express the difference, and the UI would silently erase values the operator never touched.
type RolesListInput ¶
type RolesListInput struct {
PageRequest
NamespacePath *string `json:"namespacePath,omitempty"`
Search string `json:"search,omitempty"`
IsSystem *bool `json:"isSystem,omitempty"`
IsDefault *bool `json:"isDefault,omitempty"`
}
RolesListInput filters the roles list.
NamespacePath is a pointer because nil and the empty string are different queries: nil means every namespace, "" means the tenant root.
type RolesListResponse ¶
type RolesListResponse struct {
PageMeta
Items []RoleSummary `json:"items"`
}
RolesListResponse is the paged reply.
type SchemaApplyInput ¶
type SchemaApplyInput struct {
Source string `json:"source"`
Prune bool `json:"prune"`
// Digest is the plan digest the operator saw.
Digest string `json:"digest"`
}
SchemaApplyInput is the schema.apply request.
type SchemaApplyResponse ¶
type SchemaApplyResponse struct {
Created []string `json:"created"`
Updated []string `json:"updated"`
Deleted []string `json:"deleted"`
NoOps int `json:"noOps"`
// Diverged is true when what was written differs from the plan the
// digest vouched for. The digest is checked before the write and the
// write is a second read of the store, so a change in between can make
// the two differ. The lists are what was actually written.
Diverged bool `json:"diverged"`
}
SchemaApplyResponse is the schema.apply reply: what was written.
type SchemaDiagnostic ¶
type SchemaDiagnostic struct {
Line int `json:"line"` // 1-based
Col int `json:"col"` // 1-based byte column, as dsl.Pos
Message string `json:"message"`
}
SchemaDiagnostic is one problem with submitted source.
type SchemaExportInput ¶
type SchemaExportInput struct {
// NamespacePrefix limits the export to that namespace and below. "" is
// every namespace.
NamespacePrefix string `json:"namespacePrefix,omitempty"`
}
SchemaExportInput is the schema.export request.
type SchemaExportResponse ¶
type SchemaExportResponse struct {
Source string `json:"source"`
}
SchemaExportResponse is the schema.export reply.
type SchemaPlanInput ¶
SchemaPlanInput is the schema.plan request.
type SchemaPlanResponse ¶
type SchemaPlanResponse struct {
// Valid is false when Diagnostics is non-empty; then the lists are empty
// and Digest is "".
Valid bool `json:"valid"`
Diagnostics []SchemaDiagnostic `json:"diagnostics"`
Created []string `json:"created"`
Updated []string `json:"updated"`
Deleted []string `json:"deleted"`
NoOps int `json:"noOps"`
// Digest identifies this exact diff and prune flag; schema.apply
// requires it.
Digest string `json:"digest"`
}
SchemaPlanResponse is the schema.plan reply.
type SubjectAssignment ¶
type SubjectAssignment struct {
ID string `json:"id"`
NamespacePath string `json:"namespacePath"`
RoleID string `json:"roleId"`
RoleSlug string `json:"roleSlug"`
ResourceType string `json:"resourceType,omitempty"`
ResourceID string `json:"resourceId,omitempty"`
ExpiresAt string `json:"expiresAt,omitempty"`
Expired bool `json:"expired"`
// ExpiringSoon is true for a live assignment whose ExpiresAt falls within
// the horizon assignments.expiring uses. That feed also lists lapsed
// rows; this flag does not, because Expired already says so.
ExpiringSoon bool `json:"expiringSoon"`
}
SubjectAssignment is one assignment row of the subject.
type SubjectDetailInput ¶
type SubjectDetailInput struct {
SubjectKind string `json:"subjectKind"`
SubjectID string `json:"subjectId"`
NamespacePath string `json:"namespacePath"` // "" is the root; always explicit
}
SubjectDetailInput names one subject at one namespace.
SubjectKind is not validated: the check log and the stores accept any string, and a subject of a kind no assignment names is a legitimate question with an empty answer.
type SubjectDetailResponse ¶
type SubjectDetailResponse struct {
Roles []SubjectRole `json:"roles"`
Assignments []SubjectAssignment `json:"assignments"`
AssignmentsTruncated bool `json:"assignmentsTruncated"`
Relations []SubjectRelation `json:"relations"`
RelationsTruncated bool `json:"relationsTruncated"`
Policies []SubjectPolicy `json:"policies"`
// Withheld names the sections left empty because the caller lacks their
// read grant. Always an array, empty when nothing is withheld.
Withheld []string `json:"withheld"`
}
SubjectDetailResponse is the subjects.detail reply.
The intent's own grant (read on warden:assignment) covers Assignments. The other sections need their own read grant, checked in the handler: Roles needs warden:role, Relations warden:relation and Policies warden:policy. A section the caller may not read is an empty array, its truncated flag is false, and its name ("roles", "relations" or "policies") is in Withheld. The check log is not here: it needs read_audit, and the page reads it through checkLogs.list.
type SubjectPermission ¶
type SubjectPermission struct {
Name string `json:"name"` // resource:action as stored, wildcards included
Resource string `json:"resource"`
Action string `json:"action"`
}
SubjectPermission is one grant of a role.
type SubjectPolicy ¶
type SubjectPolicy struct {
ID string `json:"id"`
Name string `json:"name"`
Effect string `json:"effect"`
Priority int `json:"priority"`
NamespacePath string `json:"namespacePath"`
// SelectedBy says how the policy's subject matchers select this subject:
// "everyone" (no subject matchers, or an empty matcher), "kind", "id" or
// "role:<slug>" (the first agreeing matcher's most specific field).
SelectedBy string `json:"selectedBy"`
}
SubjectPolicy is one policy in effect that selects the subject.
type SubjectRelation ¶
type SubjectRelation struct {
ID string `json:"id"`
NamespacePath string `json:"namespacePath"`
ObjectType string `json:"objectType"`
ObjectID string `json:"objectId"`
Relation string `json:"relation"`
// SubjectRelation is the tuple's subject relation, set when the tuple
// grants a userset (group:eng#member) rather than the subject itself.
SubjectRelation string `json:"subjectRelation,omitempty"`
}
SubjectRelation is one tuple naming the subject.
type SubjectRole ¶
type SubjectRole struct {
ID string `json:"id"`
Slug string `json:"slug"`
Name string `json:"name"`
NamespacePath string `json:"namespacePath"`
// Via is "assigned" or "inherited".
Via string `json:"via"`
// InheritedBy lists the slugs of the resolved roles whose ParentSlug
// names this role in its namespace. Empty for an assigned role that no
// other resolved role inherits.
InheritedBy []string `json:"inheritedBy"`
Permissions []SubjectPermission `json:"permissions"`
}
SubjectRole is one resolved role.
Source Files
¶
- authz.go
- contract.go
- errors.go
- handlers_assignments.go
- handlers_checklogs.go
- handlers_config.go
- handlers_graphs.go
- handlers_maintenance.go
- handlers_namespaces.go
- handlers_overview.go
- handlers_permissions.go
- handlers_playground.go
- handlers_policies.go
- handlers_relations.go
- handlers_resourcetypes.go
- handlers_roles.go
- handlers_schema.go
- handlers_subjects.go
- immutable.go
- members.go
- mutate.go
- paging.go
- policy_analysis.go
- policy_validate.go