Let operators manage ordered action groups on each host, run them manually or on a schedule over SSH, and inspect disk-backed JSON run logs from a node detail UI.
180 lines
4.9 KiB
Go
180 lines
4.9 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 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 (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
|
|
}
|