Documentation
¶
Overview ¶
Package persona provides persona-based access control and customization.
Index ¶
- Constants
- Variables
- func AtLeast(level, want string) bool
- func Differs(stored, effective PortalAccess) []string
- func On(v *bool) bool
- func ValidateAPIRoutes(rules []APIRouteRule) error
- func ValidateAccount(p *Persona, adminPersona string) error
- type APIRouteRule
- type AccessDecision
- type AccessSource
- type Authorizer
- type ChainedRoleMapper
- type CoherenceFinding
- type Config
- type ConnectionRules
- type ConnectionRulesConfig
- type ContextOverrides
- type ContextOverridesYAML
- type OIDCRoleMapper
- type Persona
- type PortalAccess
- type PortalUploads
- type Registry
- func (r *Registry) AdminRule(adminPersona string) func(roles []string) bool
- func (r *Registry) All() []*Persona
- func (r *Registry) Get(name string) (*Persona, bool)
- func (r *Registry) GetForRoles(roles []string) (*Persona, bool)
- func (r *Registry) IsAdminName(name, adminPersona string) bool
- func (r *Registry) IsServiceAccount(name string) bool
- func (r *Registry) LoadFromConfig(config map[string]*Config) error
- func (r *Registry) Names() []string
- func (r *Registry) Register(p *Persona) error
- func (r *Registry) ServiceAccountNames() []string
- func (r *Registry) SetRoleMapping(mapping map[string]string)
- func (r *Registry) SharingAllowed(name string) bool
- func (r *Registry) Unregister(name string) error
- type RoleMapper
- type RouteDecision
- type RouteQuery
- type StaticRoleMapper
- type ToolFilter
- func (f *ToolFilter) FilterTools(persona *Persona, tools []string) []string
- func (f *ToolFilter) IsAPIRouteAllowed(persona *Persona, q RouteQuery) bool
- func (*ToolFilter) IsAllowed(persona *Persona, toolName string) bool
- func (f *ToolFilter) IsConnectionAllowed(persona *Persona, connectionName string) bool
- func (*ToolFilter) WhyAPIRouteAllowed(persona *Persona, q RouteQuery) RouteDecision
- func (*ToolFilter) WhyAllowed(persona *Persona, toolName string) AccessDecision
- func (*ToolFilter) WhyConnectionAllowed(persona *Persona, connectionName string) AccessDecision
- type ToolRules
- type ToolRulesConfig
Constants ¶
const ( ActionAllow = "allow" ActionDeny = "deny" )
API route action values. Empty Action defaults to ActionAllow.
const ( // PortalNone takes an area away: its navigation entry and every route. PortalNone = "none" // collections the items shared with them, for resources their own folder, // their persona's folder and Global. Nothing is created. PortalShared = "shared" // PortalOwn creates and owns items: assets and collections, or resources // uploaded to the person's own folder, which administrators see. PortalOwn = "own" // PortalPersonaFolders is resources as before #2225: the person's own // folder, their persona's folder and Global. PortalPersonaFolders = "persona" // PortalRead reads an area: knowledge pages, the catalog. PortalRead = "read" // PortalWrite reads and writes an area. PortalWrite = "write" // PortalBrowse opens the portal's API browser, still filtered by the // persona's connections and API routes. PortalBrowse = "browse" // PortalList lists the scratch tables registered on the connections the // persona reaches (#2257). PortalList = "list" // PortalRegister lists them and registers a table over a file. PortalRegister = "register" )
Portal area levels (#2225). A persona governs what an agent may call through Tools, Connections and APIRoutes; its Portal block governs what its people may do in the portal, one setting per area, enforced on every route of the area and reported on /api/v1/portal/me so the navigation shows only what the persona has.
Variables ¶
var ( // ErrExternalAdmin refuses marking the administrator persona external: // the admin API would otherwise admit people the platform created // accounts for. ErrExternalAdmin = errors.New("the administrator persona cannot be external") // ErrAccountType refuses a persona that is both external and an // automated caller: the account type is one of internal people, // external people or an automated caller. ErrAccountType = errors.New("a persona is for internal people, external people or an automated caller, not more than one") // ErrUploadsInternal refuses an uploads setting on an internal persona, // whose people upload by their Resources level. ErrUploadsInternal = errors.New("portal.uploads applies only to an external persona") )
Account-type refusals ValidateAccount returns (#2257).
Functions ¶
func AtLeast ¶ added in v1.149.0
AtLeast reports whether level reaches want in the order an area's levels are listed. Both must be levels of the same area.
func Differs ¶ added in v1.150.0
func Differs(stored, effective PortalAccess) []string
Differs lists the areas whose resolved level differs between two blocks, in the order the editor shows them: what an external persona's limits took from the levels it stored (#2257).
func ValidateAPIRoutes ¶ added in v1.126.0
func ValidateAPIRoutes(rules []APIRouteRule) error
ValidateAPIRoutes catches misconfigurations in APIRoutes so a typo can't silently disable a deny rule (matchPattern returns false on filepath.ErrBadPattern, which is the safe default for allow rules but lets a malformed deny rule fail open). Also rejects an empty Connection — required per the APIRouteRule docstring; without this check matchingRouteRules would silently skip the rule and the operator would see no error.
Register calls it, and so does the admin API before it persists a persona: that write reaches the database first and the registry second, so a rule Register would refuse has to be caught before it is stored (#1479).
func ValidateAccount ¶ added in v1.150.0
ValidateAccount refuses an account type the platform cannot honor: an external administrator persona, a persona that is both external and an automated caller, and uploads on an internal persona (#2257, #2258). adminPersona is the configured administrator persona's name.
Types ¶
type APIRouteRule ¶ added in v1.59.0
type APIRouteRule struct {
// Connection is a glob (e.g. "crm-*") matched against the connection
// name. Required.
Connection string `json:"connection" yaml:"connection"`
// Methods is a list of HTTP method globs (e.g. ["GET", "HEAD"]).
// Empty = any method. Patterns are case-sensitive — typically
// uppercase ("GET", "POST", etc.) since the toolkit normalizes
// inbound methods to uppercase before this check runs.
Methods []string `json:"methods,omitempty" yaml:"methods,omitempty"`
// Paths is a list of path globs. Each is matched against both the
// path a call reaches ("/v1/orders/42") and the catalog template it
// resolved from ("/v1/orders/{id}"), so a rule may name either form:
// the templated path an operation is listed under governs the calls
// that operation serves, and a glob written by hand still governs a
// path no catalog declares. Empty = any path.
Paths []string `json:"paths,omitempty" yaml:"paths,omitempty"`
// Action is "allow" (default) or "deny". Deny rules take precedence
// over allow rules within the matching-Connection subset.
Action string `json:"action,omitempty" yaml:"action,omitempty"`
}
APIRouteRule constrains the HTTP API gateway's api_invoke_endpoint tool to specific (method, path) combinations on connections matched by Connection. A persona's APIRoutes list is consulted only for kind=api connections; other toolkit kinds ignore it.
Semantics, evaluated against a single (connection, method, path) tuple:
- Rules whose Connection glob does not match are skipped.
- Among matching rules: any "deny" rule whose Methods+Paths match denies the call (deny takes precedence).
- Otherwise, at least one "allow" rule whose Methods+Paths match must be present for the call to be authorized.
- If NO rule matches the connection at all, the API route check is a no-op — the existing ConnectionRules check is the sole gate (backward-compatible behavior).
Empty Methods or Paths slices mean "any" — a rule with empty Methods and Paths matches every method+path combination on the connection.
type AccessDecision ¶ added in v1.57.0
type AccessDecision struct {
Allowed bool `json:"allowed"`
MatchedPattern string `json:"matched_pattern"`
Source AccessSource `json:"source"`
}
AccessDecision is the per-tool decision a persona produces for a specific tool name, with the matching pattern and source recorded so the admin UI can render "why".
type AccessSource ¶ added in v1.57.0
type AccessSource string
AccessSource indicates which clause of a persona's tool rules produced an allow/deny decision. Used by the admin Tools-detail endpoint so operators can see WHY a persona allows or denies a tool.
const ( // AccessSourceAllow — an explicit allow pattern matched. AccessSourceAllow AccessSource = "allow" // AccessSourceDeny — an explicit deny pattern matched (takes precedence over allow). AccessSourceDeny AccessSource = "deny" // AccessSourceDefault — no allow pattern matched; falls through to fail-closed default. AccessSourceDefault AccessSource = "default" )
type Authorizer ¶ added in v0.14.0
type Authorizer struct {
// contains filtered or unexported fields
}
Authorizer implements middleware.Authorizer using personas.
func NewAuthorizer ¶ added in v0.14.0
func NewAuthorizer(registry *Registry, mapper RoleMapper) *Authorizer
NewAuthorizer creates a new persona-based authorizer.
func (*Authorizer) IsAPIRouteAllowed ¶ added in v1.59.0
func (a *Authorizer) IsAPIRouteAllowed(ctx context.Context, roles []string, q RouteQuery) (allowed bool, personaName, reason string)
IsAPIRouteAllowed authorizes the query's method and path on its HTTP API gateway connection for the user with the given roles. Layered on top of IsAuthorized: the caller (the api gateway toolkit) is expected to have already passed the tool/connection-level gate via the standard MCP middleware, and this method adds per-route granularity. Returns the resolved persona name (for audit) and a reason on denial.
func (*Authorizer) IsAuthorized ¶ added in v0.14.0
func (a *Authorizer) IsAuthorized(ctx context.Context, _ string, roles []string, toolName, connectionName string) (allowed bool, personaName, reason string)
IsAuthorized checks if the user is authorized for the tool on the given connection. Both the tool and the connection must be allowed by the persona's rules. Returns the resolved persona name for audit logging.
type ChainedRoleMapper ¶
type ChainedRoleMapper struct {
Mappers []RoleMapper
}
ChainedRoleMapper tries multiple mappers in order.
func (*ChainedRoleMapper) MapToPersona ¶
MapToPersona uses the first mapper that returns a persona.
func (*ChainedRoleMapper) MapToRoles ¶
func (c *ChainedRoleMapper) MapToRoles(claims map[string]any) ([]string, error)
MapToRoles aggregates roles from all mappers.
type CoherenceFinding ¶ added in v1.119.0
type CoherenceFinding struct {
// Persona is the name of the persona the finding is about.
Persona string `json:"persona"`
// Granted is the tool the persona holds that arms the rule.
Granted string `json:"granted"`
// Missing is the tool the persona lacks.
Missing string `json:"missing"`
// Why states the capability lost by the pairing being broken.
Why string `json:"why"`
// Remedy is the operator-facing fix.
Remedy string `json:"remedy"`
}
CoherenceFinding reports one persona holding an incoherent tool set.
It is a diagnostic, never a gate: an operator may have a real reason for a restricted persona, so callers log findings and continue.
func CheckCoherence ¶ added in v1.119.0
func CheckCoherence(p *Persona, registered []string) []CoherenceFinding
CheckCoherence evaluates the rule table for one persona against the set of tools actually registered on this deployment.
registered scopes the check to what exists: a deployment that never registered fetch (no database, no search toolkit) is not misconfigured for withholding it, and warning about it would train operators to ignore the warning. A rule fires only when both of its tools are registered, the persona allows Grant, and the persona does not allow Requires.
A nil persona yields no findings — it reaches nothing, so no pairing of capabilities is broken.
func CheckRegistryCoherence ¶ added in v1.119.0
func CheckRegistryCoherence(reg *Registry, registered []string) []CoherenceFinding
CheckRegistryCoherence evaluates every registered persona, returning the findings sorted by persona name then granted tool so a startup log is stable across restarts (Registry.All iterates a map).
type Config ¶ added in v0.14.0
type Config struct {
DisplayName string `yaml:"display_name"`
Description string `yaml:"description,omitempty"`
Roles []string `yaml:"roles"`
Tools ToolRulesConfig `yaml:"tools"`
Connections ConnectionRulesConfig `yaml:"connections"`
Context ContextOverridesYAML `yaml:"context"`
Priority int `yaml:"priority,omitempty"`
}
Config is the configuration format for personas.
type ConnectionRules ¶ added in v1.48.0
type ConnectionRules struct {
// Allow patterns for allowed connections (supports wildcards like "prod-*").
Allow []string `json:"allow,omitempty" yaml:"allow,omitempty"`
// Deny patterns for denied connections (takes precedence over Allow).
Deny []string `json:"deny,omitempty" yaml:"deny,omitempty"`
}
ConnectionRules defines connection-level access rules for a persona. These work alongside ToolRules — a tool call must pass both the tool check AND the connection check. Connections are deny-by-default: an empty Allow list grants NO connections, so a persona that should reach data must list what it may reach. See ToolFilter.IsConnectionAllowed for the evaluation order.
type ConnectionRulesConfig ¶ added in v1.48.0
type ConnectionRulesConfig struct {
Allow []string `yaml:"allow,omitempty"`
Deny []string `yaml:"deny,omitempty"`
}
ConnectionRulesConfig is the YAML configuration for connection rules.
type ContextOverrides ¶ added in v1.48.0
type ContextOverrides struct {
// DescriptionPrefix is prepended to the server description (separated by
// a blank line). Use this to add persona-specific context before the
// base platform description. Ignored if DescriptionOverride is set.
DescriptionPrefix string `json:"description_prefix,omitempty" yaml:"description_prefix,omitempty"`
// DescriptionOverride replaces the server description entirely.
// Use this when a persona needs a completely different description.
DescriptionOverride string `json:"description_override,omitempty" yaml:"description_override,omitempty"`
// AgentInstructionsSuffix is appended to the server agent instructions
// (separated by a blank line). Use this to add persona-specific guidance
// after the base instructions. Ignored if AgentInstructionsOverride is set.
AgentInstructionsSuffix string `json:"agent_instructions_suffix,omitempty" yaml:"agent_instructions_suffix,omitempty"`
// AgentInstructionsOverride replaces the server agent instructions entirely.
// Use this when a persona needs completely different instructions.
AgentInstructionsOverride string `json:"agent_instructions_override,omitempty" yaml:"agent_instructions_override,omitempty"`
}
ContextOverrides defines per-persona overrides for the description and agent instructions that the platform_info tool returns. These let you tailor what an AI agent sees based on who is using the platform.
For each field pair (prefix/override), the override takes precedence. If an override is set, the prefix/suffix is ignored.
type ContextOverridesYAML ¶ added in v1.48.0
type ContextOverridesYAML struct {
DescriptionPrefix string `yaml:"description_prefix,omitempty"`
DescriptionOverride string `yaml:"description_override,omitempty"`
AgentInstructionsSuffix string `yaml:"agent_instructions_suffix,omitempty"`
AgentInstructionsOverride string `yaml:"agent_instructions_override,omitempty"`
}
ContextOverridesYAML is the YAML configuration for context overrides.
type OIDCRoleMapper ¶
type OIDCRoleMapper struct {
// ClaimPath is the dot-separated path to roles in claims.
ClaimPath string
// RolePrefix filters roles to those starting with this prefix.
RolePrefix string
// PersonaMapping maps roles to persona names.
PersonaMapping map[string]string
// Registry is the persona registry.
Registry *Registry
}
OIDCRoleMapper extracts roles from OIDC token claims.
func (*OIDCRoleMapper) GrantingRoles ¶ added in v1.131.3
func (m *OIDCRoleMapper) GrantingRoles() []string
GrantingRoles returns, sorted and without duplicates, every role that Resolve maps to a persona on its own: the roles each registered persona matches on, and each explicitly mapped role whose persona is registered. It is the list an operator chooses a key's roles from.
func (*OIDCRoleMapper) MapToPersona ¶
MapToPersona maps roles to a persona: an explicit role-to-persona mapping first, then the registry's role matching. Roles that map to nothing resolve to the deny-all DefaultPersona, so an identity an operator never granted a role reaches no tool.
func (*OIDCRoleMapper) MapToRoles ¶
func (m *OIDCRoleMapper) MapToRoles(claims map[string]any) ([]string, error)
MapToRoles extracts roles from OIDC claims.
func (*OIDCRoleMapper) Resolve ¶ added in v1.131.3
func (m *OIDCRoleMapper) Resolve(roles []string) (*Persona, bool)
Resolve returns the persona roles map to, or (nil, false) when they map to none: an explicit role-to-persona mapping first, then the registry's role matching. It is MapToPersona without the deny-all fallback, for a caller that needs to tell an unmapped identity apart from one mapped to a persona, such as the admin API warning about a key whose roles reach nothing (#1705).
type Persona ¶
type Persona struct {
// Name is the unique identifier for this persona.
Name string `json:"name" yaml:"name"`
// DisplayName is the human-readable name.
DisplayName string `json:"display_name" yaml:"display_name"`
// Description describes this persona.
Description string `json:"description,omitempty" yaml:"description,omitempty"`
// Roles are the roles that map to this persona. When a user authenticates
// via OIDC, their token claims are mapped to roles. When using API keys,
// roles are assigned directly. A user gets this persona when any of their
// roles match.
Roles []string `json:"roles" yaml:"roles"`
// Tools defines tool access rules (allow/deny glob patterns).
Tools ToolRules `json:"tools" yaml:"tools"`
// Connections defines connection-level access rules. A tool call must pass
// both the tool check and the connection check, and so must every discovery
// surface (#1108): search, fetch, and list_connections show only the
// connections these rules admit. Connections are deny-by-default (see
// ToolFilter.IsConnectionAllowed) — an empty Allow grants no connection, so a
// persona that should reach data must list what it may reach.
Connections ConnectionRules `json:"connections" yaml:"connections"`
// APIRoutes defines per-(connection, method, path) rules for the HTTP
// API gateway toolkit (kind=api). Layered on top of Connections: if a
// persona is allowed a connection at the tool/connection level, APIRoutes
// can further restrict which HTTP methods and paths the model can invoke
// against that connection. Personas with no APIRoutes entries for a
// given connection get the existing connection-level behavior unchanged
// (backward-compatible). See pkg/persona/filter.go IsAPIRouteAllowed.
APIRoutes []APIRouteRule `json:"api_routes,omitempty" yaml:"api_routes,omitempty"`
// Context defines per-persona overrides for the platform description and
// agent instructions returned by the platform_info tool.
Context ContextOverrides `json:"context" yaml:"context"`
// Priority determines which persona takes precedence when a user's roles
// match multiple personas. Higher values win. Default is 0; the built-in
// admin persona uses 100.
Priority int `json:"priority,omitempty" yaml:"priority,omitempty"`
// ServiceAccount marks the persona an automated caller signs in under,
// such as an integration holding an API key: its calls are audited but not
// added to the call catalog, and the records it wrote before it was marked
// are removed by the catalog's next sweep (#1980). A person's own persona
// leaves it false, and its calls are cataloged.
ServiceAccount bool `json:"service_account,omitempty" yaml:"service_account,omitempty"`
// Portal is what the persona's people may do in the portal, one setting
// per area (#2225). nil, and any field left unset, resolves to what the
// persona reached before the setting existed; read it through
// PortalPermissions. An administrator has every area whatever this says,
// and the tools an agent calls are governed by Tools, not by this.
Portal *PortalAccess `json:"portal,omitempty" yaml:"portal,omitempty"`
// External marks a persona for people outside the organization (#2092),
// whose accounts the platform creates. It sets limits the persona's other
// settings cannot raise: no MCP tool, only what is shared with its people
// in the portal, no sharing, no API keys and no people directory. Read the
// portal half through PortalPermissions.
External bool `json:"external,omitempty" yaml:"external,omitempty"`
// AllowSharing lets the people signed in under this persona share what
// they own: an asset, collection or prompt with a person, and an asset to
// a notification channel (#2241). nil, which every persona written before
// it carries, allows it; read it through SharingAllowed. Without it the
// portal shows no Share action and every share-creating route and tool
// action refuses. An administrator shares whatever the persona says.
AllowSharing *bool `json:"allow_sharing,omitempty" yaml:"allow_sharing,omitempty"`
// Source indicates where this persona was loaded from at runtime.
// Values: "file" (YAML config), "database" (DB-managed), "both" (file
// with DB override). This is runtime metadata — not persisted.
Source string `json:"source,omitempty" yaml:"-"`
}
Persona defines a user persona with associated permissions and customizations.
func AdminPersona ¶
func AdminPersona() *Persona
AdminPersona creates an admin persona with full access.
func DefaultPersona ¶
func DefaultPersona() *Persona
DefaultPersona creates a default persona that denies all access. This ensures fail-closed behavior - users must be explicitly granted access.
func (*Persona) ApplyAgentInstructions ¶ added in v1.48.0
ApplyAgentInstructions returns the effective agent instructions for this persona. If AgentInstructionsOverride is set, it replaces the base entirely. If AgentInstructionsSuffix is set, it is appended to the base. Otherwise the base is returned unchanged.
func (*Persona) ApplyDescription ¶ added in v1.48.0
ApplyDescription returns the effective description for this persona. If DescriptionOverride is set, it replaces the base entirely. If DescriptionPrefix is set, it is prepended to the base. Otherwise the base is returned unchanged.
func (*Persona) PortalPermissions ¶ added in v1.149.0
func (p *Persona) PortalPermissions(granted []string) PortalAccess
PortalPermissions is a persona's portal access with every field set. A field the persona leaves unset resolves to what the persona reached before #2225: assets own, resources persona, the API browser and every switch on; knowledge write when granted holds apply_knowledge and read otherwise; the catalog write when it holds apply_knowledge and fetch, read with fetch alone, none without it. granted is the persona's tools as the portal has always read them: its rules over the tools the deployment registers, so a tool the deployment does not register grants nothing here either.
func (*Persona) SharingAllowed ¶ added in v1.149.0
SharingAllowed reports whether the persona's people may share what they own. An unset AllowSharing allows it.
func (*Persona) StoredPortal ¶ added in v1.150.0
func (p *Persona) StoredPortal(granted []string) PortalAccess
StoredPortal is the persona's portal block resolved without the limits External sets: what the persona editor shows and saves, so marking a persona external and back leaves its levels where they were (#2257). granted is as PortalPermissions takes it.
type PortalAccess ¶ added in v1.149.0
type PortalAccess struct {
// Assets is assets and collections: shared or own.
Assets string `json:"assets,omitempty" yaml:"assets,omitempty"`
// Resources is managed resources: none, shared, own or persona.
Resources string `json:"resources,omitempty" yaml:"resources,omitempty"`
// Knowledge is knowledge pages: none, read or write.
Knowledge string `json:"knowledge,omitempty" yaml:"knowledge,omitempty"`
// Catalog is the platform catalog: none, read or write.
Catalog string `json:"catalog,omitempty" yaml:"catalog,omitempty"`
// APIs is the portal API browser: none or browse.
APIs string `json:"apis,omitempty" yaml:"apis,omitempty"`
// ScratchTables is the scratch-table routes and page: none, list or
// register (#2257). The connections a table sits on are still the
// persona's Connections.
ScratchTables string `json:"scratch_tables,omitempty" yaml:"scratch_tables,omitempty"`
// Feedback is threads, comments, activity, worklists and sign-off.
Feedback *bool `json:"feedback,omitempty" yaml:"feedback,omitempty"`
// Prompts is the prompt library.
Prompts *bool `json:"prompts,omitempty" yaml:"prompts,omitempty"`
// Automations is managed scripts in the portal; the script tools an agent
// calls are governed by Tools. It replaces hide_automations (#2109).
Automations *bool `json:"automations,omitempty" yaml:"automations,omitempty"`
// Search is the portal's unified search.
Search *bool `json:"search,omitempty" yaml:"search,omitempty"`
// Memory is memory and insights.
Memory *bool `json:"memory,omitempty" yaml:"memory,omitempty"`
// Download is exporting an asset or a resource as PDF; the portal names
// it PDF export (#2257). Reading content is the item's own sharing.
Download *bool `json:"download,omitempty" yaml:"download,omitempty"`
// Uploads lets the people of an external persona upload files, each one
// shared on arrival with the recipients it names (#2258). nil is no
// uploads; an internal persona's people upload by their Resources level
// and this is dropped from its resolved block.
Uploads *PortalUploads `json:"uploads,omitempty" yaml:"uploads,omitempty"`
// External is reported, never set: the persona is external (#2092), so
// the portal offers its people no people directory and no API keys.
External bool `json:"external,omitempty" yaml:"-"`
}
PortalAccess is a persona's portal permissions (#2225). An empty level or a nil switch is unset, and resolves to what the persona reached before the setting existed (PortalPermissions); the persona editor writes every field.
func FullPortalAccess ¶ added in v1.149.0
func FullPortalAccess() PortalAccess
FullPortalAccess is every area at its highest level: what an administrator has, whatever their persona says.
func (PortalAccess) Resolve ¶ added in v1.149.0
func (a PortalAccess) Resolve(granted []string) PortalAccess
Resolve is the block with every area resolved as PortalPermissions resolves a persona's: an area left unset reads from granted.
func (*PortalAccess) Validate ¶ added in v1.149.0
func (a *PortalAccess) Validate() error
Validate refuses a level an area does not admit. Unset fields are valid.
type PortalUploads ¶ added in v1.150.0
type PortalUploads struct {
SharePersonas []string `json:"share_personas,omitempty" yaml:"share_personas,omitempty"`
ShareEmails []string `json:"share_emails,omitempty" yaml:"share_emails,omitempty"`
}
PortalUploads is who an external persona's uploads are shared with: every person of each persona and each address, as viewers.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry manages persona definitions.
A registry has no fallback persona. A caller whose roles match no registered persona resolves to nothing, and every surface that consults the registry treats that as no access. Access is granted only by a role a persona names.
func (*Registry) AdminRule ¶ added in v1.149.0
AdminRule is the one rule for who administers the platform (#2228): the persona a user's roles resolve to is the admin persona. Every surface asks it -- the admin API, the portal, resources, the MCP tools -- so a user is an administrator on all of them or on none. An empty admin persona makes no one an administrator; a nil registry likewise.
func (*Registry) GetForRoles ¶
GetForRoles returns the highest-priority persona whose roles intersect the given roles, or (nil, false) when none does.
There is deliberately no fallback. A caller whose roles match no persona is unmapped, and an unmapped caller has no access: the MCP authorizer resolves them to the deny-all DefaultPersona and the portal refuses the request outright. Reintroducing a configured fallback here would silently grant that persona's tools to every identity the IdP will issue a token for, whether or not an operator ever granted them a role.
func (*Registry) IsAdminName ¶ added in v1.150.0
IsAdminName reports whether the persona named name is the administrator persona: named adminPersona, registered, and not external (#2257). It is AdminRule for a caller that holds a resolved persona's name rather than roles, so no surface treats an external persona that carries the administrator's name as one.
func (*Registry) IsServiceAccount ¶ added in v1.138.2
IsServiceAccount reports whether the persona registered under name is marked as a service account. It reads the registry as it is now, so a persona an administrator marks at run time is honored from that moment on every replica its reload reaches. A nil registry marks nothing.
func (*Registry) LoadFromConfig ¶
LoadFromConfig loads personas from a configuration map.
func (*Registry) Names ¶ added in v1.130.0
Names returns the names of every registered persona. It is the question a caller asks when it holds a name from somewhere else and needs to know whether this deployment defines it, without caring about the persona itself.
func (*Registry) ServiceAccountNames ¶ added in v1.138.2
ServiceAccountNames returns the names of every persona marked as a service account, sorted. A nil registry returns an empty slice.
func (*Registry) SetRoleMapping ¶ added in v1.149.0
SetRoleMapping installs the role-to-persona mapping GetForRoles consults before matching roles (personas.role_mapping.oidc_to_persona), so every surface that resolves a persona -- MCP, the portal, the admin API and resources -- resolves the same one (#2228).
func (*Registry) SharingAllowed ¶ added in v1.149.0
SharingAllowed reports whether the persona registered under name lets its people share what they own (#2241), read from the registry as it is now. An unknown name is refused: a call that resolved no persona has no grant to read the permission from.
func (*Registry) Unregister ¶ added in v0.17.0
Unregister removes a persona by name. Returns error if not found.
type RoleMapper ¶
type RoleMapper interface {
// MapToRoles extracts roles from claims.
MapToRoles(claims map[string]any) ([]string, error)
// MapToPersona maps roles to a persona.
MapToPersona(ctx context.Context, roles []string) (*Persona, error)
}
RoleMapper maps identity claims to platform roles and personas.
type RouteDecision ¶ added in v1.126.0
type RouteDecision struct {
Allowed bool `json:"allowed"`
// Source is "deny" when a deny rule refused the query and "allow" when
// an allow rule admitted it. "default" means no rule decided, and
// Allowed says which of its two cases this is: true when no rule named
// the connection at all, which leaves the connection-level gate as the
// sole one, and false when rules named it and none of them admitted
// this query — a denial the rule set produced rather than any one rule.
Source AccessSource `json:"source"`
// MatchedRule is the rule the decision came from. Nil for
// AccessSourceDefault, and nil when rules touch the connection but
// none matched the query, which is a denial no single rule produced.
MatchedRule *APIRouteRule `json:"matched_rule,omitempty"`
}
RouteDecision is the verdict a persona's APIRoutes rules produce for one RouteQuery, with the rule that produced it recorded so an operator can be shown why.
type RouteQuery ¶ added in v1.126.0
type RouteQuery struct {
// Connection is the connection name the rules' Connection globs are
// matched against.
Connection string
// Method is the HTTP method, uppercase.
Method string
// Path is the concrete request path.
Path string
// Template is the catalog path template Path resolved from. Empty
// when the connection's catalog declares no matching operation, and
// equal to Path for an operation whose path carries no placeholder.
Template string
}
RouteQuery is one (connection, method, path) an APIRoutes rule set is evaluated against.
Path and Template are two forms of the same request and both are matched, because the surfaces that consult a rule reach it in different forms. api_discover and endpoint search hold the operation the catalog declares, whose path is a template ("/v1/orders/{id}"); api_invoke_endpoint holds the path the call actually reaches, with the parameters substituted ("/v1/orders/42"). A rule naming one form has to govern both, or a rule that hides an operation from the listing would permit the call it hides.
type StaticRoleMapper ¶
type StaticRoleMapper struct {
// GroupPersonas maps groups to persona names.
GroupPersonas map[string]string
// PersonaName is the persona every caller is assigned. An empty name, or a
// name absent from the registry, yields the deny-all DefaultPersona.
PersonaName string
// Registry is the persona registry.
Registry *Registry
}
StaticRoleMapper assigns every caller one named persona regardless of their roles. It exists for embedders that do their own authorization upstream and want a fixed persona; it is not wired into any platform auth path, where access follows role-to-persona matching.
func (*StaticRoleMapper) MapToPersona ¶
MapToPersona returns the configured persona, or the deny-all DefaultPersona when none is configured or the configured name is not registered.
func (*StaticRoleMapper) MapToRoles ¶
func (*StaticRoleMapper) MapToRoles(_ map[string]any) ([]string, error)
MapToRoles returns static roles (not used for static mapping).
type ToolFilter ¶
type ToolFilter struct {
// contains filtered or unexported fields
}
ToolFilter filters tools based on persona rules.
func NewToolFilter ¶
func NewToolFilter(registry *Registry) *ToolFilter
NewToolFilter creates a new tool filter.
func (*ToolFilter) FilterTools ¶
func (f *ToolFilter) FilterTools(persona *Persona, tools []string) []string
FilterTools filters a list of tools based on persona rules.
func (*ToolFilter) IsAPIRouteAllowed ¶ added in v1.59.0
func (f *ToolFilter) IsAPIRouteAllowed(persona *Persona, q RouteQuery) bool
IsAPIRouteAllowed reports whether the persona may invoke the query's method and path on its connection through the HTTP API gateway. Layered on top of IsConnectionAllowed: callers MUST also pass the connection-level gate; this function adds per-(method, path) granularity for kind=api connections.
Semantics (also documented on APIRouteRule):
- If no APIRoutes entry's Connection glob matches the connection, the check is a no-op (returns true) — the existing connection-level check is the sole gate, preserving backward-compat for personas written before APIRoutes existed.
- If at least one APIRoutes entry matches the connection, the call must pass: no matching deny rule, AND at least one matching allow rule.
- A nil persona always denies (fail-closed).
func (*ToolFilter) IsAllowed ¶
func (*ToolFilter) IsAllowed(persona *Persona, toolName string) bool
IsAllowed checks if a tool is allowed for a persona.
func (*ToolFilter) IsConnectionAllowed ¶ added in v1.48.0
func (f *ToolFilter) IsConnectionAllowed(persona *Persona, connectionName string) bool
IsConnectionAllowed checks if a connection is allowed for a persona. Connections are deny-by-default: a persona must carry an explicit ConnectionRules.Allow pattern that matches the connection name. An empty Allow list grants no connections. Deny rules take precedence over allow. Empty connection names are platform-level (always allowed). A nil persona is denied (fail-closed).
This mirrors the tool axis (evaluateToolAccess), which also defaults to deny. There is no "empty Allow means all connections" shortcut and no special-cased connection category — the operator grants exactly the connections each persona should reach.
func (*ToolFilter) WhyAPIRouteAllowed ¶ added in v1.126.0
func (*ToolFilter) WhyAPIRouteAllowed(persona *Persona, q RouteQuery) RouteDecision
WhyAPIRouteAllowed returns the same verdict IsAPIRouteAllowed returns with the deciding rule recorded, so the admin test-access route and the persona editor can render which rule governs an operation rather than only whether it is reachable.
func (*ToolFilter) WhyAllowed ¶ added in v1.57.0
func (*ToolFilter) WhyAllowed(persona *Persona, toolName string) AccessDecision
WhyAllowed returns the access decision for a tool name with the matched pattern and source recorded. Mirrors IsAllowed's logic (deny first, allow second, default deny last) but surfaces which clause produced the decision so callers can explain it to operators.
func (*ToolFilter) WhyConnectionAllowed ¶ added in v1.150.0
func (*ToolFilter) WhyConnectionAllowed(persona *Persona, connectionName string) AccessDecision
WhyConnectionAllowed returns IsConnectionAllowed's verdict with the pattern that decided it, so the persona editor can say which rule governs a connection (#2257). A platform-level (empty) connection is allowed with no pattern; a connection no rule matches is denied by default.
type ToolRules ¶
type ToolRules struct {
// Allow patterns for allowed tools (supports wildcards like "trino_*").
Allow []string `json:"allow" yaml:"allow"`
// Deny patterns for denied tools (takes precedence over Allow).
Deny []string `json:"deny" yaml:"deny"`
}
ToolRules defines tool access rules for a persona.
type ToolRulesConfig ¶
ToolRulesConfig is the YAML configuration for tool rules.