Go · 4228 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 // S26 social actions. WatchSet covers both setting an explicit
67 // level and unsetting (deleting the row). Both require a logged-in
68 // user with read access to the repo — the policy.Can engine
69 // enforces visibility before reaching the role check, so a
70 // non-collab on a private repo deny-leaks as 404.
71 ActionWatchSet Action = "watch:set"
72 )
73
74 // AllActions is the canonical list. The matrix test iterates this so a
75 // new Action that's not registered above will fail coverage and force
76 // the author to think through every actor archetype.
77 var AllActions = []Action{
78 ActionRepoRead, ActionRepoWrite, ActionRepoAdmin,
79 ActionRepoSettingsGeneral, ActionRepoSettingsCollaborators, ActionRepoSettingsBranches,
80 ActionRepoArchive, ActionRepoDelete, ActionRepoTransfer, ActionRepoVisibility,
81 ActionIssueRead, ActionIssueCreate, ActionIssueComment, ActionIssueClose, ActionIssueLabel, ActionIssueAssign,
82 ActionPullRead, ActionPullCreate, ActionPullMerge, ActionPullReview, ActionPullClose,
83 ActionStarCreate, ActionForkCreate,
84 ActionWatchSet,
85 }
86
87 // isWriteAction returns true when the action mutates state. Used by the
88 // suspended-user gate (suspended accounts can read but not write).
89 func isWriteAction(a Action) bool {
90 switch a {
91 case ActionRepoRead, ActionIssueRead, ActionPullRead:
92 return false
93 default:
94 return true
95 }
96 }
97
98 // isReadAction is the inverse, broken out for readability at call sites
99 // that branch on intent rather than on the absence of writes.
100 func isReadAction(a Action) bool { return !isWriteAction(a) }
101
102 // isIssueParticipationAction is the GitHub-shaped issue conversation
103 // surface: any logged-in user can open or comment on issues in a public
104 // repo, while private repos require normal read access.
105 func isIssueParticipationAction(a Action) bool {
106 return a == ActionIssueCreate || a == ActionIssueComment
107 }
108