v0.3.0 9fdf386

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 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 43

↑↓ moveenter openesc close