e2epolicy

package
v0.52.0 Latest Latest
Warning

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

Go to latest
Published: Oct 5, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package e2epolicy — audit.go

The policy's own rows in the audit log.

The routes that change the policy, a tenant's ceiling or a request are skipped by the audit middleware (auth.ActionForPath: /api/admin/e2e), and the rows are written here instead — the arrangement of internal/pluginreq — for two reasons. The middleware reads only the URL, so a policy change would be a bare `e2e.update` with no tenant and no before/after, and a request's approval would read as a "create". And one of the events has no route at all: an approval is spent at a create door (CheckCreate), whichever door that is. Written here, every event is one row that names what it acted on.

Package e2epolicy decides who may end-to-end encrypt what, and where (docs/E2E-ENCRYPTION.md → Who may encrypt, docs/PERMISSIONS.md → The permissions).

Encrypting is CREATING one of two names: a folder marker (.filex-e2e.json) where there was none — a new encrypted folder, or the first step of encrypting a folder in place — or a new single encrypted file (*.fxe). Everything else an encrypted folder does stays free: rewriting its marker (a new password, a recovery key, another level), adding files to it, taking the encryption off, copying or moving what is encrypted already (a folder, a `.fxe` that stays a `.fxe`; RelocationEncrypts).

Three layers must all say yes, asked in this order:

  1. the platform's ceiling for the tenant (providers.e2e_allowed, which only the supertenant changes): off binds administrators too;
  2. the tenant's policy (providers.e2e_policy; the e2e.policy setting on a single-tenant install): off, admins, permitted — the default, what filex always did — or approval;
  3. the person's files.encrypt on the path. Administrators always hold it: roles cannot narrow them, the policy can. Under the approval policy a person also needs an administrator's approval for that folder, good for one encryption and ApprovalTTL.

The server cannot see what a client encrypts, only the names it creates. So the rule is enforced where a name is created: every door that creates a file calls Service.CheckCreate at the moment it knows the write CREATES the path (nothing is catalogued there), and the explorer only mirrors the answer (Service.AnswerFor). The server decides.

Package e2epolicy — requests.go

The approval half of the policy (`approval`): somebody who holds files.encrypt asks first, and an administrator of the tenant decides.

What a request is

A PERSON, a FOLDER P and a KIND, for this account (operator decision 2026-10-03: the kinds are separate, and an approval opens only what was asked, in the folder it was asked for, never in a folder below it):

  • folder: encrypt P itself, where it is (what P holds is encrypted in place);
  • new_folder: make one NEW encrypted folder directly inside P, spent only on a folder that holds nothing (Service.approvalPlaces);
  • file: one single encrypted file (`.fxe`) in P.

It is kept under ApprovalPath, where CheckCreate and AnswerFor look for it: a file's request is its folder's, because the name a `.fxe` is stored under cannot be known when the person asks. Approved, it opens exactly one encryption of its kind, once (CheckCreate marks it `used`; UseRecorder logs it, with the folder it was spent on), within ApprovalTTL of the approval. A request nobody decides expires after ApprovalTTL too, and past it nobody can decide it any more. One request per person, folder and kind waits at a time: asking again answers the one already waiting. A person has at most MaxPendingPerPerson waiting at once (too_many_pending past it).

