v0.14.0 815fb98

ci_token.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package domain
4
5import (
6	"maps"
7	"strings"
8	"time"
9)
10
11// RepoTokenPrefix starts the secret of a job repository token: the
12// "github.token" of a job, or the token of one action archive. Unlike the
13// stored tokens (TokenSecretPrefix) it is never created, listed or revoked by
14// a person: the CI engine issues it with a job and revokes it with the job.
15const RepoTokenPrefix = "ermitegh_"
16
17// IsRepoToken reports whether s has the shape of a job repository token.
18func IsRepoToken(s string) bool { return hasTokenShape(s, RepoTokenPrefix) }
19
20// repoTokenIDPrefix starts the TokenID of the principal of a job repository
21// token, so that it never collides with the ID of a stored token.
22const repoTokenIDPrefix = "job:"
23
24// IsRepoTokenID reports whether id is the TokenID of the principal of a job
25// repository token (see RepoTokenPrincipal).
26func IsRepoTokenID(id string) bool { return strings.HasPrefix(id, repoTokenIDPrefix) }
27
28// CIRepoToken is a job repository token: it reads one repository, until
29// ExpiresAt or the end of its job. The secret is never stored, only its hash.
30type CIRepoToken struct {
31	// ID identifies the token; it is not secret.
32	ID   string
33	Hash TokenHash
34	// Repo is the one repository the token reads.
35	Repo RepoName
36	// RunID, RunAttempt and JobID name the job it belongs to.
37	RunID      int64
38	RunAttempt int32
39	JobID      string
40	// Archive is set for the token of an action archive, unset for the job's
41	// own token ("github.token"). An archive token downloads the commit Sha of
42	// Repo and nothing else.
43	Archive bool
44	// Sha is the commit an archive token is bound to; empty for the job's own.
45	Sha string
46	// Permissions are the permissions of the job's own token (key to level, see
47	// TokenPermissions). Empty: the default, which reads the repository.
48	Permissions map[string]string
49	ExpiresAt   time.Time
50	CreatedAt   time.Time
51}
52
53// Expired reports whether the token is no longer valid at now.
54func (t CIRepoToken) Expired(now time.Time) bool { return !now.Before(t.ExpiresAt) }
55
56// RepoTokenGrace is how long a job repository token outlives the deadline of
57// its job at the most (a safety net: it is revoked when the job ends).
58const RepoTokenGrace = 5 * time.Minute
59
60// RepoTokenPrincipal returns the principal of the holder of a job repository
61// token.
62//
63// The job's own token is shaped like a stored token (scopes on one repository,
64// an expiry), so that every authorization rule of the forge applies to it
65// unchanged. Its scopes follow its permissions (RepoTokenScopes): by default it
66// reads that repository, even when private, and writes nothing; with
67// contents: write it also pushes to it. It has no branch prefixes: a run whose
68// actor is limited to branches is not trusted and holds no write permission.
69//
70// An archive token holds no scope at all: it is the grant to download one
71// commit of one repository as an archive, which only the archive endpoint
72// reads (Principal.ArchiveGrant). Git over HTTP, the repository endpoint and
73// the rest of the API see a caller with no rights, so a private repository
74// does not show itself to it.
75//
76// Both have a TokenID that starts with "job:" and matches no stored token.
77func RepoTokenPrincipal(t CIRepoToken) Principal {
78	if t.Archive {
79		return Principal{
80			kind: kindArchive, tokenID: repoTokenIDPrefix + t.ID, label: "ci archive", expiresAt: t.ExpiresAt,
81			archiveRepo: t.Repo, archiveSha: t.Sha,
82		}
83	}
84	p := TokenPrincipal(Token{
85		ID: repoTokenIDPrefix + t.ID, Label: "ci job", Scopes: RepoTokenScopes(t.Permissions),
86		Repos: []RepoName{t.Repo}, ExpiresAt: t.ExpiresAt, CreatedAt: t.CreatedAt,
87	})
88	p.jobPerms = maps.Clone(t.Permissions)
89	if len(p.jobPerms) == 0 {
90		p.jobPerms = DefaultTokenPermissions()
91	}
92	return p
93}
94
95// CIPusherKind tells who pushed the commit of a run.
96type CIPusherKind string
97
98// Pusher kinds. The zero value is a run stored before the pusher was recorded.
99const (
100	CIPusherNone  CIPusherKind = ""
101	CIPusherRoot  CIPusherKind = "root"
102	CIPusherOwner CIPusherKind = "owner"
103	CIPusherToken CIPusherKind = "token"
104)
105
106// CIPusher is who pushed the commit of a run, as recorded with the run. A
107// token is recorded by its ID only: what it may read is looked up when it is
108// needed, so a token revoked since no longer reads anything on behalf of the
109// run.
110type CIPusher struct {
111	Kind    CIPusherKind
112	TokenID string
113}
114
115// PusherOf records the pusher p. Anonymous callers, which cannot push, and the
116// holders of job tokens, which cannot either, are recorded as nobody.
117func PusherOf(p Principal) CIPusher {
118	switch {
119	case p.IsOwner():
120		return CIPusher{Kind: CIPusherOwner}
121	case p.IsRoot():
122		return CIPusher{Kind: CIPusherRoot}
123	case p.TokenID() != "" && !IsRepoTokenID(p.TokenID()):
124		return CIPusher{Kind: CIPusherToken, TokenID: p.TokenID()}
125	}
126	return CIPusher{}
127}
128
129// CIActionSource says where an action was found.
130type CIActionSource int
131
132// Action sources, with the numbers of ActionSource in the protocol.
133const (
134	CIActionGitHub CIActionSource = 1
135	CIActionErmite CIActionSource = 2
136)
137
138// CIActionResolution is the resolution of an action reference for one run
139// attempt: every job of the attempt gets the same commit.
140type CIActionResolution struct {
141	RunID      int64
142	RunAttempt int32
143	// Key is "owner/repo@ref", lower-cased.
144	Key           string
145	NameWithOwner string
146	ResolvedSHA   string
147	Source        CIActionSource
148}

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 44

↑↓ moveenter openesc close