Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4b8cf42
docs: add notes on actions permissions proposal
SBALAVIGNESH123 Dec 9, 2025
9b476f8
feat(db): initial migration for actions permissions
SBALAVIGNESH123 Dec 9, 2025
659cb87
feat(models): add permission configuration models
SBALAVIGNESH123 Dec 10, 2025
713ddeb
feat(models): add cross-repo access and package linking
SBALAVIGNESH123 Dec 10, 2025
b29204c
wip: working on permission checking logic
SBALAVIGNESH123 Dec 11, 2025
c2465f9
test: add unit tests for permission checker
SBALAVIGNESH123 Dec 13, 2025
bddccc2
feat(api): add repository permissions endpoints
SBALAVIGNESH123 Dec 16, 2025
2420536
feat(api): add organization permissions endpoints
SBALAVIGNESH123 Dec 16, 2025
03b3af4
feat(ui): add repository permissions settings page
SBALAVIGNESH123 Dec 18, 2025
4c794c6
test: add integration tests for permissions API
SBALAVIGNESH123 Dec 20, 2025
4cf5510
fix: register migration and correct imports
SBALAVIGNESH123 Dec 9, 2025
5ef7c05
fix: use correct API context methods for org ownership checks
SBALAVIGNESH123 Dec 9, 2025
e491ceb
fix: replace all ctx.Org.IsOwner with proper IsOwnedBy method
SBALAVIGNESH123 Dec 9, 2025
e4a1061
docs: add swagger annotations to API structs
SBALAVIGNESH123 Dec 9, 2025
b0d693f
fix: markdown linting and complete all remaining fixes
SBALAVIGNESH123 Dec 9, 2025
3aa0c6f
fix: use APIErrorInternal for internal server errors
SBALAVIGNESH123 Dec 9, 2025
442f74c
refactor: remove duplicate permission checks and use middleware
SBALAVIGNESH123 Dec 9, 2025
a498e10
fix: add OrgAssignment middleware to populate org context
SBALAVIGNESH123 Dec 10, 2025
349a1a7
fix: apply gofumpt formatting and clean up comments
SBALAVIGNESH123 Dec 10, 2025
a7b8046
style: apply gofumpt formatting
SBALAVIGNESH123 Dec 10, 2025
1ef6e06
docs: regenerate swagger spec and fix comment syntax
SBALAVIGNESH123 Dec 10, 2025
0b63809
Merge branch 'main' into feat/actions-token-permissions
SBALAVIGNESH123 Dec 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions IMPLEMENTATION_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Actions Permissions Implementation Notes

Reading through #24635 and related PRs.
Need to understand why #23729 and #24554 were rejected.

Key points:
- Security first
- Org/repo boundaries
- No blanket permissions
226 changes: 226 additions & 0 deletions models/actions/action_permissions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package actions

import (
"context"

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/timeutil"
)

// PermissionMode represents the permission configuration mode
type PermissionMode int

const (
// PermissionModeRestricted - minimal permissions (default, secure)
PermissionModeRestricted PermissionMode = 0

// PermissionModePermissive - broad permissions (convenience)
PermissionModePermissive PermissionMode = 1

// PermissionModeCustom - user-defined permissions
PermissionModeCustom PermissionMode = 2
)

// ActionTokenPermission represents repository-level Actions token permissions
type ActionTokenPermission struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE NOT NULL"`

PermissionMode PermissionMode `xorm:"NOT NULL DEFAULT 0"`

// Granular permissions (only used in Custom mode)
ActionsRead bool `xorm:"NOT NULL DEFAULT false"`
ActionsWrite bool `xorm:"NOT NULL DEFAULT false"`
ContentsRead bool `xorm:"NOT NULL DEFAULT true"`
ContentsWrite bool `xorm:"NOT NULL DEFAULT false"`
IssuesRead bool `xorm:"NOT NULL DEFAULT false"`
IssuesWrite bool `xorm:"NOT NULL DEFAULT false"`
PackagesRead bool `xorm:"NOT NULL DEFAULT false"`
PackagesWrite bool `xorm:"NOT NULL DEFAULT false"`
PullRequestsRead bool `xorm:"NOT NULL DEFAULT false"`
PullRequestsWrite bool `xorm:"NOT NULL DEFAULT false"`
MetadataRead bool `xorm:"NOT NULL DEFAULT true"`

CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
}