A request is only ever what the rule itself answers `request` for (Service.AnswerForKind, asked with the request's kind): under the approval policy, with files.encrypt there, and no approval of that kind already waiting to be spent. What it names must be there and be of its kind: a folder for the two folder kinds, a file for a file (path_missing, kind_mismatch otherwise). Anything else would be a row an administrator could approve while the create door refused anyway, or never asked.

Who decides

The administrators of the tenant whose policy asked for the approval (Service.TenantFor), which is what the row's provider_id records — nil on a single-tenant install. A tenant administrator lists and decides their own tenant's requests only, and another tenant's reads as missing (404, the reasoning of handlers/tenantown.go). A single-tenant install's administrators see and decide every request. The platform operator (an administrator of the supertenant on a multi-tenant install) SEES every tenant's requests but decides only the platform's own: the supertenant's, or one filed under no tenant. Another tenant's is that tenant's administrators' decision (not_decidable, MayDecide; operator decision 2026-10-03).

What is announced

notify.EventE2ERequestCreated, to whoever decides it and to nobody else (announce): a tenant's request is ONE broadcast placed on the folder, so the bell hands it to the administrators of the tenant that storage belongs to, while the platform operator's bell does not take it (notify PlatformAdminBell); a webhook gets one delivery. The platform's own request on a multi-tenant install is addressed to each administrator of the supertenant instead, the webhook riding on the first of those rows. notify.EventE2ERequestDecided — to the requester alone.

Every step writes its own audit row (audit.go): create, approve, reject, expire, use.

Index

Constants

View Source
const (
	AuditActionPolicyUpdate   = "e2e_policy.update"
	AuditActionTenantUpdate   = "e2e_tenant.update"
	AuditActionRequestCreate  = "e2e_request.create"
	AuditActionRequestApprove = "e2e_request.approve"
	AuditActionRequestReject  = "e2e_request.reject"
	AuditActionRequestExpire  = "e2e_request.expire"
	AuditActionRequestUse     = "e2e_request.use"
)

The actions. Constants, so the admin panel's audit labels are held to them (web/tests/lib/auditLabel.test.ts reads `AuditAction… = "…"`).

View Source
const (
	AuditTargetPolicy  = "e2e_policy"
	AuditTargetTenant  = "providers"
	AuditTargetRequest = "e2e_request"
)

The target types those rows name: a policy (the tenant's id, or none for the instance's), a tenant (its provider id), a request (its id).

View Source
const ApprovalTTL = 7 * 24 * time.Hour

ApprovalTTL is how long an approval may wait to be used, and how long a request may wait for a decision.

View Source
const MaxPendingPerPerson = 20

MaxPendingPerPerson is how many requests one person may have waiting at once (operator decision 2026-10-03). Every new request tells the administrators, so a script must not be able to bury them: the next one is refused (429 too_many_pending) until one of the waiting ones is decided or expires. Asking again for a request already waiting is not a new one and is always answered.

View Source
const SettingE2EPolicyCarried = "e2e.policy.carried"

SettingE2EPolicyCarried marks that a single-tenant install's policy (the e2e.policy setting) has been carried to the supertenant's row once, when the install first started multi-tenant (CarryInstancePolicy). Its value is when that happened. While it is there nothing is carried again.

Variables

View Source
var ErrRefused = errors.New("e2e: encryption not allowed here")

ErrRefused is what every refusal of the rule unwraps to.

View Source
var ErrUndecided = errors.New("could not check the encryption policy")

ErrUndecided is what a door answers when the rule could not be decided: a lookup behind it failed (the tenant, the policy, the permissions, the approvals), or one the door made to ask it (the storage's row, the person a write is judged for). A server failure, never a refusal: a broken store must not read as the policy doing its job. Its words are the whole of it; what failed is in the log (DoorError).

Functions

func ApprovalPath

func ApprovalPath(rel, kind string) string

ApprovalPath is the folder an approval for encrypting rel as kind is kept under, and looked for by: rel itself for a folder (E2ERequestFolder: rel is the folder to encrypt; E2ERequestNewFolder: rel is the folder to make the new one in), the folder the file is in for a file. Requests are stored under it, and CheckCreate and AnswerFor find approvals through one function (Service.approvalPlaces), so the two cannot disagree.

⚠ A file's approval is its FOLDER's. The name a .fxe is stored under cannot be known when the person asks: a hidden name is random, a visible one takes the next free "name (2).ext.fxe". An approval is for this person, this folder, one encryption of this kind.

func Audit

func Audit(ctx context.Context, store db.Store, row AuditRow)

Audit writes one row. Best-effort, like every audit writer here: a row the store refuses is logged, never a reason to undo what already happened.

⚠ Names and flags only in Meta: the audit log is read by every administrator and exported (auth.AuditDetail says the same).

func CarryInstancePolicy

func CarryInstancePolicy(ctx context.Context, store db.Store, multiTenant bool) (bool, error)

CarryInstancePolicy keeps a single-tenant install's choice when it becomes a multi-tenant one (operator decision 2026-10-03).

On a single-tenant install the policy is the e2e.policy setting. Once FILEX_MULTI_TENANT is on, everybody with a tenant is judged by their tenant's row (providers.e2e_policy) instead, and the supertenant's row starts where migration 00080 put it: permitted. Read as it is, an install that had switched encryption off would find it silently on again for the platform's own people. So at the first multi-tenant start the setting is copied to the supertenant's row - only when that row still holds the migration's default and the setting says something else - logged, and written to the audit log. The setting itself is left alone (a return to single-tenant reads it again), and SettingE2EPolicyCarried is written, so a later start changes nothing, whatever an administrator has chosen since.

It answers whether it changed the supertenant's row. Nothing to carry - a single-tenant install, no setting, no supertenant, a setting nobody can read as a policy - is no change and no error.

func Decide

func Decide(in Input) (Answer, Reason)

Decide is the rule, with nothing left to look up.

func DoorError

func DoorError(u *model.User, st *model.Storage, err error) error

DoorError is what a door answers for err, the rule's verdict on u writing on st: nil and a refusal (*RefusedError) as they are, ErrUndecided for any other error. That error is logged here, once, with who and where (the storage and the person) and what failed, and never the path: a file's name can say as much as its contents. ErrUndecided itself passes unchanged, so a door may hand on what an earlier one decided.

func FileThere

func FileThere(ctx context.Context, drv storage.Driver, rel string) bool

FileThere reports whether a FILE is at rel on drv: Stat answers, and not with a folder. Only then is writing a key file or a `.fxe` at rel a rewrite (a password change, a key slot), never a new encryption.

A folder with the name is not the file: an object store keeps the file beside it (a prefix and an object of one name), and a copy onto it replaces it. A Stat that fails for any reason, or no driver to ask, is not "it is there" either: a backend that could not answer must not switch the rule off. storage.Exists answers the other way round (for the snapshot guard) and is the wrong question here.

func IsEncryptionName

func IsEncryptionName(rel string) bool

IsEncryptionName reports whether creating rel encrypts something: its name is the folder marker or a single encrypted file's (".fxe"), either in any case. The name is all that counts; the server cannot read what a client meant.

func MayDecide

func MayDecide(r *model.E2ERequest, platform *int64) bool

MayDecide reports whether an administrator may answer r. platform is the supertenant's id for the platform operator of a multi-tenant install, who answers only the platform's own requests (the supertenant's, or one filed under no tenant); nil for everybody else, whose reach is the list they are shown.

func RelocationEncrypts

func RelocationEncrypts(ctx context.Context, srcDrv storage.Driver, src, dst string, sameStorage bool) bool

RelocationEncrypts reports whether giving the item at src the path dst (a rename or a move) is a new encryption, to be asked as a create at dst. A copy is Service.CheckCopy's question at the doors (operator decision 2026-10-03: a copy of what is encrypted makes a new encryption); the queue's worker reads this rule's names half for a queued copy too, after the handler asked CheckCopy (ops refuseUnsettledEncryption). Landing on a key file's (`.filex-e2e.json`) or a `.fxe`'s name is one, except in exactly three cases (operator decisions 2026-09-30 and 2026-10-03):

  • src is a folder, under any name: a folder is no key file, and what it holds is not asked, as a folder copy's contents are not;
  • a `.fxe` that stays a `.fxe`: it was encrypted already;
  • a key file that stays the key file of its own folder (sameStorage, one folder: a change of case at most).

Everything else is asked: a plain file given either name, a `.fxe` given the key file's, a key file given a `.fxe`'s, and a key file moved into another folder, on this storage or another. Each makes an encryption nobody was asked about: upload a `.fxe` holding a key file's bytes, rename it `.filex-e2e.json`, and its folder is encrypted.

sameStorage: src and dst are on one storage. srcDrv is src's storage's driver. The names are read first, and src is looked at only when they do not decide. Only a clean "a folder" exempts it: a Stat that fails, or no driver to ask, counts as a file.

func TTLDays

func TTLDays() int

TTLDays is ApprovalTTL in days, as the admin table says it.

func TargetOf

func TargetOf(rel string) (dir string, kind string)

TargetOf is what creating rel encrypts, and where: the folder the marker sits in (the folder becoming encrypted), or the folder a .fxe goes into. kind is model.E2ERequestFolder or model.E2ERequestFile, and "" (with dir "") for a name that encrypts nothing. dir is storage-relative, with no leading slash; "" is the storage root.

⚠ The marker is matched in any case, as .fxe is. A case-insensitive storage keeps one file under ".filex-e2e.json" and ".FILEX-E2E.JSON", so a creation spelled the second way would escape the rule and still be read back as the marker.

func UseRecorder

func UseRecorder(store db.Store) func(ctx context.Context, r *model.E2ERequest, u *model.User, dir string)

UseRecorder is Options.OnUse for a Service: the e2e_request.use row of an approval CheckCreate has just spent — the one event of a request no route sees, since it happens at whichever door creates the name. target_name is the approval's folder, meta `encrypted` the folder it was spent on (dir): for a folder approval of P, P itself or one folder directly inside it. For a create over a protocol this row is the one link between an encryption and its approval.

Types

type Actor

type Actor struct {
	UserID *int64
	Name   string
	// TokenID: the API key the call came through, when it came through one.
	TokenID *int64
	IP      string
}

Actor is who did what a row records. The zero value is filex itself — a request that expired because nobody decided it.

type Answer

type Answer string

Answer is the rule's verdict on one encryption at one place.

const (
	// AnswerAllowed: go ahead.
	AnswerAllowed Answer = "allowed"
	// AnswerRequest: allowed once an administrator approves it. The explorer
	// offers "Request encryption" in its place.
	AnswerRequest Answer = "request"
	// AnswerDenied: not here, not by this person.
	AnswerDenied Answer = "denied"
)

type Ask

type Ask struct {
	Rel  string
	Kind string
}

Ask is one question of AnswersForKinds: may the person encrypt Rel as Kind ("" for the kind the catalogue says).

type AuditRow

type AuditRow struct {
	Action     string
	TargetType string
	TargetID   string
	// TargetName is what a reader wants once the thing has changed or gone:
	// a tenant's name, a request's place ("Depo://a/b"). Stored as meta
	// `target_name`, the key the Audit page reads (auth.SetAuditTarget).
	TargetName string
	Who        Actor
	Meta       map[string]any
}

AuditRow is one row of the policy's log.

type CreateInput

type CreateInput struct {
	User    *model.User
	Storage *model.Storage
	// Path is what the person asks about, relative to the storage: for kind
	// folder the folder to encrypt where it is, for kind new_folder the folder
	// to make a new encrypted folder in, and for kind file the file to
	// encrypt.
	Path   string
	Kind   string
	Reason string
	Who    Actor
}

CreateInput is one request as the requester asks for it.

type Decision

type Decision struct {
	ID      int64
	Approve bool
	// Tenant is the deciding administrator's tenant; nil when nobody confines
	// them. A request of another tenant reads as missing.
	Tenant *int64
	// Platform is the supertenant's id when the decider is the platform
	// operator on a multi-tenant install (MayDecide): they see every tenant's
	// request, and answer only the platform's own. nil: no such limit.
	Platform *int64
	Note     string
	Who      Actor
}

Decision is an administrator's answer to one request.

type Input

type Input struct {
	// TenantAllowed is the platform's ceiling (always true on a single-tenant
	// install).
	TenantAllowed bool
	// Policy is one of the model.E2EPolicy* values. Anything else is read as
	// model.E2EPolicyPermitted, the default.
	Policy string
	// IsAdmin: an administrator of the install or of the tenant.
	IsAdmin bool
	// HasPermission: files.encrypt on the path (acl.Set.Can).
	HasPermission bool
	// HasApproval: an unused, unexpired approval for this person and folder.
	HasApproval bool
}

Input is everything Decide weighs.

type Options

type Options struct {
	Store db.Store
	// ACL answers files.encrypt on a path. Nil: acl.New(Store).
	ACL *acl.Resolver
	// MultiTenant reads the ceiling and the policy per tenant (providers).
	// Off, the install's e2e.policy setting decides for everybody.
	MultiTenant bool
	// Now is the clock approvals expire by. Nil: time.Now.
	Now func() time.Time
	// Drivers reaches a storage's driver, for what the catalogue may not
	// know yet: whether a folder is new (nothing in it) and whether a path a
	// request names is there. Nil: the catalogue alone answers.
	Drivers func(storageID int64) (storage.Driver, error)
	// OnUse, when set, hears of every approval CheckCreate used up, after the
	// row changed — the requests service writes its e2e_request.use audit row
	// from it (UseRecorder). Never called for a lost race. dir is the folder
	// the approval was spent on: for an in-place approval of P, P itself; for
	// a new-folder approval of P, the new folder directly inside P; for a
	// file approval, its folder P.
	OnUse func(ctx context.Context, r *model.E2ERequest, u *model.User, dir string)
}

Options wires a Service.

type Reason

type Reason string

Reason says which layer refused. It is the `reason` of a 403 e2e_not_allowed and the last segment of its message key (server.e2e.not_allowed.<reason>); "" with AnswerAllowed.

const (
	ReasonTenantDisabled   Reason = "tenant_disabled"
	ReasonPolicyOff        Reason = "policy_off"
	ReasonAdminsOnly       Reason = "admins_only"
	ReasonPermission       Reason = "permission"
	ReasonApprovalRequired Reason = "approval_required"
)

type RefusedError

type RefusedError struct {
	Reason Reason
}

RefusedError is the rule's refusal, naming the layer that refused. The HTTP doors answer it as 403 e2e_not_allowed with the reason.

func (*RefusedError) Error

func (e *RefusedError) Error() string

func (*RefusedError) Unwrap

func (e *RefusedError) Unwrap() error

Unwrap lets errors.Is(err, ErrRefused) find the refusal under any wrapping.

type RequestError

type RequestError struct {
	Status  int
	Code    string
	Message string
	// Answer and Reason are the rule's own, when the refusal is that no
	// request is needed here or none could help (code not_requestable).
	Answer Answer
	Reason Reason
}

RequestError is a refusal the HTTP layer answers as it is.

func (*RequestError) Error

func (e *RequestError) Error() string

type Requests

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

Requests keeps the encryption requests.

func NewRequests

func NewRequests(o RequestsOptions) *Requests

NewRequests builds a Requests.

func (*Requests) Create

func (q *Requests) Create(ctx context.Context, in CreateInput) (*model.E2ERequest, bool, error)

Create files a request — or, when one already waits for this person, folder and kind, answers that one (created=false) and records nothing.

func (*Requests) Decide

func (q *Requests) Decide(ctx context.Context, d Decision) (*model.E2ERequest, error)

Decide approves or rejects a waiting request.

A request past its time is not waiting, whether or not a sweep has closed it yet: it expires here and is answered like one decided already. An approval would give it seven fresh days.

func (*Requests) ExpireDue

func (q *Requests) ExpireDue(ctx context.Context) int

ExpireDue closes what is past its time — a request nobody decided, an approval nobody spent — and answers how many. It runs before every read and write here, and hourly (StartSweeper).

The store hands it only what is due, the longest overdue first (E2ERequestFilter.ExpiresBefore), a batch at a time until a batch comes back short: one call closes every due row, however many newer rows there are. What it cannot read or write is logged and left to the next call. That delays the label, and opens nothing: Decide refuses a pending request past its time by itself, and FindApprovedE2ERequest an approval past its expiry, whatever their rows still say.

func (*Requests) List

func (q *Requests) List(ctx context.Context, tenant *int64, status string) ([]*model.E2ERequest, error)

List answers the requests in one state, newest first: "" is pending, "all" every state. tenant keeps that tenant's only (nil: every tenant's).

func (*Requests) Mine

func (q *Requests) Mine(ctx context.Context, userID int64) ([]*model.E2ERequest, error)

Mine answers one person's own requests, every state, newest first.

func (*Requests) StartSweeper

func (q *Requests) StartSweeper(ctx context.Context)

StartSweeper closes what is past its time now and every hour after, until ctx ends.

type RequestsOptions

type RequestsOptions struct {
	// Store keeps the rows (e2e_requests) and the audit log.
	Store db.Store
	// Policy answers whether a request is what the rule asks for, and whose
	// tenant files it.
	Policy *Service
	// Notify tells the administrators and the requester (nil: nobody).
	Notify notify.Service
	Now    func() time.Time
	Log    *slog.Logger
}

RequestsOptions wire a Requests.

type Service

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

Service answers the rule against the store: the tenant's ceiling and policy, the person's permissions, and the approvals.

func New

func New(o Options) *Service

New returns a Service.

func (*Service) AnswerFor

func (s *Service) AnswerFor(ctx context.Context, u *model.User, st *model.Storage, rel string) (Answer, Reason)

AnswerFor is the explorer's question: may u encrypt at rel on st, rel being the folder to encrypt in place, or the file to encrypt (AnswerForKind with the kind the catalogue says). It changes nothing: an approval is looked for, never used. When the answer cannot be found out - the tenant, the permissions or a lookup failed - it is denied (and logged): the menu hides what the server might refuse, and the server decides anyway.

func (*Service) AnswerForKind

func (s *Service) AnswerForKind(ctx context.Context, u *model.User, st *model.Storage, rel, kind string) (Answer, Reason)

AnswerForKind is AnswerFor for one kind of encryption: E2ERequestFolder (rel is the folder to encrypt where it is), E2ERequestNewFolder (rel is the folder to make a new encrypted folder in), E2ERequestFile (rel is the file), or "" for the one the catalogue says. The approvals it looks for are the ones CheckCreate would spend for that encryption (approvalPlaces), so the explorer and the doors cannot disagree. A kind that is not what is at rel (a file asked about as a folder) is denied.

func (*Service) AnswersFor

func (s *Service) AnswersFor(ctx context.Context, u *model.User, st *model.Storage, rels []string) []Answer

AnswersFor is AnswerFor for many paths on one storage, in their order.

func (*Service) AnswersForKinds

func (s *Service) AnswersForKinds(ctx context.Context, u *model.User, st *model.Storage, asks []Ask) []Answer

AnswersForKinds is VerdictsFor's answers alone.

func (*Service) CheckCopy

func (s *Service) CheckCopy(ctx context.Context, u *model.User, st *model.Storage, dst string, srcStorageID int64, src string, srcIsDir bool) error

CheckCopy is the rule at a door that COPIES the item at src (on srcStorageID; srcIsDir: it is a folder) to dst on st (operator decision 2026-10-03). A copy of an encrypted folder or of a `.fxe` makes a new encrypted item where it lands, exactly as creating one would, so it is asked as that create; a move and a rename are not (RelocationEncrypts).

  • A file is asked by the name it lands under (CheckCreate at dst): a `.fxe` copied as a `.fxe`, a key file copied into another folder, a plain file copied onto either name. A `.fxe` copied under a plain name is no encryption by its name - the rule reads names (docs: the honest limits).
  • A folder that holds a key file or a `.fxe` anywhere below it (the catalogue says, as the transfer guard reads it) is a NEW encrypted folder at dst: asked once, at dst, and under the approval policy it spends a new-folder approval of dst's folder. What it holds is looked up only when the rule would not let it through anyway, so an install whose policy lets this person encrypt pays nothing for it.

Under the approval policy it spends, as CheckCreate does: ask it once per copied item.

func (*Service) CheckCreate

func (s *Service) CheckCreate(ctx context.Context, u *model.User, st *model.Storage, rel string) error

CheckCreate is the rule at a door. It answers nil when u may create rel on st, a *RefusedError when the rule refuses, and another error when it could not be decided — the door fails the write either way.

Call it where the door knows the write CREATES rel (nothing is catalogued there), and only there: rewriting a marker that exists is free (a new password, a recovery key), and so are moves of what is already encrypted (RelocationEncrypts). A copy is asked with CheckCopy. A name that encrypts nothing returns nil before anything is looked up. A caller with no person behind the write passes the account it acts for; nil is refused.

Under the approval policy the person's approval is used up here, before the bytes are written. ⚠ A write that fails afterwards does not give it back — the person asks again. Spending it after the write would let two parallel uploads use one approval twice.

func (*Service) CheckCreateWithoutApproval

func (s *Service) CheckCreateWithoutApproval(ctx context.Context, u *model.User, st *model.Storage, rel string) error

CheckCreateWithoutApproval is CheckCreate that never uses an approval: where CheckCreate would spend one, it refuses with ReasonApprovalRequired, and nothing is recorded. Every other answer is CheckCreate's.

For a write judged as somebody who did not make it: a file request's upload is the link creator's file, but the visitor must not use up the approval the creator asked for (operator decision 2026-10-03).

func (*Service) MultiTenant

func (s *Service) MultiTenant() bool

MultiTenant reports whether the Service reads the ceiling and the policy per tenant (Options.MultiTenant).

func (*Service) PolicyFor

func (s *Service) PolicyFor(ctx context.Context, providerID *int64) (model.ProviderE2E, error)

PolicyFor is the ceiling and the policy that bind one tenant. A single-tenant install — and a caller with no tenant — has no ceiling and the install's setting for a policy. A policy nobody knows (a hand-written setting) reads as the default, permitted, which is what Decide does with it too.

func (*Service) SourceIsFolder

func (s *Service) SourceIsFolder(ctx context.Context, srcStorageID int64, srcDrv storage.Driver, src string) (bool, error)

SourceIsFolder reports whether the item a copy reads at src (on srcStorageID) is a folder: srcDrv answers when the door has it, else the storage's driver (Options.Drivers), else the catalogue. Nothing there is not a folder (the copy fails on its own); a look that fails is an error, so a folder is never taken for a file on a guess.

func (*Service) TenantFor

func (s *Service) TenantFor(ctx context.Context, u *model.User, st *model.Storage) (*int64, error)

TenantFor is the tenant whose ceiling and policy bind u encrypting on st; nil on a single-tenant install, where the install's setting decides.

A tenant's own member is judged by their tenant: confinement keeps them on its storages anyway. A platform operator — a member of the supertenant — is judged by the tenant whose data it is: the storage's tenant (its lowest-id link, as tags' teamTenantFor picks it), else their own. A storage no tenant is linked to is the platform's.

func (*Service) VerdictsFor

func (s *Service) VerdictsFor(ctx context.Context, u *model.User, st *model.Storage, asks []Ask) []Verdict

VerdictsFor is AnswerForKind for many questions on one storage, in their order, finding the tenant, the policy and the person's permissions once - the explorer asks for a whole listing at a time. The first lookup that fails ends the asking: it is logged once, and that question and every one after it is denied, instead of a failing store being asked again for each row.

type Verdict

type Verdict struct {
	Answer Answer
	Reason Reason
}

Verdict is the rule's answer to one Ask, with the layer that refused ("" when it is allowed; ReasonApprovalRequired with AnswerRequest).

Jump to

Keyboard shortcuts

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