access

package
v1.131.2 Latest Latest
Warning

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

Go to latest
Published: Sep 12, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package access holds the portal's authorization core: one checker that answers every "may this user see or change this thing" question the portal asks, over assets, collections, prompts, knowledge pages and threads.

Before this package the answers were methods scattered across the handler files, reachable only from *Handler. Splitting the handler layer into seams made that untenable: a seam would have had to receive each check as an injected func, which is not a seam but a second copy of the handler's dependency graph. The checks are also the portal's security boundary, so a single implementation both the parent and the seams call is what keeps a permission from quietly meaning two things in two places.

Every method is pure with respect to HTTP: it answers the question and never writes a response. Callers that must deny with a specific status keep their own thin wrapper, so the status and message wording stay with the route.

Index

Constants

View Source
const ApplyKnowledgeTool = "apply_knowledge"

ApplyKnowledgeTool is the persona tool whose access gates insight review and canonical-knowledge writes. It is the single capability the REST path checks, matching the MCP path's persona tool-visibility gate so both agree on who may promote knowledge.

Variables

This section is empty.

Functions

func AssetOwnerOf added in v1.127.0

func AssetOwnerOf(user *User) portaldomain.AssetOwner

AssetOwnerOf is the ownership identity a portal user is judged by: the id they authenticated with and their address.