// ActionOrgPermission represents organization-level Actions token permissions
type ActionOrgPermission struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"UNIQUE NOT NULL"`

PermissionMode PermissionMode `xorm:"NOT NULL DEFAULT 0"`
AllowRepoOverride bool `xorm:"NOT NULL DEFAULT true"`

// Granular permissions (only used in Custom mode)
ActionsRead bool `xorm:"NOT NULL DEFAULT false"`
ActionsWrite bool `xorm:"NOT NULL DEFAULT false"`
ContentsRead bool `xorm:"NOT NULL DEFAULT true"`
ContentsWrite bool `xorm:"NOT NULL DEFAULT false"`
IssuesRead bool `xorm:"NOT NULL DEFAULT false"`
IssuesWrite bool `xorm:"NOT NULL DEFAULT false"`
PackagesRead bool `xorm:"NOT NULL DEFAULT false"`
PackagesWrite bool `xorm:"NOT NULL DEFAULT false"`
PullRequestsRead bool `xorm:"NOT NULL DEFAULT false"`
PullRequestsWrite bool `xorm:"NOT NULL DEFAULT false"`
MetadataRead bool `xorm:"NOT NULL DEFAULT true"`

CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
}

func init() {
db.RegisterModel(new(ActionTokenPermission))
db.RegisterModel(new(ActionOrgPermission))
}

// GetRepoActionPermissions retrieves the Actions permissions for a repository
// If no configuration exists, returns nil (will use defaults)
func GetRepoActionPermissions(ctx context.Context, repoID int64) (*ActionTokenPermission, error) {
perm := &ActionTokenPermission{RepoID: repoID}
has, err := db.GetEngine(ctx).Get(perm)
if err != nil {
return nil, err
}
if !has {
return nil, nil // No custom config, will use defaults
}
return perm, nil
}

// GetOrgActionPermissions retrieves the Actions permissions for an organization
func GetOrgActionPermissions(ctx context.Context, orgID int64) (*ActionOrgPermission, error) {
perm := &ActionOrgPermission{OrgID: orgID}
has, err := db.GetEngine(ctx).Get(perm)
if err != nil {
return nil, err
}
if !has {
return nil, nil // No custom config, will use defaults
}
return perm, nil
}

// CreateOrUpdateRepoPermissions creates or updates repository-level permissions
func CreateOrUpdateRepoPermissions(ctx context.Context, perm *ActionTokenPermission) error {
existing := &ActionTokenPermission{RepoID: perm.RepoID}
has, err := db.GetEngine(ctx).Get(existing)
if err != nil {
return err
}

if has {
// Update existing
perm.ID = existing.ID
perm.CreatedUnix = existing.CreatedUnix
_, err = db.GetEngine(ctx).ID(perm.ID).Update(perm)
return err
}

// Create new
_, err = db.GetEngine(ctx).Insert(perm)
return err
}

// CreateOrUpdateOrgPermissions creates or updates organization-level permissions
func CreateOrUpdateOrgPermissions(ctx context.Context, perm *ActionOrgPermission) error {
existing := &ActionOrgPermission{OrgID: perm.OrgID}
has, err := db.GetEngine(ctx).Get(existing)
if err != nil {
return err
}

if has {
// Update existing
perm.ID = existing.ID
perm.CreatedUnix = existing.CreatedUnix
_, err = db.GetEngine(ctx).ID(perm.ID).Update(perm)
return err
}

// Create new
_, err = db.GetEngine(ctx).Insert(perm)
return err
}

// ToPermissionMap converts permission struct to a map for easy access
func (p *ActionTokenPermission) ToPermissionMap() map[string]map[string]bool {
// Apply permission mode defaults
var perms map[string]map[string]bool

switch p.PermissionMode {
case PermissionModeRestricted:
// Minimal permissions - only read metadata and contents
perms = map[string]map[string]bool{
"actions": {"read": false, "write": false},
"contents": {"read": true, "write": false},
"issues": {"read": false, "write": false},
"packages": {"read": false, "write": false},
"pull_requests": {"read": false, "write": false},
"metadata": {"read": true, "write": false},
}
case PermissionModePermissive:
// Broad permissions - read/write for most things
perms = map[string]map[string]bool{
"actions": {"read": true, "write": true},
"contents": {"read": true, "write": true},
"issues": {"read": true, "write": true},
"packages": {"read": true, "write": true},
"pull_requests": {"read": true, "write": true},
"metadata": {"read": true, "write": false},
}
case PermissionModeCustom:
// Use explicitly set permissions
perms = map[string]map[string]bool{
"actions": {"read": p.ActionsRead, "write": p.ActionsWrite},
"contents": {"read": p.ContentsRead, "write": p.ContentsWrite},
"issues": {"read": p.IssuesRead, "write": p.IssuesWrite},
"packages": {"read": p.PackagesRead, "write": p.PackagesWrite},
"pull_requests": {"read": p.PullRequestsRead, "write": p.PullRequestsWrite},
"metadata": {"read": p.MetadataRead, "write": false},
}
}

return perms
}

// ToPermissionMap converts org permission struct to a map
func (p *ActionOrgPermission) ToPermissionMap() map[string]map[string]bool {
var perms map[string]map[string]bool

switch p.PermissionMode {
case PermissionModeRestricted:
perms = map[string]map[string]bool{
"actions": {"read": false, "write": false},
"contents": {"read": true, "write": false},
"issues": {"read": false, "write": false},
"packages": {"read": false, "write": false},
"pull_requests": {"read": false, "write": false},
"metadata": {"read": true, "write": false},
}
case PermissionModePermissive:
perms = map[string]map[string]bool{
"actions": {"read": true, "write": true},
"contents": {"read": true, "write": true},
"issues": {"read": true, "write": true},
"packages": {"read": true, "write": true},
"pull_requests": {"read": true, "write": true},
"metadata": {"read": true, "write": false},
}
case PermissionModeCustom:
perms = map[string]map[string]bool{
"actions": {"read": p.ActionsRead, "write": p.ActionsWrite},
"contents": {"read": p.ContentsRead, "write": p.ContentsWrite},
"issues": {"read": p.IssuesRead, "write": p.IssuesWrite},
"packages": {"read": p.PackagesRead, "write": p.PackagesWrite},
"pull_requests": {"read": p.PullRequestsRead, "write": p.PullRequestsWrite},
"metadata": {"read": p.MetadataRead, "write": false},
}
}

return perms
}
Loading
Loading