Go · 3552 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 // Package policy is the single source of truth for "who can do what".
4 // Every authorization decision in shithub flows through Can(); handlers
5 // must not read ownership, visibility, or collaborator state inline.
6 //
7 // The package shape:
8 //
9 // Actor — who is asking (anonymous, suspended, site-admin etc.)
10 // Resource — what they want to act on (a RepoRef today; org/team later)
11 // Action — a constant from the registry below
12 // Can(...) — the only public decision function
13 // Decision — { Allow bool; Reason string }
14 //
15 // Reasons are for logs and admin debugging, never for end-user error
16 // strings — they can leak existence.
17 package policy
18
19 // Action is a constant identifier for an operation against some resource.
20 // Adding a new action: add the const, register a default rule in
21 // policy.Can, and extend the test matrix. The matrix test asserts that
22 // every Action has explicit coverage for every Actor archetype.
23 type Action string
24
25 // Repo-level actions.
26 const (
27 ActionRepoRead Action = "repo:read"
28 ActionRepoWrite Action = "repo:write"
29 ActionRepoAdmin Action = "repo:admin"
30
31 ActionRepoSettingsGeneral Action = "repo:settings:general"
32 ActionRepoSettingsCollaborators Action = "repo:settings:collaborators"
33 ActionRepoSettingsBranches Action = "repo:settings:branches"
34
35 ActionRepoArchive Action = "repo:archive"
36 ActionRepoDelete Action = "repo:delete"
37 ActionRepoTransfer Action = "repo:transfer"
38 ActionRepoVisibility Action = "repo:visibility"
39 )
40
41 // Issue-level actions. (Issue resources arrive in S18; S15 just ships
42 // the registry entries so the matrix is exhaustive from day one.)
43 const (
44 ActionIssueRead Action = "issue:read"
45 ActionIssueCreate Action = "issue:create"
46 ActionIssueComment Action = "issue:comment"
47 ActionIssueClose Action = "issue:close"
48 ActionIssueLabel Action = "issue:label"
49 ActionIssueAssign Action = "issue:assign"
50 )
51
52 // Pull-request actions. (Pull resources arrive in S19; same note as above.)
53 const (
54 ActionPullRead Action = "pull:read"
55 ActionPullCreate Action = "pull:create"
56 ActionPullMerge Action = "pull:merge"
57 ActionPullReview Action = "pull:review"
58 ActionPullClose Action = "pull:close"
59 )
60
61 // Per-user social actions.
62 const (
63 ActionStarCreate Action = "star:create"
64 ActionForkCreate Action = "fork:create"
65 )
66
67 // AllActions is the canonical list. The matrix test iterates this so a
68 // new Action that's not registered above will fail coverage and force
69 // the author to think through every actor archetype.
70 var AllActions = []Action{
71 ActionRepoRead, ActionRepoWrite, ActionRepoAdmin,
72 ActionRepoSettingsGeneral, ActionRepoSettingsCollaborators, ActionRepoSettingsBranches,
73 ActionRepoArchive, ActionRepoDelete, ActionRepoTransfer, ActionRepoVisibility,
74 ActionIssueRead, ActionIssueCreate, ActionIssueComment, ActionIssueClose, ActionIssueLabel, ActionIssueAssign,
75 ActionPullRead, ActionPullCreate, ActionPullMerge, ActionPullReview, ActionPullClose,
76 ActionStarCreate, ActionForkCreate,
77 }
78
79 // isWriteAction returns true when the action mutates state. Used by the
80 // suspended-user gate (suspended accounts can read but not write).
81 func isWriteAction(a Action) bool {
82 switch a {
83 case ActionRepoRead, ActionIssueRead, ActionPullRead:
84 return false
85 default:
86 return true
87 }
88 }
89
90 // isReadAction is the inverse, broken out for readability at call sites
91 // that branch on intent rather than on the absence of writes.
92 func isReadAction(a Action) bool { return !isWriteAction(a) }
93