startgrant

package
v0.22.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 3, 2026 License: AGPL-3.0 Imports: 13 Imported by: 0

Documentation

Overview

Package startgrant evaluates the independent, least-privilege mayStart audience of ADR-0516. It is not a route guard: callers must also validate human authentication, publication, version and start form on the server.

Index

Constants

View Source
const (
	BlockApplicationMissing   = "application-missing"
	BlockApplicationProtected = "application-protected"
	BlockNotDeployed          = "not-deployed"
	BlockOtherApplication     = "newest-version-in-other-application"
	BlockNotExecutable        = "not-executable"
	BlockInactive             = "inactive"
	BlockTriggerOnly          = "trigger-only"
	BlockConstantsMissing     = "constants-missing"
)

Blocker codes say why a granted process would still not be offered for a start. They are stable strings so the Console and tests can read them.

View Source
const (
	ActionGrant  = "grant"
	ActionRevoke = "revoke"
)

ActionGrant and ActionRevoke describe immutable audited audience changes.

View Source
const SchemaVersion = 1

SchemaVersion identifies the durable grant-document format.

Variables

View Source
var ErrStaleRevision = errors.New("start grant revision changed")

ErrStaleRevision means another admin change won before this request.

View Source
var ErrUncertainWrite = errors.New("start grant store requires recovery after write error")

ErrUncertainWrite requires restart/recovery before any positive authorization.

Functions

func Allowed

func Allowed(grants []Grant, applicationID, processID string, p Principal) bool

Allowed evaluates an explicit grant for exactly one application/process pair. The caller supplies current group membership and a current durable grant snapshot; no cached positive decision may survive a persisted revocation. It deliberately does not infer permission from viewer/editor or operator.

Types

type Account

type Account struct {
	// Disabled accounts cannot sign in, so a grant to one starts nothing.
	Disabled bool
	// SelfService reports whether the account holds a role the self-service start
	// routes are declared for (user, or admin, which reaches every route). A grant
	// is a second key beside that role, never a replacement for it.
	SelfService bool
}

Account is what the directory says about a person a grant or a preview names.

type Change

type Change struct {
	SchemaVersion int    `json:"schemaVersion"`
	Revision      uint64 `json:"revision"`
	At            int64  `json:"at"`
	ActorID       string `json:"actorId"`
	Action        string `json:"action"`
	Grant         Grant  `json:"grant"`
}

Change is one immutable audit entry in a process-scoped grant document. Revision increases per application/process, not globally.

type ChangeRequest

type ChangeRequest struct {
	Grant            Grant
	ActorID          string
	At               int64
	ExpectedRevision uint64
}

ChangeRequest is prepared by a future admin-only HTTP service. The actor and timestamp must come from the authenticated server context, not the client.

type ChangeView

type ChangeView struct {
	Revision uint64   `json:"revision"`
	At       int64    `json:"at"`
	ActorID  string   `json:"actorId"`
	Action   string   `json:"action"`
	UserIDs  []string `json:"userIds"`
	GroupIDs []string `json:"groupIds"`
}

ChangeView is one audited change as the history route answers it.

type Directory

type Directory struct {
	User     func(id string) (Account, bool, error)
	Group    func(id string) (bool, error)
	GroupsOf func(userID string) ([]string, error)
}

Directory resolves the identities a grant names. Every function is called on the run loop only. Identities are looked up by id, never by a display or user name: a name can be changed or reused, an id cannot.

type Grant

type Grant struct {
	ApplicationID string   `json:"applicationId"`
	ProcessID     string   `json:"processId"`
	UserIDs       []string `json:"userIds,omitempty"`
	GroupIDs      []string `json:"groupIds,omitempty"`
}

Grant is a process-scoped audience configured independently of app sharing. Persistence and admin audit belong to the design-time sidecar, not the WAL.

type GrantView

type GrantView struct {
	ApplicationID string `json:"applicationId"`
	ProcessID     string `json:"processId"`
	// Revision is the per-scope revision a change must name as expectedRevision;
	// zero when nothing was ever granted for this scope.
	Revision  uint64   `json:"revision"`
	Granted   bool     `json:"granted"`
	UserIDs   []string `json:"userIds"`
	GroupIDs  []string `json:"groupIds"`
	ChangedAt int64    `json:"changedAt,omitempty"`
	ChangedBy string   `json:"changedBy,omitempty"`
}

GrantView is one scope's current state as the administration routes answer it.

type MutationView

type MutationView struct {
	GrantView
	Changed bool `json:"changed"`
}

MutationView is a write's answer: the scope as it now stands, and whether this request changed it. Changed is false for a revoke of what is already revoked and for a grant of exactly the audience already granted — neither writes an audit entry, so a retried request does not pile up history.

type PreviewView

type PreviewView struct {
	ApplicationID string   `json:"applicationId"`
	ProcessID     string   `json:"processId"`
	SubjectKind   string   `json:"subjectKind"`
	SubjectID     string   `json:"subjectId"`
	Revision      uint64   `json:"revision"`
	Granted       bool     `json:"granted"`
	MatchedBy     []string `json:"matchedBy"`
	AccountIssues []string `json:"accountIssues"`
	Startable     bool     `json:"startable"`
	Blockers      []string `json:"blockers"`
	DefinitionKey uint64   `json:"definitionKey,omitempty"`
	Version       int32    `json:"version,omitempty"`
	// WouldBeOffered is the conjunction the self-service list applies: granted, an
	// account that can sign in and holds the role, and a process that is startable.
	// A machine credential never qualifies, whatever it carries (ADR-0516).
	WouldBeOffered bool `json:"wouldBeOffered"`
}