The address is a second key, not a fallback. A managed script's output is stamped with the script principal as its owner id and the script owner's address as owner_email, so a check on the id alone leaves the person the output was produced for unable to see it (#1551). It is a no-op on an asset a person saved themselves, whose two identifiers name the same person.

func ContextWithUser

func ContextWithUser(ctx context.Context, user *User) context.Context

ContextWithUser returns a copy of ctx carrying the authenticated user, the value GetUser reads. The key is unexported and lives here with the subject of every permission check, so the parent package and every handler seam read the identity the portal authenticator wrote rather than each keying its own copy.

func GrantsEdit added in v1.121.0

func GrantsEdit(perm portaldomain.SharePermission) bool

GrantsEdit reports whether a resolved share permission carries edit rights. It is the one place the Editor level is compared, so a caller that already holds a resolved permission — getCollection reports one alongside the record it is answering with — applies the same rule CanEditCollection applies, without repeating the comparison or paying for a second lookup.

func HasAnyRole

func HasAnyRole(userRoles, targetRoles []string) bool

HasAnyRole returns true if any role in userRoles is also in targetRoles. It is exported for the few callers that hold a role list rather than a Checker (the prompt mutation gate takes its admin roles as an argument).

func IsShareActive

func IsShareActive(s portaldomain.Share) bool

IsShareActive returns true if the share is not revoked and not expired.

func OwnsAsset added in v1.127.0

func OwnsAsset(asset *portaldomain.Asset, user *User) bool

OwnsAsset reports whether the asset belongs to the user, on either of the two identifiers it records. It is the one judgment of "this asset is this person's" the portal makes, so an owner-only affordance and an owner-scoped listing cannot come to different answers about the same row.

Types

type Checker

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

Checker answers the portal's authorization questions.

func New

func New(cfg Config) *Checker

New returns a Checker over cfg.

func (*Checker) AssetSharePermission

func (c *Checker) AssetSharePermission(ctx context.Context, assetID string, user *User) (portaldomain.SharePermission, error)

AssetSharePermission returns the highest permission level a user has for a shared asset. It returns the empty permission if the asset is not shared with this user.

func (*Checker) AssetViewGrant

func (c *Checker) AssetViewGrant(ctx context.Context, assetID string, asset *portaldomain.Asset, user *User) (bool, error)

AssetViewGrant reports whether the user may view the asset, distinguishing a denial from a failure to determine one. A non-nil error means the direct-share lookup failed and no collection grant covered for it, which callers surface as 500 rather than 403.

func (*Checker) CanEditAssetSilent

func (c *Checker) CanEditAssetSilent(ctx context.Context, assetID string, user *User) bool

CanEditAssetSilent reports owner-or-admin-or-editor access to an asset. A missing or soft-deleted asset denies.

func (*Checker) CanEditCollection added in v1.121.0

func (c *Checker) CanEditCollection(ctx context.Context, coll *portaldomain.Collection, user *User) bool

CanEditCollection reports whether the user may change the collection itself: its name, description, settings, sections and thumbnail. It grants to the owner, an admin, and the holder of an Editor share on the collection.

The Editor arm is what makes an Editor share on a collection mean anything about the collection rather than only about the assets inside it (#1294): a person trusted to rewrite the contents of every asset in a collection is not a plausible person to refuse a fix to the collection's own title. Deleting a collection, sharing it, and reading its share list stay on CanManage — destruction and re-granting access are owner rights, not editing rights, so an Editor deliberately holds neither.

func (*Checker) CanEditCollectionSilent

func (c *Checker) CanEditCollectionSilent(ctx context.Context, collectionID string, user *User) bool

CanEditCollectionSilent is CanEditCollection for callers that hold only an ID. A missing or soft-deleted collection denies.

func (*Checker) CanManage added in v1.121.0

func (c *Checker) CanManage(ownerID string, user *User) bool

CanManage reports whether the user holds owner authority over a thing owned by ownerID: the owner, or an admin.

It is the single seam behind every "only the owner can ..." gate the portal applies to assets and collections, so admin reach is decided once instead of being re-derived at each route. Admin only ever widens here, and never grants more than the admin already has: an admin reads, edits and deletes any asset through the admin API, so refusing them the weaker rights to share it, list its shares, or revoke one was an artifact of the gate being written as a bare ID comparison rather than a decision (#1293).

Assets use CanManageAsset instead: their ownership is recorded as an id and an address, and only the pair identifies the person a managed script's output was produced for (#1551).

func (*Checker) CanManageAsset added in v1.127.0

func (c *Checker) CanManageAsset(asset *portaldomain.Asset, user *User) bool

CanManageAsset reports whether the user holds owner authority over the asset: its owner, or an admin. It is CanManage for assets, which record ownership as an id and an address rather than as an id alone.

func (*Checker) CanManageEmail added in v1.121.0

func (c *Checker) CanManageEmail(ownerEmail string, user *User) bool

CanManageEmail is CanManage for the entities whose ownership is recorded as an email address rather than a user ID (prompts). The address comparison is case-insensitive, matching OwnsPersonalPrompt and the share-recipient match, because addresses reach the platform from several identity providers.

func (*Checker) CanModerateThread

func (c *Checker) CanModerateThread(ctx context.Context, user *User, thread *threads.Thread) bool

CanModerateThread reports whether the user may change a thread's status or delete it: the thread author, an admin, or an owner/editor of the target. Standalone threads are moderated only by their author or an admin.

func (*Checker) CanViewAsset

func (c *Checker) CanViewAsset(ctx context.Context, assetID string, asset *portaldomain.Asset, user *User) bool

CanViewAsset reports whether the user may view the asset (owner, a direct share, or a collection share). A direct-share store error is tolerated: a collection grant still allows access. It short-circuits on a direct grant to avoid a collection query on the hot path, where callers resolve many assets in a loop.

func (*Checker) CanViewCollection

func (c *Checker) CanViewCollection(ctx context.Context, coll *portaldomain.Collection, user *User) bool

CanViewCollection reports whether the user may view the collection (owner or any share).

func (*Checker) CanViewPrompt

func (c *Checker) CanViewPrompt(ctx context.Context, user *User, pr *prompt.Prompt) bool

CanViewPrompt reports whether the user can see the prompt: global prompts are visible to all; personal prompts to their owner, admins, or share grantees.

func (*Checker) CollectionSharePermission

func (c *Checker) CollectionSharePermission(ctx context.Context, collectionID string, user *User) portaldomain.SharePermission

CollectionSharePermission returns the highest share permission for a user on a collection. A store error, or no share store at all, yields the empty permission: a deployment without shares grants none rather than panicking on the write gates that now consult it.

func (*Checker) HasApplyKnowledge

func (c *Checker) HasApplyKnowledge(user *User) bool

HasApplyKnowledge reports whether the user holds the apply_knowledge capability. It grants when the user's resolved persona lists the tool (the same Tools the frontend reads from GET /me and the MCP path gates on, so a non-admin persona granted apply_knowledge can review and promote), OR when the user is an admin.

Admins are always treated as holding the capability for two reasons: their persona normally grants every registered tool, and the tool may not be registered at all on a given deployment (apply_knowledge is absent when Knowledge.Apply.Enabled is false, its default), in which case the resolved Tools list can never contain it. Without the admin arm, enabling capability gating would lock admins out of knowledge writes wherever apply is disabled, a regression from the prior admin-role gate. The admin arm only widens access; the capability still grants non-admins, which is the behavior #661 requires.

func (*Checker) HasTool

func (c *Checker) HasTool(user *User, tool string) bool

HasTool reports whether the user's resolved persona grants the named tool, or the user is an admin. It is the shared capability check behind the apply_knowledge and DataHub write authorizations; the admin arm only widens access (a separate write-enabled-connection check still applies to DataHub writes, so admin cannot mutate a read-only connection).

func (*Checker) IsAdmin

func (c *Checker) IsAdmin(user *User) bool

IsAdmin reports whether the user holds one of the portal's admin roles.

func (*Checker) OwnedAssetIDs

func (c *Checker) OwnedAssetIDs(ctx context.Context, ids []string, user *User) []string

OwnedAssetIDs filters ids down to the live assets the user owns.

func (*Checker) OwnedCollectionIDs

func (c *Checker) OwnedCollectionIDs(ctx context.Context, ids []string, user *User) []string

OwnedCollectionIDs filters ids down to the live collections the user owns.

func (*Checker) OwnedTargetIDs

func (c *Checker) OwnedTargetIDs(ctx context.Context, targetType string, ids []string, user *User) []string

OwnedTargetIDs filters ids down to the targets of the given type the user owns. An unsupported target type owns nothing.

func (*Checker) OwnsPersonalPrompt

func (c *Checker) OwnsPersonalPrompt(ctx context.Context, promptID string, user *User) bool

OwnsPersonalPrompt reports whether the user owns the given personal prompt.

func (*Checker) ResolveAssetPermission

func (c *Checker) ResolveAssetPermission(ctx context.Context, assetID string, user *User) (portaldomain.SharePermission, error)

ResolveAssetPermission returns the highest permission a non-owner user holds for an asset, combining a direct share (AssetSharePermission) with a collection share (GetUserAssetPermissionViaCollection). A direct editor short-circuits the collection lookup because editor is the ceiling. The returned error is the direct-share store error (nil on success); a collection-lookup error is treated as no collection access, matching the best-effort cascade the view checks use. Owner access must be checked by the caller, which treats a non-nil error as 500.

This is for callers that need the effective permission value (getAsset reports it as SharePermission; the edit check compares it to editor). The bool view checks (CanViewAsset) deliberately do not use it: they only need "any grant" and short-circuit on a direct share to avoid a collection query per asset.

type Config

type Config struct {
	Assets      portaldomain.AssetStore
	Collections portaldomain.CollectionStore
	Shares      portaldomain.ShareStore
	Prompts     prompt.Store
	// AdminRoles are the roles that grant admin access in the portal.
	AdminRoles []string
	// PersonaTools resolves a user's roles to the tool names their persona
	// grants. nil means no persona resolution is wired, in which case only
	// the admin arm of the capability checks can grant.
	PersonaTools func(roles []string) []string
}

Config carries the stores and identity settings the checks read. Every field is optional in the sense that a nil store disables the checks that need it (they deny rather than panic), which is what a portal deployed without collections or prompts requires.

type User

type User struct {
	UserID string
	Email  string
	Roles  []string
	// FromCookie is true for browser-session (cookie) auth; only such requests
	// are CSRF-enforced (API-key / Bearer auth is exempt).
	FromCookie bool
	// AuthType is HOW this caller was authenticated, as the authenticator that
	// admitted them reported it. It is carried rather than re-derived because a
	// surface that opens a session ON THIS CALLER'S BEHALF — the portal's
	// script dry run (#1364) — must present the authentication the request
	// actually arrived with, and FromCookie alone cannot tell an API key from a
	// bearer token.
	AuthType string
}

User holds information about the authenticated portal user.

func GetUser

func GetUser(ctx context.Context) *User

GetUser returns the User from context, or nil if not set.

Jump to

Keyboard shortcuts

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