Skip to content

Commit 8221de2

Browse files
committed
Add required models/actions/permissions file
Not sure how this got dropped!
1 parent 74ace80 commit 8221de2

File tree

1 file changed

+217
-0
lines changed

1 file changed

+217
-0
lines changed

models/actions/permissions.go

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package actions
5+
6+
import (
7+
"errors"
8+
"fmt"
9+
10+
"gopkg.in/yaml.v3"
11+
)
12+
13+
type Permission int
14+
15+
const (
16+
PermissionUnspecified Permission = iota
17+
PermissionNone
18+
PermissionRead
19+
PermissionWrite
20+
)
21+
22+
// Per https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idpermissions
23+
type Permissions struct {
24+
Actions Permission `yaml:"actions"`
25+
Checks Permission `yaml:"checks"`
26+
Contents Permission `yaml:"contents"`
27+
Deployments Permission `yaml:"deployments"`
28+
IDToken Permission `yaml:"id-token"`
29+
Issues Permission `yaml:"issues"`
30+
Discussions Permission `yaml:"discussions"`
31+
Packages Permission `yaml:"packages"`
32+
Pages Permission `yaml:"pages"`
33+
PullRequests Permission `yaml:"pull-requests"`
34+
RepositoryProjects Permission `yaml:"repository-projects"`
35+
SecurityEvents Permission `yaml:"security-events"`
36+
Statuses Permission `yaml:"statuses"`
37+
}
38+
39+
// WorkflowPermissions parses a workflow and returns
40+
// a Permissions struct representing the permissions set
41+
// at the workflow (i.e. file) level
42+
func WorkflowPermissions(contents []byte) (Permissions, error) {
43+
p := struct {
44+
Permissions Permissions `yaml:"permissions"`
45+
}{}
46+
err := yaml.Unmarshal(contents, &p)
47+
return p.Permissions, err
48+
}
49+
50+
// Given the contents of a workflow, JobPermissions
51+
// returns a Permissions object representing the permissions
52+
// of THE FIRST job in the file.
53+
func JobPermissions(contents []byte) (Permissions, error) {
54+
p := struct {
55+
Jobs []struct {
56+
Permissions Permissions `yaml:"permissions"`
57+
} `yaml:"jobs"`
58+
}{}
59+
err := yaml.Unmarshal(contents, &p)
60+
if len(p.Jobs) > 0 {
61+
return p.Jobs[0].Permissions, err
62+
}
63+
return Permissions{}, errors.New("no jobs detected in workflow")
64+
}
65+
66+
func (p *Permission) UnmarshalYAML(unmarshal func(interface{}) error) error {
67+
var data string
68+
if err := unmarshal(&data); err != nil {
69+
return err
70+
}
71+
72+
switch data {
73+
case "none":
74+
*p = PermissionNone
75+
case "read":
76+
*p = PermissionRead
77+
case "write":
78+
*p = PermissionWrite
79+
default:
80+
return fmt.Errorf("invalid permission: %s", data)
81+
}
82+
83+
return nil
84+
}
85+
86+
// DefaultAccessPermissive is the default "permissive" set granted to actions on repositories
87+
// per https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token
88+
// That page also lists a "metadata" permission that I can't find mentioned anywhere else.
89+
// However, it seems to always have "read" permission, so it doesn't really matter.
90+
// Interestingly, it doesn't list "Discussions", so we assume "write" for permissive and "none" for restricted.
91+
var DefaultAccessPermissive = Permissions{
92+
Actions: PermissionWrite,
93+
Checks: PermissionWrite,
94+
Contents: PermissionWrite,
95+
Deployments: PermissionWrite,
96+
IDToken: PermissionNone,
97+
Issues: PermissionWrite,
98+
Discussions: PermissionWrite,
99+
Packages: PermissionWrite,
100+
Pages: PermissionWrite,
101+
PullRequests: PermissionWrite,
102+
RepositoryProjects: PermissionWrite,
103+
SecurityEvents: PermissionWrite,
104+
Statuses: PermissionWrite,
105+
}
106+
107+
// DefaultAccessRestricted is the default "restrictive" set granted. See docs for
108+
// DefaultAccessPermissive above.
109+
//
110+
// This is not currently used, since Gitea does not have a permissive/restricted setting.
111+
var DefaultAccessRestricted = Permissions{
112+
Actions: PermissionNone,
113+
Checks: PermissionNone,
114+
Contents: PermissionWrite,
115+
Deployments: PermissionNone,
116+
IDToken: PermissionNone,
117+
Issues: PermissionNone,
118+
Discussions: PermissionNone,
119+
Packages: PermissionRead,
120+
Pages: PermissionNone,
121+
PullRequests: PermissionNone,
122+
RepositoryProjects: PermissionNone,
123+
SecurityEvents: PermissionNone,
124+
Statuses: PermissionNone,
125+
}
126+
127+
var ReadAllPermissions = Permissions{
128+
Actions: PermissionRead,
129+
Checks: PermissionRead,
130+
Contents: PermissionRead,
131+
Deployments: PermissionRead,
132+
IDToken: PermissionRead,
133+
Issues: PermissionRead,
134+
Discussions: PermissionRead,
135+
Packages: PermissionRead,
136+
Pages: PermissionRead,
137+
PullRequests: PermissionRead,
138+
RepositoryProjects: PermissionRead,
139+
SecurityEvents: PermissionRead,
140+
Statuses: PermissionRead,
141+
}
142+
143+
var WriteAllPermissions = Permissions{
144+
Actions: PermissionWrite,
145+
Checks: PermissionWrite,
146+
Contents: PermissionWrite,
147+
Deployments: PermissionWrite,
148+
IDToken: PermissionWrite,
149+
Issues: PermissionWrite,
150+
Discussions: PermissionWrite,
151+
Packages: PermissionWrite,
152+
Pages: PermissionWrite,
153+
PullRequests: PermissionWrite,
154+
RepositoryProjects: PermissionWrite,
155+
SecurityEvents: PermissionWrite,
156+
Statuses: PermissionWrite,
157+
}
158+
159+
// FromYAML takes a yaml.Node representing a permissions
160+
// definition and parses it into a Permissions struct
161+
func (p *Permissions) FromYAML(rawPermissions *yaml.Node) error {
162+
switch rawPermissions.Kind {
163+
case yaml.ScalarNode:
164+
var val string
165+
err := rawPermissions.Decode(&val)
166+
if err != nil {
167+
return err
168+
}
169+
if val == "read-all" {
170+
*p = ReadAllPermissions
171+
}
172+
if val == "write-all" {
173+
*p = WriteAllPermissions
174+
}
175+
return fmt.Errorf("unexpected `permissions` value: %v", rawPermissions)
176+
case yaml.MappingNode:
177+
var perms Permissions
178+
err := rawPermissions.Decode(&perms)
179+
if err != nil {
180+
return err
181+
}
182+
return nil
183+
case 0:
184+
*p = Permissions{}
185+
return nil
186+
default:
187+
return fmt.Errorf("invalid permissions value: %v", rawPermissions)
188+
}
189+
}
190+
191+
func merge[T comparable](a, b T) T {
192+
var zero T
193+
if a == zero {
194+
return b
195+
}
196+
return a
197+
}
198+
199+
// Merge merges two Permission values
200+
//
201+
// Already set values take precedence over `other`.
202+
// I.e. you want to call jobLevel.Permissions.Merge(topLevel.Permissions)
203+
func (p *Permissions) Merge(other Permissions) {
204+
p.Actions = merge(p.Actions, other.Actions)
205+
p.Checks = merge(p.Checks, other.Checks)
206+
p.Contents = merge(p.Contents, other.Contents)
207+
p.Deployments = merge(p.Deployments, other.Deployments)
208+
p.IDToken = merge(p.IDToken, other.IDToken)
209+
p.Issues = merge(p.Issues, other.Issues)
210+
p.Discussions = merge(p.Discussions, other.Discussions)
211+
p.Packages = merge(p.Packages, other.Packages)
212+
p.Pages = merge(p.Pages, other.Pages)
213+
p.PullRequests = merge(p.PullRequests, other.PullRequests)
214+
p.RepositoryProjects = merge(p.RepositoryProjects, other.RepositoryProjects)
215+
p.SecurityEvents = merge(p.SecurityEvents, other.SecurityEvents)
216+
p.Statuses = merge(p.Statuses, other.Statuses)
217+
}

0 commit comments

Comments
 (0)