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