Documentation
¶
Overview ¶
Package projectmember is the durable per-project member list: who besides a project's owner may reach it, and at what role (Task 20366).
It is the split pkg/rolestore and pkg/sessionstore already use: pkg/authz owns the role model and stays stdlib-only, pkg/statedb owns the table, and this package owns normalisation and the cache that makes reading the list on every authorization decision affordable.
What a membership is ¶
An additive grant scoped to one project: "this identity is also admitted to this path, at this role". The hub unions it with whatever the identity already holds (authz.Union), so sharing a project can never demote anybody. It is also never more than the granter's own authority on that project — the REST layer enforces that cap, because this package does not know who is asking.
Freshness ¶
Reads are served from an in-memory snapshot refreshed every TTL, so a write another process made (the CLI, another hub member) lands within one TTL. Writes through a Store land at once: the Store reloads — or, should the database refuse the read, patches its snapshot with the change it just committed — before returning, so "I removed them" holds on the very next request. Invalidate makes the next read reload; pkg/ui calls it when another hub member announces a change on the cluster bus. Every reload that finds the list changed is reported to the OnChange callback, which is how a hub closes the live streams of somebody who just lost access.
Failure policy ¶
A refresh that fails keeps serving the last good snapshot. The table only grants, so an empty answer would be the "safe" direction, but it would also revoke every collaborator for as long as a storage blip lasted. The first load happens in New and its failure is fatal to hub startup, so the degraded case is bounded by a snapshot that was known-good once. A row whose role this build cannot parse is skipped and reported, never fatal: one unreadable row must not un-share every project on the hub.
Index ¶
- Constants
- func KeyFor(email, sub string) string
- func NormalizeKey(key string) string
- func NormalizePath(path string) string
- func ParseGrantableRole(raw string) (authz.Role, error)
- func ParseKey(raw string) (string, error)
- type Audit
- type Change
- type Member
- type Option
- type Store
- func (s *Store) All() []Member
- func (s *Store) Any() bool
- func (s *Store) Grant(m Member, audit Audit) (Member, *Member, error)
- func (s *Store) Invalidate()
- func (s *Store) Lookup(projectPath, identityKey string) (Member, bool)
- func (s *Store) MembersOf(projectPath string) []Member
- func (s *Store) ProjectsOf(identityKey string) []Member
- func (s *Store) Refresh() error
- func (s *Store) Revoke(projectPath, identityKey string, audit Audit) (*Member, error)
- func (s *Store) RevokeProject(projectPath string, audit Audit) ([]Member, error)
- func (s *Store) RoleFor(projectPath, identityKey string) (authz.Role, bool)
Constants ¶
const DefaultTTL = 10 * time.Second
DefaultTTL bounds how long a membership written by another process takes to reach this one's authorization decisions.
const MaxKeyLen = 320
MaxKeyLen bounds an identity key. RFC 5321 caps an address at 254 octets; the rest is room for a "sub:" prefix on a long opaque subject.
const MaxReasonLen = 500
MaxReasonLen bounds the free-text reason stored with a membership.
Variables ¶
This section is empty.
Functions ¶
func KeyFor ¶
KeyFor derives the membership key for an identity's claims, exactly as oidcauth.Identity.OwnerKey does: the lowercased email, or "sub:" and the subject when the identity provider released no email. "" when it has neither. One definition, because the key is the namespace project owners and members share, and a second spelling would match nobody.
func NormalizeKey ¶
NormalizeKey canonicalises an identity key without judging it: emails are lowercased, because an identity provider may release a casing the operator did not type; a "sub:" key keeps its subject's case, because a subject is an opaque identifier and two differing only in case are two people.
func NormalizePath ¶
NormalizePath canonicalises a project path so two spellings of one directory are one membership: absolute and cleaned, but deliberately not symlink-resolved, because the registry and authz.Scope.ProjectPath carry the path as registered and a resolved key would match neither.
func ParseGrantableRole ¶
ParseGrantableRole parses a role a membership may carry: any rung of the ladder above "none". A membership granting nothing is a revocation in a membership's clothes, and would list somebody on a project they cannot open.
Types ¶
type Audit ¶
type Audit func(prev, next *Member) ([]*statedb.AuditEvent, error)
Audit builds the audit events committed together with a change; prev and next are nil when there was no membership before or after. See statedb.MemberAudit.
type Change ¶
type Change struct {
// Projects are the paths whose roster changed.
Projects []string
// Identities are the keys whose memberships changed.
Identities []string
}
Change is what a reload found different from the snapshot before it.
type Member ¶
type Member struct {
// ProjectPath is the project's normalised absolute path.
ProjectPath string `json:"project_path"`
// IdentityKey is the grantee in oidcauth.Identity.OwnerKey's namespace.
IdentityKey string `json:"identity"`
// Role is what the grantee may do on the project.
Role authz.Role `json:"role"`
// Reason is why they were admitted; may be empty.
Reason string `json:"reason,omitempty"`
// GrantedAt and GrantedBy record the most recent write.
GrantedAt time.Time `json:"granted_at"`
GrantedBy string `json:"granted_by,omitempty"`
}
Member is one membership as the rest of the hub sees it.
type Option ¶
type Option func(*Store)
Option configures a Store.
func WithErrorHandler ¶
WithErrorHandler installs a sink for failed refreshes and skipped rows.
func WithOnChange ¶
WithOnChange installs the callback a reload calls, on its own goroutine, when the list changed.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store reads memberships through a TTL cache.
func New ¶
New adapts a control-plane database and performs the first load.
The database must be the hub's own, never a managed project's: a tenant able to write the file holding this table could admit itself to every project on the hub with one INSERT.
func (*Store) Any ¶
Any reports whether any project on the hub has been shared. Served from the cache, so a hub that never shared anything pays one slice length per check.
func (*Store) Grant ¶
Grant records or replaces a membership and returns it as stored, along with the membership it replaced (nil for a new member). audit builds the rows committed with it. The grant is in force for this process when Grant returns.
func (*Store) Invalidate ¶
func (s *Store) Invalidate()
Invalidate makes the next read reload from the database.
func (*Store) ProjectsOf ¶
ProjectsOf returns identityKey's memberships ordered by project.
func (*Store) Refresh ¶
Refresh reloads now, whatever the TTL says, and reports a failure. The change it finds, if any, goes to the OnChange callback.
func (*Store) Revoke ¶
Revoke removes a membership and returns it, or nil when there was none. The removal is in force for this process when Revoke returns.
func (*Store) RevokeProject ¶
RevokeProject removes every membership on a project that is leaving the hub.