-
-
Notifications
You must be signed in to change notification settings - Fork 5.9k
OIDC provider #33945
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
OIDC provider #33945
Changes from 13 commits
3a16680
31e8ad3
8271be5
724e138
fc80a8a
e192676
69ce797
66dca88
747dcc1
32e618a
647bdf5
0625616
f66c4b9
3cde888
6395824
828e2d5
3ac43c6
74ace80
8221de2
50c2a21
95c31fb
8f02684
236745d
c028254
d77b250
f014369
f952190
7b98be8
702f640
c477e0e
7094798
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
// Copyright 2025 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package actions | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
|
||
"gopkg.in/yaml.v3" | ||
) | ||
|
||
type Permission int | ||
|
||
const ( | ||
PermissionUnspecified Permission = iota | ||
PermissionNone | ||
PermissionRead | ||
PermissionWrite | ||
) | ||
|
||
// Per https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idpermissions | ||
type Permissions struct { | ||
Actions Permission `yaml:"actions"` | ||
Checks Permission `yaml:"checks"` | ||
Contents Permission `yaml:"contents"` | ||
Deployments Permission `yaml:"deployments"` | ||
IDToken Permission `yaml:"id-token"` | ||
Issues Permission `yaml:"issues"` | ||
Discussions Permission `yaml:"discussions"` | ||
Packages Permission `yaml:"packages"` | ||
Pages Permission `yaml:"pages"` | ||
PullRequests Permission `yaml:"pull-requests"` | ||
RepositoryProjects Permission `yaml:"repository-projects"` | ||
SecurityEvents Permission `yaml:"security-events"` | ||
Statuses Permission `yaml:"statuses"` | ||
} | ||
|
||
// WorkflowPermissions parses a workflow and returns | ||
// a Permissions struct representing the permissions set | ||
// at the workflow (i.e. file) level | ||
func WorkflowPermissions(contents []byte) (Permissions, error) { | ||
p := struct { | ||
Permissions Permissions `yaml:"permissions"` | ||
}{} | ||
err := yaml.Unmarshal(contents, &p) | ||
return p.Permissions, err | ||
} | ||
|
||
// Given the contents of a workflow, JobPermissions | ||
// returns a Permissions object representing the permissions | ||
// of THE FIRST job in the file. | ||
func JobPermissions(contents []byte) (Permissions, error) { | ||
p := struct { | ||
Jobs []struct { | ||
Permissions Permissions `yaml:"permissions"` | ||
} `yaml:"jobs"` | ||
}{} | ||
err := yaml.Unmarshal(contents, &p) | ||
if len(p.Jobs) > 0 { | ||
return p.Jobs[0].Permissions, err | ||
} | ||
return Permissions{}, errors.New("no jobs detected in workflow") | ||
} | ||
|
||
func (p *Permission) UnmarshalYAML(unmarshal func(interface{}) error) error { | ||
var data string | ||
if err := unmarshal(&data); err != nil { | ||
return err | ||
} | ||
|
||
switch data { | ||
case "none": | ||
*p = PermissionNone | ||
case "read": | ||
*p = PermissionRead | ||
case "write": | ||
*p = PermissionWrite | ||
default: | ||
return fmt.Errorf("invalid permission: %s", data) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// DefaultAccessPermissive is the default "permissive" set granted to actions on repositories | ||
// per https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token | ||
// That page also lists a "metadata" permission that I can't find mentioned anywhere else. | ||
// However, it seems to always have "read" permission, so it doesn't really matter. | ||
// Interestingly, it doesn't list "Discussions", so we assume "write" for permissive and "none" for restricted. | ||
var DefaultAccessPermissive = Permissions{ | ||
Actions: PermissionWrite, | ||
Checks: PermissionWrite, | ||
Contents: PermissionWrite, | ||
Deployments: PermissionWrite, | ||
IDToken: PermissionNone, | ||
Issues: PermissionWrite, | ||
Discussions: PermissionWrite, | ||
Packages: PermissionWrite, | ||
Pages: PermissionWrite, | ||
PullRequests: PermissionWrite, | ||
RepositoryProjects: PermissionWrite, | ||
SecurityEvents: PermissionWrite, | ||
Statuses: PermissionWrite, | ||
} | ||
|
||
// DefaultAccessRestricted is the default "restrictive" set granted. See docs for | ||
// DefaultAccessPermissive above. | ||
// | ||
// This is not currently used, since Gitea does not have a permissive/restricted setting. | ||
var DefaultAccessRestricted = Permissions{ | ||
Actions: PermissionNone, | ||
Checks: PermissionNone, | ||
Contents: PermissionWrite, | ||
Deployments: PermissionNone, | ||
IDToken: PermissionNone, | ||
Issues: PermissionNone, | ||
Discussions: PermissionNone, | ||
Packages: PermissionRead, | ||
Pages: PermissionNone, | ||
PullRequests: PermissionNone, | ||
RepositoryProjects: PermissionNone, | ||
SecurityEvents: PermissionNone, | ||
Statuses: PermissionNone, | ||
} | ||
|
||
var ReadAllPermissions = Permissions{ | ||
Actions: PermissionRead, | ||
Checks: PermissionRead, | ||
Contents: PermissionRead, | ||
Deployments: PermissionRead, | ||
IDToken: PermissionRead, | ||
Issues: PermissionRead, | ||
Discussions: PermissionRead, | ||
Packages: PermissionRead, | ||
Pages: PermissionRead, | ||
PullRequests: PermissionRead, | ||
RepositoryProjects: PermissionRead, | ||
SecurityEvents: PermissionRead, | ||
Statuses: PermissionRead, | ||
} | ||
|
||
var WriteAllPermissions = Permissions{ | ||
Actions: PermissionWrite, | ||
Checks: PermissionWrite, | ||
Contents: PermissionWrite, | ||
Deployments: PermissionWrite, | ||
IDToken: PermissionWrite, | ||
Issues: PermissionWrite, | ||
Discussions: PermissionWrite, | ||
Packages: PermissionWrite, | ||
Pages: PermissionWrite, | ||
PullRequests: PermissionWrite, | ||
RepositoryProjects: PermissionWrite, | ||
SecurityEvents: PermissionWrite, | ||
Statuses: PermissionWrite, | ||
} | ||
|
||
// FromYAML takes a yaml.Node representing a permissions | ||
// definition and parses it into a Permissions struct | ||
func (p *Permissions) FromYAML(rawPermissions *yaml.Node) error { | ||
switch rawPermissions.Kind { | ||
case yaml.ScalarNode: | ||
var val string | ||
err := rawPermissions.Decode(&val) | ||
if err != nil { | ||
return err | ||
} | ||
if val == "read-all" { | ||
*p = ReadAllPermissions | ||
} | ||
if val == "write-all" { | ||
*p = WriteAllPermissions | ||
} | ||
return fmt.Errorf("unexpected `permissions` value: %v", rawPermissions) | ||
case yaml.MappingNode: | ||
var perms Permissions | ||
err := rawPermissions.Decode(&perms) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
case 0: | ||
*p = Permissions{} | ||
return nil | ||
default: | ||
return fmt.Errorf("invalid permissions value: %v", rawPermissions) | ||
} | ||
} | ||
|
||
func merge[T comparable](a, b T) T { | ||
var zero T | ||
if a == zero { | ||
return b | ||
} | ||
return a | ||
} | ||
|
||
// Merge merges two Permission values | ||
// | ||
// Already set values take precedence over `other`. | ||
// I.e. you want to call jobLevel.Permissions.Merge(topLevel.Permissions) | ||
func (p *Permissions) Merge(other Permissions) { | ||
p.Actions = merge(p.Actions, other.Actions) | ||
p.Checks = merge(p.Checks, other.Checks) | ||
p.Contents = merge(p.Contents, other.Contents) | ||
p.Deployments = merge(p.Deployments, other.Deployments) | ||
p.IDToken = merge(p.IDToken, other.IDToken) | ||
p.Issues = merge(p.Issues, other.Issues) | ||
p.Discussions = merge(p.Discussions, other.Discussions) | ||
p.Packages = merge(p.Packages, other.Packages) | ||
p.Pages = merge(p.Pages, other.Pages) | ||
p.PullRequests = merge(p.PullRequests, other.PullRequests) | ||
p.RepositoryProjects = merge(p.RepositoryProjects, other.RepositoryProjects) | ||
p.SecurityEvents = merge(p.SecurityEvents, other.SecurityEvents) | ||
p.Statuses = merge(p.Statuses, other.Statuses) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
// Copyright 2022 The Gitea Authors. All rights reserved. | ||
// Copyright 2025 The Gitea Authors. All rights reserved. | ||
scubbo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// SPDX-License-Identifier: MIT | ||
|
||
package actions | ||
|
@@ -46,6 +46,7 @@ | |
EventPayload string `xorm:"LONGTEXT"` | ||
TriggerEvent string // the trigger event defined in the `on` configuration of the triggered workflow | ||
Status Status `xorm:"index"` | ||
Permissions Permissions `xorm:"-"` | ||
Version int `xorm:"version default 0"` // Status could be updated concomitantly, so an optimistic lock is needed | ||
// Started and Stopped is used for recording last run time, if rerun happened, they will be reset to 0 | ||
Started timeutil.TimeStamp | ||
|
@@ -82,6 +83,38 @@ | |
return fmt.Sprintf("%s/actions/?workflow=%s", run.Repo.Link(), run.WorkflowID) | ||
} | ||
|
||
func (run *ActionRun) RefShaBaseRefAndHeadRef() (string, string, string, string) { | ||
var ref, sha, baseRef, headRef string | ||
|
||
ref = run.Ref | ||
sha = run.CommitSHA | ||
|
||
if pullPayload, err := run.GetPullRequestEventPayload(); err == nil && pullPayload.PullRequest != nil && pullPayload.PullRequest.Base != nil && pullPayload.PullRequest.Head != nil { | ||
baseRef = pullPayload.PullRequest.Base.Ref | ||
headRef = pullPayload.PullRequest.Head.Ref | ||
|
||
// if the TriggerEvent is pull_request_target, ref and sha need to be set according to the base of pull request | ||
// In GitHub's documentation, ref should be the branch or tag that triggered workflow. But when the TriggerEvent is pull_request_target, | ||
// the ref will be the base branch. | ||
if run.TriggerEvent == "pull_request_target" { | ||
ref = git.BranchPrefix + pullPayload.PullRequest.Base.Name | ||
sha = pullPayload.PullRequest.Base.Sha | ||
} | ||
} | ||
return ref, sha, baseRef, headRef | ||
} | ||
|
||
func (run *ActionRun) EventName() string { | ||
// TriggerEvent is added in https://github.com/go-gitea/gitea/pull/25229 | ||
// This fallback is for the old ActionRun that doesn't have the TriggerEvent field | ||
// and should be removed in 1.22 | ||
eventName := run.TriggerEvent | ||
if eventName == "" { | ||
eventName = run.Event.Event() | ||
} | ||
return eventName | ||
} | ||
|
||
// RefLink return the url of run's ref | ||
func (run *ActionRun) RefLink() string { | ||
refName := git.RefName(run.Ref) | ||
|
@@ -313,7 +346,7 @@ | |
hasWaiting = true | ||
} | ||
job.Name = util.EllipsisDisplayString(job.Name, 255) | ||
runJobs = append(runJobs, &ActionRunJob{ | ||
runJob := &ActionRunJob{ | ||
RunID: run.ID, | ||
RepoID: run.RepoID, | ||
OwnerID: run.OwnerID, | ||
|
@@ -325,7 +358,19 @@ | |
Needs: needs, | ||
RunsOn: job.RunsOn(), | ||
Status: status, | ||
}) | ||
} | ||
runJobs = append(runJobs, runJob) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is appended twice? once here and once on 374? |
||
|
||
// Parse the job's permissions | ||
if err := job.RawPermissions.Decode(&runJob.Permissions); err != nil { | ||
Check failure on line 365 in models/actions/run.go
|
||
return err | ||
} | ||
|
||
// Merge the job's permissions with the workflow permissions. | ||
// Job permissions take precedence. | ||
runJob.Permissions.Merge(run.Permissions) | ||
|
||
runJobs = append(runJobs, runJob) | ||
} | ||
if err := db.Insert(ctx, runJobs); err != nil { | ||
return err | ||
|
Uh oh!
There was an error while loading. Please reload this page.