principal.go RawHistoryFolder
1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package domain
4
5import (
6 "slices"
7 "time"
8)
9
10// principalKind tells who a caller is.
11type principalKind uint8
12
13// Principal kinds. The zero value is anonymous, the least privileged.
14const (
15 kindAnonymous principalKind = iota
16 kindRoot
17 kindToken
18 kindOwner
19)
20
21// Principal is the authenticated identity of a caller: nobody, the root
22// token, the owner signed in on the web, or a stored token with its scopes
23// and repositories.
24type Principal struct {
25 kind principalKind
26 tokenID string
27 scopes []Scope
28 repos []RepoName
29 expiresAt time.Time
30 label string
31}
32
33// Anonymous returns the principal of a caller without credentials.
34func Anonymous() Principal { return Principal{kind: kindAnonymous} }
35
36// Root returns the principal of the admin token holder.
37func Root() Principal { return Principal{kind: kindRoot} }
38
39// Owner returns the principal of the owner's web session (ADR 0018): the
40// rights of root, labelled "owner" in logs and messages. Only the web
41// middleware creates it, for a valid session cookie; no secret maps to it.
42func Owner() Principal { return Principal{kind: kindOwner} }
43
44// TokenPrincipal returns the principal of the holder of a stored token.
45func TokenPrincipal(t Token) Principal {
46 return Principal{
47 kind: kindToken,
48 tokenID: t.ID,
49 scopes: slices.Clone(t.Scopes),
50 repos: slices.Clone(t.Repos),
51 expiresAt: t.ExpiresAt,
52 label: t.Label,
53 }
54}
55
56// Label names the kind of principal for logs and messages: "anonymous",
57// "root", "owner" or "token".
58func (p Principal) Label() string {
59 switch p.kind {
60 case kindRoot:
61 return "root"
62 case kindOwner:
63 return "owner"
64 case kindToken:
65 return "token"
66 }
67 return "anonymous"
68}
69
70// IsRoot reports whether p may do everything: the root token, or the owner
71// signed in on the web.
72func (p Principal) IsRoot() bool { return p.kind == kindRoot || p.kind == kindOwner }
73
74// HasOwnerRights reports whether p is the owner: root, the owner's web
75// session, or a stored token that holds every scope and is not limited to a
76// list of repositories. It decides who may manage browser sessions.
77func (p Principal) HasOwnerRights() bool {
78 if p.IsRoot() {
79 return true
80 }
81 if p.kind != kindToken || len(p.repos) > 0 {
82 return false
83 }
84 for _, s := range AllScopes() {
85 if !slices.Contains(p.scopes, s) {
86 return false
87 }
88 }
89 return true
90}
91
92// IsOwner reports whether p is the owner signed in on the web.
93func (p Principal) IsOwner() bool { return p.kind == kindOwner }
94
95// IsAnonymous reports whether p has no credentials.
96func (p Principal) IsAnonymous() bool { return p.kind == kindAnonymous }
97
98// TokenID returns the ID of the stored token, or "" for other principals.
99func (p Principal) TokenID() string { return p.tokenID }
100
101// Scopes returns the scopes of a stored token; nil for other principals.
102func (p Principal) Scopes() []Scope { return slices.Clone(p.scopes) }
103
104// Repos returns the repository list of a stored token; empty means every
105// repository.
106func (p Principal) Repos() []RepoName { return slices.Clone(p.repos) }
107
108// ExpiresAt returns the expiry of a stored token; zero means none.
109func (p Principal) ExpiresAt() time.Time { return p.expiresAt }
110
111// HasScope reports whether p holds the scope, for some repository. Root and
112// the owner hold every scope; anonymous none.
113func (p Principal) HasScope(s Scope) bool {
114 switch p.kind {
115 case kindRoot, kindOwner:
116 return true
117 case kindToken:
118 return slices.Contains(p.scopes, s)
119 }
120 return false
121}
122
123// Allows reports whether p holds the scope on the repository: the scope, and
124// the repository within the token's list when it has one.
125func (p Principal) Allows(s Scope, repo RepoName) bool {
126 if !p.HasScope(s) {
127 return false
128 }
129 if p.kind == kindToken && len(p.repos) > 0 {
130 return slices.Contains(p.repos, repo)
131 }
132 return true
133}
134
135// TokenLabel returns the label of the stored token, or "" for other
136// principals.
137func (p Principal) TokenLabel() string { return p.label }