Help us shape the future of Go! Take the 2026 Go Developer Survey

perm

package
v0.56.0 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package perm is filex's per-user permission model: 29 named permissions, the presets built from them, and the resolver that turns an account's role, the install's defaults, the permission rules that match it and its own overrides into one answer per permission — with where that answer came from, so "why can't they do this?" has a reply.

It is the complement to package acl, not a replacement. acl answers "how far may this user reach into this path" (folder grants, the viewer ceiling, app-plugin locks); perm answers "which actions may this user take at all". A file action needs both (acl.Set.Can, Phase 2).

⚠ Permissions are persisted BY NAME (overrides, rules, the defaults setting), never by bit position. The bit layout of Set is a runtime detail and may be reordered freely; a stored name that is no longer in the catalogue is ignored on read.

Index

Constants

View Source
const (
	PresetFullAdmin  = "full_admin"
	PresetStandard   = "standard"
	PresetReadOnly   = "read_only"
	PresetUploadOnly = "upload_only"
	PresetGuest      = "guest"
)

Preset names.

View Source
const AppPrefix = "app."

AppPrefix starts every app permission key.

View Source
const MaxRuleDescriptionLen = 1000

MaxRuleDescriptionLen bounds a translated description: what the role's own description column holds on MySQL (00069, VARCHAR(1000)). A translated name is bounded like the name (MaxRuleNameLen).

View Source
const MaxRuleNameLen = 100

MaxRuleNameLen bounds a rule's name.

View Source
const SnapshotTTL = 3 * time.Second

SnapshotTTL bounds how stale the cached defaults and rules may be. Writes in this process call Invalidate and are seen at once; the TTL is what carries a write made by ANOTHER filex process on the same database.

Variables

View Source
var ErrInvalid = errors.New("perm: invalid")

ErrInvalid wraps every validation failure so callers can map it to a 400.

Guest can look and download, and cannot even change its own profile — a shared or demo login.

View Source
var ReadOnly = Standard & viewerCeiling

ReadOnly is what a role=viewer account could do before permissions existed: read, comment, personal tags, AI, apps, every protocol and token (read-only — acl caps the level), and its own profile. No file mutation, no sharing.

View Source
var Standard = filter(func(d Def) bool { return d.Group != GroupAdmin })

Standard is everything outside the admin area: exactly what a role=user account could do before permissions existed. It is the fallback for the install's defaults, so an upgrade changes nobody's access.

UploadOnly can put files in — encrypted ones too, as it could before files.encrypt existed — and nothing else: a drop-box account.

Functions

func AppDefaultFor added in v0.50.0

func AppDefaultFor(def AppDefault) map[string]bool

AppDefaultFor is, for each built-in role (viewer, user, admin), whether an account on it holds an app permission whose manifest default is def when nobody has decided it: no exception, no custom role, no decision of the built-in role. It is AppAllowed itself, asked of such an account, so the catalogue (GET /api/admin/roles/catalogue → apps[].default_for) carries the answer and the role editors' "Default (allowed / not allowed)" only reads it — 0.49.0's editors kept a copy of the last layer of appDecide.

func AppKey

func AppKey(plugin, id string) string

AppKey builds the key for permission id of app plugin.

func Conditionable

func Conditionable(p Perm) bool

Conditionable reports whether p may be changed by a rule with conditions.

func DismissGap added in v0.52.0

func DismissGap(ctx context.Context, store db.Store, id string) error

DismissGap records that the gap named id is on purpose: it is not shown again while it stays as it is.

func EffectiveRole added in v0.50.0

func EffectiveRole(own int64, groups []*model.Group, rules []*model.PermissionRule, providerID *int64) (roleID int64, via *model.Group)

