Completes run-if/set_variable and requires_sudo editing, sudoers path resolve helpers, and execute permission checks so node action groups can gate upgrades and elevate safely.
185 lines
5.2 KiB
Go
185 lines
5.2 KiB
Go
package api
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
|
|
"codeberg.org/SquidSE/ClusterCanvas/service/internal/settings"
|
|
)
|
|
|
|
var errUsersManageRequired = errors.New("permission users.manage required")
|
|
var errNodesReadRequired = errors.New("permission nodes.read required")
|
|
var errNodesExecRequired = errors.New("permission nodes.exec required")
|
|
var errNodesDeleteRequired = errors.New("permission nodes.delete required")
|
|
var errActionsCreateRequired = errors.New("permission actions.create required")
|
|
var errActionsUpdateRequired = errors.New("permission actions.update required")
|
|
var errActionsDeleteRequired = errors.New("permission actions.delete required")
|
|
var errActionsExecuteRequired = errors.New("permission actions.execute required")
|
|
var errJobsRunRequired = errors.New("permission jobs.run required")
|
|
var errLogsReadRequired = errors.New("permission logs.read required")
|
|
|
|
// permissionsForUser returns the union of permissions from the user's groups.
|
|
func permissionsForUser(user settings.UserCredential, groups []settings.Group) map[string]struct{} {
|
|
groupByName := make(map[string]settings.Group, len(groups))
|
|
for _, group := range groups {
|
|
groupByName[group.Name] = group
|
|
}
|
|
|
|
result := make(map[string]struct{})
|
|
for _, groupName := range user.GroupNames {
|
|
group, ok := groupByName[groupName]
|
|
if !ok {
|
|
continue
|
|
}
|
|
for _, permission := range group.Permissions {
|
|
result[permission] = struct{}{}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func userHasPermission(user settings.UserCredential, groups []settings.Group, permission string) bool {
|
|
_, ok := permissionsForUser(user, groups)[permission]
|
|
return ok
|
|
}
|
|
|
|
func permissionList(user settings.UserCredential, groups []settings.Group) []string {
|
|
set := permissionsForUser(user, groups)
|
|
ordered := allPermissionList()
|
|
result := make([]string, 0, len(set))
|
|
for _, permission := range ordered {
|
|
if _, ok := set[permission]; ok {
|
|
result = append(result, permission)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func authorizeNamedPermission(request *http.Request, configDir string, permission string, missing error) error {
|
|
user, ok := UserFromContext(request.Context())
|
|
if !ok {
|
|
return errors.New("authentication required")
|
|
}
|
|
|
|
payload, err := loadSettingsOrDefault(configDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !userHasPermission(user, payload.Groups, permission) {
|
|
return missing
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (app *App) authorizeUsersWrite(request *http.Request) error {
|
|
user, ok := UserFromContext(request.Context())
|
|
if !ok {
|
|
return errors.New("authentication required")
|
|
}
|
|
|
|
payload, err := loadSettingsOrDefault(app.ConfigDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !userHasPermission(user, payload.Groups, "users.manage") {
|
|
return errUsersManageRequired
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (app *App) authorizeNodesRead(request *http.Request) error {
|
|
user, ok := UserFromContext(request.Context())
|
|
if !ok {
|
|
return errors.New("authentication required")
|
|
}
|
|
|
|
payload, err := loadSettingsOrDefault(app.ConfigDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !userHasPermission(user, payload.Groups, "nodes.read") {
|
|
return errNodesReadRequired
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (app *App) authorizeLogsRead(request *http.Request) error {
|
|
user, ok := UserFromContext(request.Context())
|
|
if !ok {
|
|
return errors.New("authentication required")
|
|
}
|
|
|
|
payload, err := loadSettingsOrDefault(app.ConfigDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !userHasPermission(user, payload.Groups, "logs.read") {
|
|
return errLogsReadRequired
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (app *App) authorizeNodesExec(request *http.Request) error {
|
|
user, ok := UserFromContext(request.Context())
|
|
if !ok {
|
|
return errors.New("authentication required")
|
|
}
|
|
|
|
payload, err := loadSettingsOrDefault(app.ConfigDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !userHasPermission(user, payload.Groups, "nodes.exec") {
|
|
return errNodesExecRequired
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (app *App) authorizeNodesDelete(request *http.Request) error {
|
|
user, ok := UserFromContext(request.Context())
|
|
if !ok {
|
|
return errors.New("authentication required")
|
|
}
|
|
|
|
payload, err := loadSettingsOrDefault(app.ConfigDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !userHasPermission(user, payload.Groups, "nodes.delete") {
|
|
return errNodesDeleteRequired
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func authorizeActionsCreate(request *http.Request, configDir string) error {
|
|
return authorizeNamedPermission(request, configDir, "actions.create", errActionsCreateRequired)
|
|
}
|
|
|
|
func authorizeActionsUpdate(request *http.Request, configDir string) error {
|
|
return authorizeNamedPermission(request, configDir, "actions.update", errActionsUpdateRequired)
|
|
}
|
|
|
|
func authorizeActionsDelete(request *http.Request, configDir string) error {
|
|
return authorizeNamedPermission(request, configDir, "actions.delete", errActionsDeleteRequired)
|
|
}
|
|
|
|
func authorizeActionsExecute(request *http.Request, configDir string) error {
|
|
return authorizeNamedPermission(request, configDir, "actions.execute", errActionsExecuteRequired)
|
|
}
|
|
|
|
func (app *App) authorizeJobsRun(request *http.Request) error {
|
|
user, ok := UserFromContext(request.Context())
|
|
if !ok {
|
|
return errors.New("authentication required")
|
|
}
|
|
|
|
payload, err := loadSettingsOrDefault(app.ConfigDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !userHasPermission(user, payload.Groups, "jobs.run") {
|
|
return errJobsRunRequired
|
|
}
|
|
return nil
|
|
}
|