Go · 4321 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 ActionRepoSettingsActions Action = "repo:settings:actions"
35
36 ActionRepoArchive Action = "repo:archive"
37 ActionRepoDelete Action = "repo:delete"
38 ActionRepoTransfer Action = "repo:transfer"
39 ActionRepoVisibility Action = "repo:visibility"
40 )
41
42 // Issue-level actions. (Issue resources arrive in S18; S15 just ships
43 // the registry entries so the matrix is exhaustive from day one.)
44 const (
45 ActionIssueRead Action = "issue:read"
46 ActionIssueCreate Action = "issue:create"
47 ActionIssueComment Action = "issue:comment"
48 ActionIssueClose Action = "issue:close"
49 ActionIssueLabel Action = "issue:label"
50 ActionIssueAssign Action = "issue:assign"
51 )
52
53 // Pull-request actions. (Pull resources arrive in S19; same note as above.)
54 const (
55 ActionPullRead Action = "pull:read"
56 ActionPullCreate Action = "pull:create"
57 ActionPullMerge Action = "pull:merge"
58 ActionPullReview Action = "pull:review"
59 ActionPullClose Action = "pull:close"
60 )
61
62 // Per-user social actions.
63 const (
64 ActionStarCreate Action = "star:create"
65 ActionForkCreate Action = "fork:create"
66
67 // S26 social actions. WatchSet covers both setting an explicit
68 // level and unsetting (deleting the row). Both require a logged-in
69 // user with read access to the repo — the policy.Can engine
70 // enforces visibility before reaching the role check, so a
71 // non-collab on a private repo deny-leaks as 404.
72 ActionWatchSet Action = "watch:set"
73 )
74
75 // AllActions is the canonical list. The matrix test iterates this so a
76 // new Action that's not registered above will fail coverage and force
77 // the author to think through every actor archetype.
78 var AllActions = []Action{
79 ActionRepoRead, ActionRepoWrite, ActionRepoAdmin,
80 ActionRepoSettingsGeneral, ActionRepoSettingsCollaborators, ActionRepoSettingsBranches, ActionRepoSettingsActions,
81 ActionRepoArchive, ActionRepoDelete, ActionRepoTransfer, ActionRepoVisibility,
82 ActionIssueRead, ActionIssueCreate, ActionIssueComment, ActionIssueClose, ActionIssueLabel, ActionIssueAssign,
83 ActionPullRead, ActionPullCreate, ActionPullMerge, ActionPullReview, ActionPullClose,
84 ActionStarCreate, ActionForkCreate,
85 ActionWatchSet,
86 }
87
88 // isWriteAction returns true when the action mutates state. Used by the
89 // suspended-user gate (suspended accounts can read but not write).
90 func isWriteAction(a Action) bool {
91 switch a {
92 case ActionRepoRead, ActionIssueRead, ActionPullRead:
93 return false
94 default:
95 return true
96 }
97 }
98
99 // isReadAction is the inverse, broken out for readability at call sites
100 // that branch on intent rather than on the absence of writes.
101 func isReadAction(a Action) bool { return !isWriteAction(a) }
102
103 // isIssueParticipationAction is the GitHub-shaped issue conversation
104 // surface: any logged-in user can open or comment on issues in a public
105 // repo, while private repos require normal read access.
106 func isIssueParticipationAction(a Action) bool {
107 return a == ActionIssueCreate || a == ActionIssueComment
108 }
109