mirror of
https://github.com/go-gitea/gitea.git
synced 2026-02-20 22:14:50 +01:00
Implement all requested changes
This commit is contained in:
parent
d26294839e
commit
9610d7f27a
@ -194,6 +194,10 @@ type ActionsTokenPermissions struct {
|
||||
Actions perm.AccessMode `json:"actions"`
|
||||
// Wiki - read/write/none
|
||||
Wiki perm.AccessMode `json:"wiki"`
|
||||
// Releases - read/write/none
|
||||
Releases perm.AccessMode `json:"releases"`
|
||||
// Projects - read/write/none
|
||||
Projects perm.AccessMode `json:"projects"`
|
||||
}
|
||||
|
||||
// HasAccess checks if the permission meets the required access level for the given scope
|
||||
@ -212,6 +216,10 @@ func (p ActionsTokenPermissions) HasAccess(scope string, required perm.AccessMod
|
||||
mode = p.PullRequests
|
||||
case "wiki":
|
||||
mode = p.Wiki
|
||||
case "releases":
|
||||
mode = p.Releases
|
||||
case "projects":
|
||||
mode = p.Projects
|
||||
}
|
||||
return mode >= required
|
||||
}
|
||||
@ -236,6 +244,8 @@ func DefaultActionsTokenPermissions(mode ActionsTokenPermissionMode) ActionsToke
|
||||
Packages: perm.AccessModeRead,
|
||||
Actions: perm.AccessModeRead,
|
||||
Wiki: perm.AccessModeRead,
|
||||
Releases: perm.AccessModeRead,
|
||||
Projects: perm.AccessModeRead,
|
||||
}
|
||||
}
|
||||
// Permissive mode (default)
|
||||
@ -246,6 +256,8 @@ func DefaultActionsTokenPermissions(mode ActionsTokenPermissionMode) ActionsToke
|
||||
Packages: perm.AccessModeRead, // Packages read by default for security
|
||||
Actions: perm.AccessModeWrite,
|
||||
Wiki: perm.AccessModeWrite,
|
||||
Releases: perm.AccessModeWrite,
|
||||
Projects: perm.AccessModeWrite,
|
||||
}
|
||||
}
|
||||
|
||||
@ -258,6 +270,8 @@ func ForkPullRequestPermissions() ActionsTokenPermissions {
|
||||
Packages: perm.AccessModeRead,
|
||||
Actions: perm.AccessModeRead,
|
||||
Wiki: perm.AccessModeRead,
|
||||
Releases: perm.AccessModeRead,
|
||||
Projects: perm.AccessModeRead,
|
||||
}
|
||||
}
|
||||
|
||||
@ -370,6 +384,8 @@ func (cfg *ActionsConfig) GetMaxTokenPermissions() ActionsTokenPermissions {
|
||||
Packages: perm.AccessModeWrite,
|
||||
Actions: perm.AccessModeWrite,
|
||||
Wiki: perm.AccessModeWrite,
|
||||
Releases: perm.AccessModeWrite,
|
||||
Projects: perm.AccessModeWrite,
|
||||
}
|
||||
}
|
||||
|
||||
@ -383,6 +399,8 @@ func (cfg *ActionsConfig) ClampPermissions(perms ActionsTokenPermissions) Action
|
||||
Packages: min(perms.Packages, maxPerms.Packages),
|
||||
Actions: min(perms.Actions, maxPerms.Actions),
|
||||
Wiki: min(perms.Wiki, maxPerms.Wiki),
|
||||
Releases: min(perms.Releases, maxPerms.Releases),
|
||||
Projects: min(perms.Projects, maxPerms.Projects),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3749,6 +3749,10 @@
|
||||
"actions.general.token_permissions.pull_requests.description": "Pull requests and related comments, assignees, labels, and milestones.",
|
||||
"actions.general.token_permissions.wiki": "Wiki",
|
||||
"actions.general.token_permissions.wiki.description": "Wiki pages and files.",
|
||||
"actions.general.token_permissions.releases": "Releases",
|
||||
"actions.general.token_permissions.releases.description": "Repository releases and tags.",
|
||||
"actions.general.token_permissions.projects": "Projects",
|
||||
"actions.general.token_permissions.projects.description": "Repository projects and boards.",
|
||||
"actions.general.token_permissions.packages": "Packages",
|
||||
"actions.general.token_permissions.packages.description": "Packages and container images.",
|
||||
"actions.general.token_permissions.actions_scope": "Actions",
|
||||
|
||||
@ -39,6 +39,25 @@ func ActionsGeneral(ctx *context.Context) {
|
||||
ctx.Data["AllowCrossRepoAccess"] = actionsCfg.AllowCrossRepoAccess
|
||||
ctx.Data["HasSelectedRepos"] = len(actionsCfg.AllowedCrossRepoIDs) > 0
|
||||
|
||||
// Load Allowed Repositories
|
||||
var allowedRepos []*repo_model.Repository
|
||||
if len(actionsCfg.AllowedCrossRepoIDs) > 0 {
|
||||
// Since the list shouldn't be too long, we can loop.
|
||||
// Ideally use GetRepositoriesByIDs but simple loop is fine for now.
|
||||
for _, id := range actionsCfg.AllowedCrossRepoIDs {
|
||||
repo, err := repo_model.GetRepositoryByID(ctx, id)
|
||||
if err != nil {
|
||||
if repo_model.IsErrRepoNotExist(err) {
|
||||
continue
|
||||
}
|
||||
ctx.ServerError("GetRepositoryByID", err)
|
||||
return
|
||||
}
|
||||
allowedRepos = append(allowedRepos, repo)
|
||||
}
|
||||
}
|
||||
ctx.Data["AllowedRepos"] = allowedRepos
|
||||
|
||||
ctx.HTML(http.StatusOK, tplSettingsActionsGeneral)
|
||||
}
|
||||
|
||||
@ -105,3 +124,83 @@ func ActionsGeneralPost(ctx *context.Context) {
|
||||
ctx.Flash.Success(ctx.Tr("org.settings.update_setting_success"))
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/settings/actions")
|
||||
}
|
||||
|
||||
// ActionsAllowedReposAdd adds a repository to the allowed list for cross-repo access
|
||||
func ActionsAllowedReposAdd(ctx *context.Context) {
|
||||
repoName := ctx.FormString("repo_name")
|
||||
if repoName == "" {
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/settings/actions")
|
||||
return
|
||||
}
|
||||
|
||||
repo, err := repo_model.GetRepositoryByName(ctx, ctx.Org.Organization.ID, repoName)
|
||||
if err != nil {
|
||||
if repo_model.IsErrRepoNotExist(err) {
|
||||
ctx.Flash.Error(ctx.Tr("repo.not_exist"))
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/settings/actions")
|
||||
return
|
||||
}
|
||||
ctx.ServerError("GetRepositoryByName", err)
|
||||
return
|
||||
}
|
||||
|
||||
actionsCfg, err := actions_model.GetOrgActionsConfig(ctx, ctx.Org.Organization.AsUser().ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetOrgActionsConfig", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if already exists
|
||||
for _, id := range actionsCfg.AllowedCrossRepoIDs {
|
||||
if id == repo.ID {
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/settings/actions")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
actionsCfg.AllowedCrossRepoIDs = append(actionsCfg.AllowedCrossRepoIDs, repo.ID)
|
||||
// Ensure mode is set to selected if we are adding specific repos?
|
||||
// Logic: If user adds a repo, they probably want it enabled.
|
||||
// But let's respect the current mode toggle. If "all" or "none" is set, adding a repo updates the list but might not activate "selected" mode unless user explicitly chose "selected".
|
||||
// However, if "selected" is active, this adds to it.
|
||||
|
||||
if err := actions_model.SetOrgActionsConfig(ctx, ctx.Org.Organization.AsUser().ID, actionsCfg); err != nil {
|
||||
ctx.ServerError("SetOrgActionsConfig", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/settings/actions")
|
||||
}
|
||||
|
||||
// ActionsAllowedReposRemove removes a repository from the allowed list
|
||||
func ActionsAllowedReposRemove(ctx *context.Context) {
|
||||
repoID := ctx.FormInt64("repo_id")
|
||||
if repoID == 0 {
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/settings/actions")
|
||||
return
|
||||
}
|
||||
|
||||
actionsCfg, err := actions_model.GetOrgActionsConfig(ctx, ctx.Org.Organization.AsUser().ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetOrgActionsConfig", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Filter out the ID
|
||||
newIDs := make([]int64, 0, len(actionsCfg.AllowedCrossRepoIDs))
|
||||
for _, id := range actionsCfg.AllowedCrossRepoIDs {
|
||||
if id != repoID {
|
||||
newIDs = append(newIDs, id)
|
||||
}
|
||||
}
|
||||
actionsCfg.AllowedCrossRepoIDs = newIDs
|
||||
|
||||
if err := actions_model.SetOrgActionsConfig(ctx, ctx.Org.Organization.AsUser().ID, actionsCfg); err != nil {
|
||||
ctx.ServerError("SetOrgActionsConfig", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/settings/actions")
|
||||
}
|
||||
|
||||
@ -183,6 +183,9 @@ func UpdateTokenPermissions(ctx *context.Context) {
|
||||
Packages: parseMaxPerm("packages"),
|
||||
PullRequests: parseMaxPerm("pull_requests"),
|
||||
Wiki: parseMaxPerm("wiki"),
|
||||
Actions: parseMaxPerm("actions"),
|
||||
Releases: parseMaxPerm("releases"),
|
||||
Projects: parseMaxPerm("projects"),
|
||||
}
|
||||
} else {
|
||||
actionsCfg.MaxTokenPermissions = nil
|
||||
|
||||
@ -961,6 +961,10 @@ func registerWebRoutes(m *web.Router) {
|
||||
m.Group("/actions", func() {
|
||||
m.Get("", org_setting.ActionsGeneral)
|
||||
m.Post("", org_setting.ActionsGeneralPost)
|
||||
m.Group("/allowed_repos", func() {
|
||||
m.Post("/add", org_setting.ActionsAllowedReposAdd)
|
||||
m.Post("/remove", org_setting.ActionsAllowedReposRemove)
|
||||
})
|
||||
addSettingsRunnersRoutes()
|
||||
addSettingsSecretsRoutes()
|
||||
addSettingsVariablesRoutes()
|
||||
|
||||
@ -70,6 +70,8 @@ func parseRawPermissions(rawPerms *yaml.Node, defaultPerms repo_model.ActionsTok
|
||||
Packages: perm.AccessModeRead,
|
||||
Actions: perm.AccessModeRead,
|
||||
Wiki: perm.AccessModeRead,
|
||||
Releases: perm.AccessModeRead,
|
||||
Projects: perm.AccessModeRead,
|
||||
}
|
||||
case "write-all":
|
||||
return repo_model.ActionsTokenPermissions{
|
||||
@ -79,6 +81,8 @@ func parseRawPermissions(rawPerms *yaml.Node, defaultPerms repo_model.ActionsTok
|
||||
Packages: perm.AccessModeWrite,
|
||||
Actions: perm.AccessModeWrite,
|
||||
Wiki: perm.AccessModeWrite,
|
||||
Releases: perm.AccessModeWrite,
|
||||
Projects: perm.AccessModeWrite,
|
||||
}
|
||||
}
|
||||
return defaultPerms
|
||||
@ -117,6 +121,10 @@ func parseRawPermissions(rawPerms *yaml.Node, defaultPerms repo_model.ActionsTok
|
||||
result.Actions = accessMode
|
||||
case "wiki":
|
||||
result.Wiki = accessMode
|
||||
case "releases":
|
||||
result.Releases = accessMode
|
||||
case "projects":
|
||||
result.Projects = accessMode
|
||||
// Additional GitHub scopes we don't explicitly handle yet:
|
||||
// These fall through to defaults
|
||||
// - deployments, environments, id-token, pages, repository-projects, security-events, statuses
|
||||
|
||||
@ -16,24 +16,53 @@
|
||||
<div class="grouped fields">
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" name="cross_repo_mode" value="none" {{if not .AllowCrossRepoAccess}}checked{{end}}>
|
||||
<input type="radio" name="cross_repo_mode" value="none" {{if not .AllowCrossRepoAccess}}checked{{end}} class="js-cross-repo-mode">
|
||||
<label>{{ctx.Locale.Tr "settings.disabled"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" name="cross_repo_mode" value="all" {{if and .AllowCrossRepoAccess (not .HasSelectedRepos)}}checked{{end}}>
|
||||
<input type="radio" name="cross_repo_mode" value="all" {{if and .AllowCrossRepoAccess (not .HasSelectedRepos)}}checked{{end}} class="js-cross-repo-mode">
|
||||
<label>{{ctx.Locale.Tr "actions.general.token_permissions.cross_repo_all"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" name="cross_repo_mode" value="selected" {{if and .AllowCrossRepoAccess .HasSelectedRepos}}checked{{end}}>
|
||||
<input type="radio" name="cross_repo_mode" value="selected" {{if and .AllowCrossRepoAccess .HasSelectedRepos}}checked{{end}} class="js-cross-repo-mode">
|
||||
<label>{{ctx.Locale.Tr "actions.general.token_permissions.cross_repo_selected"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Allowed Repositories List -->
|
||||
<div id="allowed-repos-section" class="field">
|
||||
<h5 class="ui header">
|
||||
{{ctx.Locale.Tr "actions.general.token_permissions.allowed_repos"}}
|
||||
</h5>
|
||||
<div class="ui middle aligned divided list">
|
||||
{{range .AllowedRepos}}
|
||||
<div class="item">
|
||||
<div class="right floated content">
|
||||
<button class="ui red tiny button" formmethod="post" formaction="{{$.Link}}/allowed_repos/remove?repo_id={{.ID}}">{{ctx.Locale.Tr "remove"}}</button>
|
||||
</div>
|
||||
<div class="content">
|
||||
<a href="{{.Link}}">{{.Name}}</a>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="item text empty">{{ctx.Locale.Tr "org.repos.none"}}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<h5 class="ui header">
|
||||
{{ctx.Locale.Tr "actions.general.token_permissions.add_repo"}}
|
||||
</h5>
|
||||
<div class="ui action input">
|
||||
<input name="repo_name" placeholder="{{ctx.Locale.Tr "search.search_repo"}}">
|
||||
<button class="ui green button" formmethod="post" formaction="{{$.Link}}/allowed_repos/add">{{ctx.Locale.Tr "add"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- Default Permission Mode -->
|
||||
|
||||
@ -196,6 +196,56 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Releases -->
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ctx.Locale.Tr "actions.general.token_permissions.releases"}}</strong>
|
||||
<p class="text small grey">{{ctx.Locale.Tr "actions.general.token_permissions.releases.description"}}</p>
|
||||
</td>
|
||||
<td style="text-align: center">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" name="max_releases" value="none" {{if not ($.MaxTokenPermissions.HasRead "releases")}}checked{{end}}>
|
||||
<label></label>
|
||||
</div>
|
||||
</td>
|
||||
<td style="text-align: center">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" name="max_releases" value="read" {{if and ($.MaxTokenPermissions.HasRead "releases") (not ($.MaxTokenPermissions.HasWrite "releases"))}}checked{{end}}>
|
||||
<label></label>
|
||||
</div>
|
||||
</td>
|
||||
<td style="text-align: center">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" name="max_releases" value="write" {{if $.MaxTokenPermissions.HasWrite "releases"}}checked{{end}}>
|
||||
<label></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Projects -->
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ctx.Locale.Tr "actions.general.token_permissions.projects"}}</strong>
|
||||
<p class="text small grey">{{ctx.Locale.Tr "actions.general.token_permissions.projects.description"}}</p>
|
||||
</td>
|
||||
<td style="text-align: center">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" name="max_projects" value="none" {{if not ($.MaxTokenPermissions.HasRead "projects")}}checked{{end}}>
|
||||
<label></label>
|
||||
</div>
|
||||
</td>
|
||||
<td style="text-align: center">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" name="max_projects" value="read" {{if and ($.MaxTokenPermissions.HasRead "projects") (not ($.MaxTokenPermissions.HasWrite "projects"))}}checked{{end}}>
|
||||
<label></label>
|
||||
</div>
|
||||
</td>
|
||||
<td style="text-align: center">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" name="max_projects" value="write" {{if $.MaxTokenPermissions.HasWrite "projects"}}checked{{end}}>
|
||||
<label></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Packages -->
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
@ -28,11 +28,11 @@ export function initActionsPermissionsTable(): void {
|
||||
for (const input of inputs) {
|
||||
input.disabled = tableDisabled;
|
||||
}
|
||||
permTable.style.opacity = tableDisabled ? '0.5' : '1';
|
||||
permTable.style.display = tableDisabled ? 'none' : '';
|
||||
}
|
||||
|
||||
if (tableSection) {
|
||||
tableSection.style.opacity = tableDisabled ? '0.5' : '1';
|
||||
tableSection.style.display = tableDisabled ? 'none' : '';
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,4 +43,23 @@ export function initActionsPermissionsTable(): void {
|
||||
followOrgCheckbox?.addEventListener('change', updateTableState);
|
||||
|
||||
updateTableState();
|
||||
|
||||
// Cross-Repo Access Table Toggle
|
||||
const crossRepoRadios = document.querySelectorAll<HTMLInputElement>('.js-cross-repo-mode');
|
||||
const allowedReposSection = document.querySelector<HTMLElement>('#allowed-repos-section');
|
||||
|
||||
if (crossRepoRadios.length && allowedReposSection) {
|
||||
function updateCrossRepoState(): void {
|
||||
const selectedMode = document.querySelector<HTMLInputElement>('input[name="cross_repo_mode"]:checked');
|
||||
const isSelected = selectedMode?.value === 'selected';
|
||||
if (allowedReposSection) {
|
||||
allowedReposSection.style.display = isSelected ? '' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
for (const radio of crossRepoRadios) {
|
||||
radio.addEventListener('change', updateCrossRepoState);
|
||||
}
|
||||
updateCrossRepoState();
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user