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)
19
20// Principal is the authenticated identity of a caller: nobody, the root
21// token, or a stored token with its scopes and repositories.
22type Principal struct {
23 kind principalKind
24 tokenID string
25 scopes []Scope
26 repos []RepoName
27 expiresAt time.Time
28}
29
30// Anonymous returns the principal of a caller without credentials.
31func Anonymous() Principal { return Principal{kind: kindAnonymous} }
32
33// Root returns the principal of the admin token holder.
34func Root() Principal { return Principal{kind: kindRoot} }
35
36// TokenPrincipal returns the principal of the holder of a stored token.
37func TokenPrincipal(t Token) Principal {
38 return Principal{
39 kind: kindToken,
40 tokenID: t.ID,
41 scopes: slices.Clone(t.Scopes),
42 repos: slices.Clone(t.Repos),
43 expiresAt: t.ExpiresAt,
44 }
45}
46
47// Label names the kind of principal for logs and messages: "anonymous",
48// "root" or "token".
49func (p Principal) Label() string {
50 switch p.kind {
51 case kindRoot:
52 return "root"
53 case kindToken:
54 return "token"
55 }
56 return "anonymous"
57}
58
59// IsRoot reports whether p may do everything.
60func (p Principal) IsRoot() bool { return p.kind == kindRoot }
61
62// IsAnonymous reports whether p has no credentials.
63func (p Principal) IsAnonymous() bool { return p.kind == kindAnonymous }
64
65// TokenID returns the ID of the stored token, or "" for other principals.
66func (p Principal) TokenID() string { return p.tokenID }
67
68// Scopes returns the scopes of a stored token; nil for other principals.
69func (p Principal) Scopes() []Scope { return slices.Clone(p.scopes) }
70
71// Repos returns the repository list of a stored token; empty means every
72// repository.
73func (p Principal) Repos() []RepoName { return slices.Clone(p.repos) }
74
75// ExpiresAt returns the expiry of a stored token; zero means none.
76func (p Principal) ExpiresAt() time.Time { return p.expiresAt }
77
78// HasScope reports whether p holds the scope, for some repository. Root holds
79// every scope; anonymous none.
80func (p Principal) HasScope(s Scope) bool {
81 switch p.kind {
82 case kindRoot:
83 return true
84 case kindToken:
85 return slices.Contains(p.scopes, s)
86 }
87 return false
88}
89
90// Allows reports whether p holds the scope on the repository: the scope, and
91// the repository within the token's list when it has one.
92func (p Principal) Allows(s Scope, repo RepoName) bool {
93 if !p.HasScope(s) {
94 return false
95 }
96 if p.kind == kindToken && len(p.repos) > 0 {
97 return slices.Contains(p.repos, repo)
98 }
99 return true
100}