EffectiveRole is the custom role an account holds: own when it is not 0, else its groups' (see above). groups are the account's; rules are every custom role (a group's role missing from them, or of another tenant, is no role). via is the group it came from, nil for the account's own role or none.

func Foreign added in v0.52.0

func Foreign(k string) bool

Foreign reports whether k is a permission this version does not know: not in the catalogue and not an app permission (perm/app.go, which has rules of its own).

func ForeignEffects added in v0.52.0

func ForeignEffects(m map[string]string) map[string]string

ForeignEffects returns the entries of an Allow / Deny map whose key this version does not know; nil when there are none.

func ForeignKeys added in v0.52.0

func ForeignKeys(list []string) []string

ForeignKeys returns the keys of list this version does not know, in their order, each once.

func GroupBefore added in v0.50.0

func GroupBefore(a, b *model.Group) bool

GroupBefore is the order a member's role is looked for in: the higher priority first, then the lower id.

func GroupInScope added in v0.50.0

func GroupInScope(g *model.Group, providerID *int64) bool

GroupInScope reports whether a group reaches an account of the given tenant: an install-wide group reaches everyone, a tenant's group only its own people.

func HolderRole

func HolderRole(r *model.PermissionRule, set Set) string

HolderRole is the built-in role the people holding r get. It is not a choice: a role that can add, change, delete or share anything — everywhere or in some folders — needs the User level to do it; any other role is a Viewer's, which keeps a read-only role read-only on every door, even if an exception later allows a write.

func Invalidate

func Invalidate()

Invalidate drops every Loader's cached defaults and rules. Call it after writing either. Prefer InvalidateFor, which says whose people the change can concern: Invalidate tells the hook nothing about it, which the server reads as "anybody, on every tenant".

func InvalidateFor added in v0.54.0

func InvalidateFor(ctx context.Context, userIDs ...int64)

InvalidateFor is Invalidate for a write made by ctx's caller about the named accounts. It tells the invalidate hook (the realtime hub: open explorers ask their menu answers again, #196) who may be concerned: the named accounts when there are any, else the people of the caller's tenant (ctx's tenant scope), else - a platform-level change, or a single-tenant install - everybody.

func IsAppKey

func IsAppKey(k string) bool

IsAppKey reports whether k names an app permission ("app.sign.request").

func KeepForeignEffects added in v0.52.0

func KeepForeignEffects(m, stored map[string]string) map[string]string

KeepForeignEffects puts the entries of stored this version does not know into m, as they were stored, and returns m.

func Known

func Known(k Perm) bool

Known reports whether k is in the catalogue.

func LevelUnderGroups added in v0.50.0

func LevelUnderGroups(current string, groupRole *model.PermissionRule, kept string) string

LevelUnderGroups is the built-in level (User or Viewer) of an account with no custom role of its own, as its groups decide it: the level the role its groups give it sets (RoleHolder); with none, the level kept from before a group's role first moved it (kept, "" for none — never administrator); else the level it has now.

ONE rule: group.SyncLevels writes it, and every preview a delegated administrator is judged by reads it (Loader.Preview, the admin handlers' groupsPreview) — so a change is judged at the level the account will really be on.

func MatchPreset

func MatchPreset(s Set) string

MatchPreset returns the name of the preset equal to s, or "" (custom).

func Middleware

func Middleware(next http.Handler) http.Handler

Middleware installs WithMemo on every request.

func NormalizeRule

func NormalizeRule(r *model.PermissionRule) error

NormalizeRule validates r in place and canonicalizes what it can (trimmed name and targets, lowercase dot-less extensions). It does not check that user ids or the provider exist — that is the handler's job, with the store.

func NormalizeRuleEdit

func NormalizeRuleEdit(r, prev *model.PermissionRule) error

NormalizeRuleEdit is NormalizeRule for a new version of prev (nil for a new role). Two things differ: a language prev's translations already carry stays acceptable (normalizeRuleTexts), and a permission prev holds that this version does not know - a later version's - is kept as prev has it, in the role's own list and in its folder part, whether the request sends it back or leaves it out (foreign.go). A folder part taken away takes those with it.

func NoteGapsSaved added in v0.52.0

func NoteGapsSaved(ctx context.Context, store db.Store, before, after []Gap, shown []string) error

NoteGapsSaved records what a save on this version did to one list's gaps: before and after are that list's gaps either side of the save. A gap the save opened is on purpose when the editor showed the administrator its key (shown: the keys it showed) and is dismissed there and then; a dismissed gap the save closed leaves the dismissed list, so that it is shown again should it ever open the other way.

func PreviewHolderRole added in v0.50.0

func PreviewHolderRole(r model.PermissionRule) string

PreviewHolderRole is RoleHolder for a role still being edited: the body as the role editor has it now, which may not be valid yet (no name, a path half typed). It answers what the role's people would be on if the body were saved as it stands — with the folder conditions a save stores (a blank path names no folder) — and refuses nothing. The custom role editor asks it (POST /api/admin/roles/preview) for each app permission's "Default" instead of keeping its own copy of this rule, which 0.49.0's did.

func RestoreBuiltinGap added in v0.52.0

func RestoreBuiltinGap(ctx context.Context, store db.Store, g Gap) ([]string, []string, error)

RestoreBuiltinGap gives a built-in role's stored list the gap's key, and returns the list before and after. Everything else in it - a later version's keys too - stays as it is.

func RoleHolder

func RoleHolder(r *model.PermissionRule) string

RoleHolder is HolderRole for a role as stored.

func SaveAppDecisions

func SaveAppDecisions(ctx context.Context, store db.Store, role string, m map[string]string) error

SaveAppDecisions replaces one built-in role's app decisions ({} clears them).

func SaveDefaults

func SaveDefaults(ctx context.Context, store db.Store, s Set) error

SaveDefaults stores the install's defaults for role=user. Role-only permissions are refused rather than silently dropped. A key the stored list holds that this version does not know - a later version's - is kept (foreign.go).

func SaveRoleBase

func SaveRoleBase(ctx context.Context, store db.Store, role string, s Set) error

SaveRoleBase stores the base of a built-in role. A viewer's is refused if it names a permission a viewer can never hold, rather than silently trimmed. A key the stored list holds that this version does not know is kept: the page cannot show it, so the save cannot have taken it away (foreign.go).

func SetInvalidateHook added in v0.54.0

func SetInvalidateHook(fn InvalidateHook)

SetInvalidateHook installs fn to be called after every invalidation; nil removes it. fn must not block: it runs on the request that wrote the change. The server wires the realtime hub here (api.BuildRouter), so a role, a rule or a group membership edited reaches the open explorers it can concern as an `access.changed` frame (internal/realtime/access.go).

func StartingRole

func StartingRole(rules []*model.PermissionRule, groups []string, providerID *int64) *model.PermissionRule

StartingRole is the custom role a NEW account is given when its first SSO sign-in carries one of the groups a role names: the lowest-numbered enabled role in the account's tenant with a matching sso_group target, or nil.

func StoredForeign added in v0.52.0

func StoredForeign(ctx context.Context, store db.Store, role string) ([]string, error)

StoredForeign returns the keys a built-in role's saved list holds that this version does not know: what its page cannot show and its save keeps.

func ValidAppDefault

func ValidAppDefault(d string) bool

ValidAppDefault reports whether d is one of the three.

func ValidateAppDecisions

func ValidateAppDecisions(m map[string]string) error

ValidateAppDecisions is SaveAppDecisions' check without the write.

func ValidateEffects

func ValidateEffects(m map[string]string) error

ValidateEffects checks an overrides or rule effects map: known keys, not role-only, allow/deny only.

func ValidateEffectsEdit added in v0.52.0

func ValidateEffectsEdit(m, stored map[string]string) error

ValidateEffectsEdit is ValidateEffects for a new version of stored (a person's exceptions, a role's folder part): a key this version does not know passes when stored holds it already. Its effect is not looked at: the stored one is kept (KeepForeignEffects).

func ValidateRoleBase

func ValidateRoleBase(role string, s Set) error

ValidateRoleBase is SaveRoleBase's check without the write: role=user may not hold a role-only permission, role=viewer nothing past the viewer ceiling, and no other role has an editable base.

func WithMemo

func WithMemo(ctx context.Context) context.Context

WithMemo returns ctx carrying a memo so a request that consults permissions several times (one acl.LoadSet per storage it touches) resolves each account once. Install it once per request, at the router.

func WithoutForeign added in v0.52.0

func WithoutForeign(m map[string]string) map[string]string

WithoutForeign returns m without the keys this version does not know: what a request leaves when the later version's entries, which it may only send back, are set aside to be put back as they were stored (KeepForeignEffects).

Types

type AppDecisions

type AppDecisions map[string]map[string]string

AppDecisions is the built-in roles' app decisions: {"user": {"app.sign.request": "deny"}, "viewer": {...}}.

func LoadAppDecisions

func LoadAppDecisions(ctx context.Context, store db.Store) (AppDecisions, error)

LoadAppDecisions reads the built-in roles' app decisions; none is empty.

type AppDefault

type AppDefault string

AppDefault is who holds an app permission nobody has decided: the app's manifest picks it, the administrator can change it per role and per person.

const (
	// AppDefaultViewer: every account (viewers included).
	AppDefaultViewer AppDefault = "viewer"
	// AppDefaultUser: accounts that can change files — not viewers.
	AppDefaultUser AppDefault = "user"
	// AppDefaultAdmin: administrators only, until granted.
	AppDefaultAdmin AppDefault = "admin"
)

type Def

type Def struct {
	Key   Perm
	Group Group
	// RoleOnly entries are held only through the account role; rules and
	// overrides naming them are ignored (and refused by Validate*).
	RoleOnly bool
	// ViewerCapped entries can never be allowed for a role=viewer account,
	// whatever a rule or override says — the same line acl.RoleCeiling draws
	// at LevelViewer for file mutation, extended to sharing (which needs
	// editor level today, see handlers/share.go).
	ViewerCapped bool
}

Def describes one catalogue entry.

func All

func All() []Def

All returns the catalogue in UI order. The slice is a copy.

func Lookup

func Lookup(k Perm) (Def, bool)

Lookup returns the definition of k and whether it exists.

type Gap added in v0.52.0

type Gap struct {
	// ID names the gap for the restore and dismiss requests:
	// "builtin:user:files.encrypt", "role:12:files.encrypt".
	ID   string `json:"id"`
	Key  Perm   `json:"key"`
	From Perm   `json:"from"`
	// Role is the built-in role whose list it is ("user"); empty for a
	// custom role.
	Role string `json:"role,omitempty"`
	// RuleID and RuleName are the custom role whose folder part it is.
	RuleID   int64  `json:"rule_id,omitempty"`
	RuleName string `json:"rule_name,omitempty"`
	// ProviderID is the custom role's tenant: nil for the platform's own
	// roles and on an install without tenants.
	ProviderID *int64 `json:"-"`
}

Gap is one list that allows From and not Key.

func AllGaps added in v0.52.0

func AllGaps(ctx context.Context, store db.Store) ([]Gap, error)

AllGaps returns every gap, dismissed or not: the built-in roles' first, then the custom roles' in id order.

func AnnounceGaps added in v0.52.0

func AnnounceGaps(ctx context.Context, store db.Store) (fresh, open []Gap, err error)

AnnounceGaps returns the open gaps, and those of them administrators have not been told of yet, and records that they now have been. It also lets go of the dismissals of gaps that are gone. Called at start and after a save that can open one (internal/permgap).

func BuiltinRoleGaps added in v0.52.0

func BuiltinRoleGaps(ctx context.Context, store db.Store, role string) ([]Gap, error)

BuiltinRoleGaps returns the gaps of a built-in role's saved list.

func FindGap added in v0.52.0

func FindGap(ctx context.Context, store db.Store, id string) (Gap, bool, error)

FindGap returns the gap named id, dismissed or not; false when there is no such gap any more (the list was saved, the role deleted).

func OpenGaps added in v0.52.0

func OpenGaps(ctx context.Context, store db.Store) ([]Gap, error)

OpenGaps returns the gaps nobody has said were on purpose.

func RuleGaps added in v0.52.0

func RuleGaps(r *model.PermissionRule) []Gap

RuleGaps returns the gaps of a custom role's folder part: it allows the older key there, does not decide the carved-out one, and the role's own list does not hold it (where it does, the folder part inherits it).

func (Gap) Builtin added in v0.52.0

func (g Gap) Builtin() bool

Builtin reports whether the gap is in a built-in role's list.

type Group

type Group string

Group is a UI grouping of the catalogue.

const (
	GroupFiles   Group = "files"
	GroupSharing Group = "sharing"
	GroupAccess  Group = "access"
	GroupAccount Group = "account"
	GroupAdmin   Group = "admin"
)

type GroupRef added in v0.50.0

type GroupRef struct {
	ID   int64
	Name string
}

GroupRef names the group an account's custom role came from.

type Input

type Input struct {
	UserID     int64
	Role       string
	ProviderID *int64
	// CustomRoleID is the one custom role the account holds: its own
	// (user_custom_roles), else its groups' (EffectiveRole); 0 is none.
	CustomRoleID int64
	// ViaGroup is the group CustomRoleID came from; nil when it is the
	// account's own.
	ViaGroup *GroupRef
	// Groups are the account's groups, for Loader.Preview only: filled from
	// the store before the change, so a change can leave one out or alter
	// one (a group deleted, its role cleared), and the role is then picked
	// from them. Resolve does not read it.
	Groups []*model.Group
	// Defaults is the base set for role=user (model.SettingPermissionDefaults).
	Defaults Set
	// ViewerBase is the base set for role=viewer
	// (model.SettingPermissionViewerDefaults); nil is the Read-only preset.
	ViewerBase *Set
	// Rules is every custom role on the install; Resolve applies the
	// account's own (CustomRoleID), if it is enabled and in scope.
	Rules []*model.PermissionRule
	// Overrides is the user's own allow/deny map (keys are Perm names, and
	// app permission keys — perm/app.go).
	Overrides map[string]string
	// AppDecisions are the account's built-in role's decisions about app
	// permissions (SettingPermissionAppDefaults for its role).
	AppDecisions map[string]string
}

Input is everything Resolve needs about one account. The Loader fills it from the store; tests build it by hand.

type InvalidateHook added in v0.54.0

type InvalidateHook func(ctx context.Context, userIDs []int64)

InvalidateHook is told after every invalidation: the writer's context and the accounts it named (none: the writer's tenant, or everybody).

type Loader

type Loader struct {
	Store db.Store
	// contains filtered or unexported fields
}

Loader reads what Resolve needs from the store.

⚠ Any read failure is returned, never papered over with a default: the caller denies on error (the same rule acl.Resolver follows). Falling back to the Standard preset when the defaults row is unreadable would hand an install that had narrowed its defaults the full set back.

func NewLoader

func NewLoader(store db.Store) *Loader

NewLoader returns a Loader backed by store.

func (*Loader) Load

func (l *Loader) Load(ctx context.Context, u *model.User) (*Result, error)

Load resolves u's permissions. A nil user holds nothing. An administrator is answered without touching the store. Within a request carrying a memo (WithMemo), an account is resolved once.

func (*Loader) Preview

func (l *Loader) Preview(ctx context.Context, u *model.User, change func(*Input)) (*Result, error)

Preview resolves u as Load would after change has been applied to the input: what the account WOULD hold with other exceptions, another custom role or another built-in role. u may be a new account that is not stored yet (id 0: no exceptions, no custom role). The per-request memo is neither read nor filled — the answer is about a state that does not exist.

It is how a delegated administrator's change is judged by its RESULT rather than by the words of the request: lifting a Deny, ending a restrictive custom role or picking a built-in role all hand out permissions without the request naming a single one.

type Perm

type Perm string

Perm is one permission, identified by its stable key.

const (
	FilesDownload Perm = "files.download"
	FilesCreate   Perm = "files.create"
	FilesModify   Perm = "files.modify"
	FilesRename   Perm = "files.rename" // a new name in the same folder
	FilesMove     Perm = "files.move"   // to another folder
	FilesDelete   Perm = "files.delete" // to trash
	FilesPurge    Perm = "files.purge"  // permanent delete, empty trash
	// FilesEncrypt is making something end-to-end encrypted: a folder's
	// first .filex-e2e.json (a new encrypted folder, or the first step of
	// encrypting one in place) or a new .fxe. It is carved out of
	// files.create, which the same write needs as well.
	FilesEncrypt Perm = "files.encrypt"
	FilesTag     Perm = "files.tag"
)

File actions. Path-scopable; each maps to an operation every protocol has.

const (
	ShareLinks       Perm = "share.links"
	ShareUploadLinks Perm = "share.upload_links"
	ShareUsers       Perm = "share.users"
	CommentsWrite    Perm = "comments.write"
	AIUse            Perm = "ai.use"
	PluginsRun       Perm = "plugins.run"
)

Sharing and features.

const (
	AccessWebDAV  Perm = "access.webdav"
	AccessSFTP    Perm = "access.sftp"
	AccessFTP     Perm = "access.ftp"
	AccessS3      Perm = "access.s3"
	AccessNFS     Perm = "access.nfs"
	AccessAPI     Perm = "access.api"
	AccessDesktop Perm = "access.desktop"
)

Access outside the web app.

const (
	AdminUsers   Perm = "admin.users"
	AdminGrants  Perm = "admin.grants"
	AdminShares  Perm = "admin.shares"
	AdminAudit   Perm = "admin.audit"
	AdminMonitor Perm = "admin.monitor"
	// AdminFull is the account role `admin`, surfaced as a permission so the
	// UI can show it. It is never granted by a rule or an override: storages,
	// settings, SSO, plugins and updates each amount to full control of the
	// server, so the only way to hold them is to BE an administrator.
	AdminFull Perm = "admin.full"
)

Admin area.

const (
	AccountEdit Perm = "account.edit"
)

Own account.

func CarvedOut added in v0.52.0

func CarvedOut() []Perm

CarvedOut returns the permissions carved out of an older one, in key order: the ones a gap can be about.

func RelocateNeeds

func RelocateNeeds(srcStorage int64, src string, dstStorage int64, dst string) []Perm

RelocateNeeds is what taking a path from src to dst is: a new name in the same folder is files.rename, the same name in another folder (or on another storage) files.move, and both at once needs both. Paths are storage-relative. A path that does not change still counts as a rename, so every caller has something to check.

type Preset

type Preset struct {
	Name string
	Set  Set
}

Preset is a named, fixed Set offered as a one-click starting point.

func Presets

func Presets() []Preset

Presets returns the presets in UI order.

type Result

type Result struct {
	Allowed Set
	Sources map[Perm]Source
	// Settings is the most restrictive merge of every applying rule's
	// settings.
	Settings model.PermRuleSettings
	// Rules are the ids of the rules that applied, in input order.
	Rules []int64

	// ViaGroup is the group the account's custom role came from, or nil.
	ViaGroup *GroupRef
	// contains filtered or unexported fields
}

Result is one account's resolved permissions.

func Resolve

func Resolve(in Input) *Result

Resolve computes an account's permissions. Layers, lowest first:

  1. base — role=user: in.Defaults; role=viewer: in.ViewerBase (the Read-only preset when unset); other: nothing.
  2. the account's custom role (one per person, CustomRoleID — its own, or else its groups', EffectiveRole), when it is enabled and in the account's tenant: its own list REPLACES the base, and its "different in some folders" part applies per path (CanAt).
  3. overrides — the user's own allow/deny; always wins over rules.

Then two hard lines no layer can cross: role-only permissions (admin.full) follow the role alone, and a viewer account never holds a ViewerCapped permission. role=admin short-circuits everything: an administrator holds every permission and is bound by no rule (they could edit the rule anyway).

func (*Result) AllowedInFolders

func (r *Result) AllowedInFolders() Set

AllowedInFolders is what the account may do only in some folders: the permissions its role's "different in some folders" part allows that it does not hold everywhere — minus what its own exceptions deny, and what a viewer can never hold. A client offers these actions and lets the server decide per path (CanAt); it must not hide them.

func (*Result) AppAllowed

func (r *Result) AppAllowed(key string, def AppDefault) (bool, Source)

AppAllowed decides the app permission key for this account. def is the app's manifest default for it. Layers, highest first: the administrator role, the person's own exception, their custom role's decision, the built-in role's decision, the app default.

func (*Result) AppInherited

func (r *Result) AppInherited(key string, def AppDefault) (bool, Source)

AppInherited is AppAllowed without the person's own exception: what the account would get from its role and the app's default alone — the answer a person's "Default" choice stands for on their exceptions editor.

func (*Result) Can

func (r *Result) Can(p Perm) bool

Can reports whether p is allowed.

func (*Result) CanAt

func (r *Result) CanAt(storageID int64, rel string, p Perm) bool

CanAt is Can for an action on one path of one storage: the same answer, except where a conditioned rule (model.PermRuleConditions) matches — its allow or deny then counts as a rule's does, beneath the user's overrides.

func (*Result) ConditionalRules

func (r *Result) ConditionalRules() []int64

ConditionalRules returns the ids of the applying rules that bind only some storages or paths — they are not in Rules, and not in Allowed.

func (*Result) VariesByFolder

func (r *Result) VariesByFolder() Set

VariesByFolder is every permission whose answer can differ from path to path: those the role's folder part allows or denies somewhere — minus what an exception of the account's own fixes everywhere, and what a viewer can never hold. A client asks the server (CanAt, per path) about these before offering them, and trusts Allowed for the rest.

func (*Result) Why

func (r *Result) Why(p Perm) Source

Why returns where p's answer came from.

func (*Result) WhyAt

func (r *Result) WhyAt(storageID int64, rel string, p Perm) Source

WhyAt is Why for one path.

type Set

type Set uint64

Set is a set of permissions as a bitset — the in-memory form only.

func FromStrings

func FromStrings(ss []string) Set

FromStrings parses persisted keys, dropping unknown ones (a permission removed from the catalogue must not fail a whole read).

func LoadDefaults

func LoadDefaults(ctx context.Context, store db.Store) (Set, error)

LoadDefaults returns the install's defaults for role=user: the stored set, or Standard when none was ever saved.

func LoadRoleBase

func LoadRoleBase(ctx context.Context, store db.Store, role string) (Set, error)

LoadRoleBase returns the editable base of a built-in role: the defaults for role=user, the viewer defaults (capped at the viewer ceiling) for role=viewer. Administrators have no editable base — everything is theirs.

func Of

func Of(ps ...Perm) Set

Of builds a Set; unknown keys are ignored.

func RoleSet

func RoleSet(r *model.PermissionRule) Set

RoleSet is what a custom role allows everywhere: its own list.

func ViewerCeiling

func ViewerCeiling() Set

ViewerCeiling is every permission a viewer account can ever hold.

func (Set) Has

func (s Set) Has(p Perm) bool

Has reports whether p is in s. Unknown p is never held.

func (Set) Keys

func (s Set) Keys() []Perm

Keys returns the members of s in catalogue order.

func (Set) Len

func (s Set) Len() int

Len is the number of permissions in s.

func (Set) Strings

func (s Set) Strings() []string

Strings is Keys as plain strings — what gets persisted.

func (Set) With

func (s Set) With(p Perm) Set

With returns s plus p (unchanged for unknown p).

func (Set) Without

func (s Set) Without(p Perm) Set

Without returns s minus p.

type Source

type Source struct {
	Kind     SourceKind `json:"kind"`
	RuleID   int64      `json:"rule_id,omitempty"`
	RuleName string     `json:"rule_name,omitempty"`
	// RuleNames are the role's names in other languages (migration 00071),
	// kept so a refusal can name the role in its reader's language
	// (RuleNameFor). Not on the wire: rule_name stays the role's own name,
	// and the admin pages hold the translations with the role itself.
	RuleNames map[string]string `json:"-"`
	// GroupID and GroupName say the custom role is the account's through a
	// group (it has none of its own) — set on a SourceRule or SourceRoleOff
	// answer only.
	GroupID   int64  `json:"group_id,omitempty"`
	GroupName string `json:"group_name,omitempty"`
}

Source is where one permission's answer came from.

func (Source) RuleNameFor

func (s Source) RuleNameFor(lang string) string

RuleNameFor is the deciding role's name for a reader of lang: its translation, else its own name (model.LocalizedText, the one rule PermissionRule.NameFor follows too).

type SourceKind

type SourceKind string

SourceKind says which layer decided a permission.

const (
	// SourceRole: the account role decided outright (admin holds everything;
	// admin.full follows the role alone).
	SourceRole SourceKind = "role"
	// SourceBase: nothing overrode it; it is the role's starting set (the
	// install defaults for role=user, the Read-only preset for role=viewer).
	SourceBase SourceKind = "base"
	// SourceRule: the account's custom role decided it (its list, or its
	// "different in some folders" part).
	SourceRule SourceKind = "rule"
	// SourceOverride: the user's own override.
	SourceOverride SourceKind = "override"
	// SourceViewerCeiling: something allowed it, but a viewer account can
	// never hold it.
	SourceViewerCeiling SourceKind = "viewer_ceiling"
	// SourceRoleOff: the account's custom role is switched off, and a
	// switched-off role gives nothing (never the built-in role instead — for
	// a role that takes things away, that would be MORE access).
	SourceRoleOff SourceKind = "role_off"
)
const SourceAppDefault SourceKind = "app_default"

SourceAppDefault is an app permission decided by the app's own default.

type UpgradeReport added in v0.51.0

type UpgradeReport struct {
	// Added are the catalogue's keys the install had not known, in
	// catalogue order — with or without an older key to follow.
	Added []Perm
	// RolesChanged is how many custom roles were given an added key.
	RolesChanged int
	// DefaultsChanged: a built-in role's saved list was given one.
	DefaultsChanged bool
	// ExceptionsChanged is how many people's own exceptions were given one.
	ExceptionsChanged int
}

UpgradeReport is what one UpgradeCatalogue changed.

func UpgradeCatalogue added in v0.51.0

func UpgradeCatalogue(ctx context.Context, store db.Store) (UpgradeReport, error)

UpgradeCatalogue gives every saved role, and every person's own exceptions, the keys this version added that they held through an older key (inheritOnAdd), and records the catalogue. Call it at start, after the migrations and before serving; a start that finds the catalogue it recorded changes nothing.

All or nothing, in one transaction: the catalogue is recorded only together with every change it implies, so a start that fails part-way leaves the database as it found it and the next start does the whole of it again. ⚠ The server runs on after such a failure, and a list an administrator edits before the next start looks like one saved before the key existed: the retry gives it the key as well.

Jump to

Keyboard shortcuts

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