PreviewView answers whether one person or group would be offered the process.

type Principal

type Principal struct {
	UserID   string
	GroupIDs []string
}

Principal is the authenticated user's current identity and group membership. A zero UserID cannot authorize a start even when groups match.

type Service

type Service struct {

	// Limits are the installation's resource budgets. NewService sets them to
	// [limits.Default]; the server overwrites them with its own once it has read the
	// environment (ADR-0291). A grant body is one ordinary JSON request.
	Limits limits.Limits
	// contains filtered or unexported fields
}

Service serves the start-grant administration routes. Build it with NewService.

func NewService

func NewService(loop *runloop.Loop, store *Store, dir Directory,
	resolve func(applicationID, processID string) (Target, error), authEnabled func() bool, now func() int64) *Service

NewService builds the administration service over the store. resolve and every function of dir read state the loop owns and are only called from inside its turn. authEnabled reports whether the server enforces authentication; it is read per request because the server settles it after constructing its services. now stamps an audit entry, in Unix seconds; the actor and the time are the server's, never the client's.

func (*Service) HandleDelete

func (s *Service) HandleDelete(w http.ResponseWriter, r *http.Request)

HandleDelete revokes one scope's grant. A revocation takes effect for every authorization check after it is durable; a start authorized before it may still finish (ADR-0516's prospective ordering). Revoking needs no application or process to exist any more — taking a right away must always be possible — and revoking what is already revoked writes nothing.

func (*Service) HandleGet

func (s *Service) HandleGet(w http.ResponseWriter, r *http.Request)

HandleGet answers one scope's current grant. A scope never granted answers revision 0 and an empty audience: deny is the default, not a missing record.

func (*Service) HandleHistory

func (s *Service) HandleHistory(w http.ResponseWriter, r *http.Request)

HandleHistory answers every audited change of one scope, newest first.

func (*Service) HandleList

func (s *Service) HandleList(w http.ResponseWriter, r *http.Request)

HandleList answers every scope of one application that was ever granted, revoked ones included, ordered by process id.

func (*Service) HandlePreview

func (s *Service) HandlePreview(w http.ResponseWriter, r *http.Request)

HandlePreview answers whether one account (?userId=) or one group (?groupId=) would be offered the process now, and why not. It reads the account's group membership from the directory — the same source a session's groups are kept current from (ADR-0185) — and writes nothing.

func (*Service) HandlePut

func (s *Service) HandlePut(w http.ResponseWriter, r *http.Request)

HandlePut issues or wholly replaces one scope's grant. The application must exist and not be a protected system application, the newest deployed version of the process must be filed under it, and every user and group id must name an account or group that exists now. All of it is checked in the same loop turn as the write, which is durable — written and fsynced — before the answer says so.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store owns durable process-start grants, with single-writer run-loop access. Never call its mutable methods concurrently or from the processor hot path.

func NewStore

func NewStore(dir string) (*Store, error)

NewStore loads all scoped grant documents and validates their audit chains. A corrupt record fails startup instead of granting access from partial data.

func (*Store) Allowed

func (s *Store) Allowed(applicationID, processID string, principal Principal) bool

Allowed checks only the exact last durable audience, never project viewer, editor, task assignment or operator privileges. Call it on the run-loop.

func (*Store) Current

func (s *Store) Current(applicationID, processID string) (Change, bool)

Current returns a defensive copy of the latest audited mutation. It is unavailable after an uncertain write until restart/recovery.

func (*Store) ForApplication

func (s *Store) ForApplication(applicationID string) []Change

ForApplication returns the latest audited change of every process scope filed under one application, revoked scopes included, ordered by process id. It is the administrator's overview; it grants nothing and is unavailable while the store is uncertain, for the reason Store.Current is.

func (*Store) GrantedTo

func (s *Store) GrantedTo(p Principal) []Grant

GrantedTo returns every scope whose latest durable change grants the principal, directly or through one of the groups it carries now, ordered by application and process id. It is the self-service list's starting point and nothing more: the caller must still check the scope's application and process before offering it. Like every positive answer, it is empty while the store is uncertain.

func (*Store) History

func (s *Store) History(applicationID, processID string) []Change

History returns defensive copies of all audited changes, oldest first.

func (*Store) Set

func (s *Store) Set(req ChangeRequest) (Change, error)

Set atomically replaces the effective policy and its complete audit trail. Callers MUST first enforce admin authorization and validate the target application, BPMN process and audience against current server-side records. The store is not itself a route guard. A failed write poisons authorization until recovery because a failed fsync can have an unknown outcome.

func (*Store) Uncertain

func (s *Store) Uncertain() bool

Uncertain reports whether a failed write left the store's durable state unknown. While it does, every positive authorization and every further write is refused until a restart re-reads what actually reached the disk.

type Target

type Target struct {
	ApplicationFound     bool
	ApplicationProtected bool
	// ProcessInApplication reports that the newest deployed version of the process
	// id is filed under this application. A grant is issued only then: a process id
	// another application deployed last is not this application's to hand out.
	ProcessInApplication bool
	// DefinitionKey and Version name that newest deployed version, zero when the
	// process id is not deployed.
	DefinitionKey uint64
	Version       int32
	// Blockers say why the newest version would not be offered for a self-service
	// start; empty means it would be.
	Blockers []string
}

Target is what the server knows, now, about one application/process pair.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL