refactor routes,
tronnet/workflows: Static Analysis -- Golang / static-go (push) Successful in 30s

use json encoding for route body parameters,
implement modify user/group/pool operations,
simplify schema by using comon lib struct tags and RequireAll and AtLeastOne helper functions,
properly implement locadb as Backend interface
This commit is contained in:
alu
2026-08-21 20:49:15 +00:00
parent 670f7d4999
commit 4783d79523
13 changed files with 959 additions and 606 deletions
+6 -1
View File
@@ -25,6 +25,10 @@ type LDAPConfig struct {
Verify bool
}
type LocalDBConfig struct {
Path string `json:"path"`
}
type Config struct {
ListenPort int `json:"listenPort"`
SessionCookieName string `json:"sessionCookieName"`
@@ -34,7 +38,8 @@ type Config struct {
Secure bool `json:"secure"`
MaxAge int `json:"maxAge"`
}
PVE PVEConfig `json:"pve"`
PVE PVEConfig `json:"pve"`
LocalDB LocalDBConfig `json:"localdb"`
}
func GetConfig(configPath string) Config {
+2 -16
View File
@@ -1,21 +1,7 @@
package app
type Login struct { // login body struct
UsernameRaw string `form:"username" binding:"required"`
UsernameRaw string `json:"username" binding:"required"`
Username Username
Password string `form:"password" binding:"required"`
}
type UserFormRequired struct { // add user body struct
CN string `form:"cn" binding:"required"`
SN string `form:"sn" binding:"required"`
Mail string `form:"mail" binding:"required"`
Password string `form:"password" binding:"required"`
}
type UserFormOptional struct { // modify user body struct
CN string `form:"cn"`
SN string `form:"sn"`
Mail string `form:"mail"`
Password string `form:"password"`
Password string `json:"password" binding:"required"`
}
+5 -2
View File
@@ -3,15 +3,18 @@ package app
import paas "proxmoxaas-common-lib"
type Backend interface {
NewPool(poolname string) (int, error)
NewPool(poolname string, pool Pool) (int, error)
ModPool(poolname string, pool Pool) (int, error)
GetPool(poolname string) (Pool, []string, int, error) // []string members
DelPool(poolname string) (int, error)
NewGroup(groupname Groupname) (int, error)
NewGroup(groupname Groupname, group Group) (int, error)
ModGroup(groupname Groupname, group Group) (int, error)
GetGroup(groupname Groupname) (Group, []string, int, error) // []string members
DelGroup(groupname Groupname) (int, error)
AddGroupToPool(groupname Groupname, poolname string) (int, error)
DelGroupFromPool(groupname Groupname, poolname string) (int, error)
NewUser(username Username, user User) (int, error)
ModUser(username Username, user User) (int, error)
GetUser(username Username) (User, int, error)
DelUser(username Username) (int, error)
AddUserToGroup(username Username, groupname Groupname) (int, error)
+55
View File
@@ -0,0 +1,55 @@
package app
import (
"reflect"
)
// RequireAll ensures that EVERY non-excluded exported field in the struct is non-zero.
func RequireAll(v any, excludes ...string) bool {
val := reflect.ValueOf(v)
if val.Kind() == reflect.Pointer {
val = val.Elem()
}
typ := val.Type()
excludeMap := make(map[string]bool)
for _, ex := range excludes {
excludeMap[ex] = true
}
for i := 0; i < val.NumField(); i++ {
fieldName := typ.Field(i).Name
if excludeMap[fieldName] {
continue
}
if val.Field(i).IsZero() {
return false
}
}
return true
}
// AtLeastOne ensures that AT LEAST ONE non-excluded exported field is non-zero.
func AtLeastOne(v any, excludes ...string) bool {
val := reflect.ValueOf(v)
if val.Kind() == reflect.Pointer {
val = val.Elem()
}
typ := val.Type()
excludeMap := make(map[string]bool)
for _, ex := range excludes {
excludeMap[ex] = true
}
for i := 0; i < val.NumField(); i++ {
fieldName := typ.Field(i).Name
if excludeMap[fieldName] {
continue
}
if !val.Field(i).IsZero() {
return true
}
}
return false
}
+19 -23
View File
@@ -78,8 +78,10 @@ func (l LDAPClient) GetUser(username common.Username) (common.User, int, error)
)
searchResponse, err := l.client.Search(searchRequest) // perform search
if err != nil {
return user, http.StatusBadRequest, err
if ldap.IsErrorAnyOf(err, ldap.LDAPResultNoSuchObject) {
return user, http.StatusNotFound, err
} else if err != nil {
return user, http.StatusInternalServerError, err
}
entry := searchResponse.Entries[0]
@@ -91,10 +93,10 @@ func (l LDAPClient) GetUser(username common.Username) (common.User, int, error)
}
func (l LDAPClient) NewUser(username common.Username, user common.User) (int, error) {
if user.CN == "" || user.SN == "" || user.Password == "" || user.Mail == "" {
if !common.RequireAll(user, "Username") {
return http.StatusBadRequest, ldap.NewError(
ldap.LDAPResultUnwillingToPerform,
errors.New("missing one of required fields: cn, sn, mail, userpassword"),
errors.New("requires all of fields: cn, sn, mail, password"),
)
}
@@ -117,10 +119,10 @@ func (l LDAPClient) NewUser(username common.Username, user common.User) (int, er
}
func (l LDAPClient) ModUser(username common.Username, user common.User) (int, error) {
if user.CN == "" && user.SN == "" && user.Password == "" && user.Mail == "" {
if !common.AtLeastOne(user, "Username") {
return http.StatusBadRequest, ldap.NewError(
ldap.LDAPResultUnwillingToPerform,
errors.New("requires one of fields: cn, sn, mail, userpassword"),
errors.New("requires one of fields: cn, sn, mail, password"),
)
}
@@ -180,8 +182,10 @@ func (l LDAPClient) GetGroup(groupname common.Groupname) (common.Group, []string
)
searchResponse, err := l.client.Search(searchRequest) // perform search
if err != nil {
return group, members, http.StatusBadRequest, err
if ldap.IsErrorAnyOf(err, ldap.LDAPResultNoSuchObject) {
return group, members, http.StatusNotFound, err
} else if err != nil {
return group, members, http.StatusInternalServerError, err
}
entry := searchResponse.Entries[0]
@@ -195,7 +199,7 @@ func (l LDAPClient) GetGroup(groupname common.Groupname) (common.Group, []string
return group, members, http.StatusOK, nil
}
func (l LDAPClient) NewGroup(groupname common.Groupname) (int, error) {
func (l LDAPClient) NewGroup(groupname common.Groupname, group common.Group) (int, error) {
// add new group by ID only
addRequest := ldap.NewAddRequest(
fmt.Sprintf("cn=%s,ou=groups,%s", groupname.GroupID, l.config.BaseDN), // DN
@@ -214,19 +218,7 @@ func (l LDAPClient) NewGroup(groupname common.Groupname) (int, error) {
}
func (l LDAPClient) ModGroup(groupname common.Groupname, group common.Group) (int, error) {
modifyRequest := ldap.NewModifyRequest(
fmt.Sprintf("cn=%s,ou=groups,%s", groupname.GroupID, l.config.BaseDN),
nil,
)
modifyRequest.Replace("cn", []string{groupname.GroupID})
err := l.client.Modify(modifyRequest)
if err != nil {
return http.StatusBadRequest, err
}
return http.StatusOK, nil
return http.StatusNotImplemented, fmt.Errorf("ldap does not implement modification of groups")
}
func (l LDAPClient) DelGroup(groupname common.Groupname) (int, error) {
@@ -285,7 +277,11 @@ func (l LDAPClient) DelUserFromGroup(username common.Username, groupname common.
return http.StatusOK, nil
}
func (l LDAPClient) NewPool(poolname string) (int, error) {
func (l LDAPClient) NewPool(poolname string, pool common.Pool) (int, error) {
return http.StatusNotImplemented, fmt.Errorf("ldap does not implement pools")
}
func (l LDAPClient) ModPool(poolname string, pool common.Pool) (int, error) {
return http.StatusNotImplemented, fmt.Errorf("ldap does not implement pools")
}
+121 -9
View File
@@ -9,32 +9,38 @@ import (
)
type DB struct {
path string
data map[string]common.Pool
}
func LoadDB(localDBPath string) (DB, error) {
db := DB{}
var db *DB
func (db *DB) load(localDBPath string) error {
db.data = make(map[string]common.Pool)
db.path = localDBPath
root, err := os.OpenRoot(".")
if err != nil {
return db, err
return err
}
defer root.Close()
content, err := root.ReadFile(localDBPath)
if err != nil {
return db, err
return err
}
err = json.Unmarshal(content, &db.data)
if err != nil {
return db, err
return err
}
return db, nil
return nil
}
func SaveDB(localDBPath string, db DB) error {
json, err := json.Marshal(db.data)
func (db *DB) save() error {
localDBPath := db.path
// write to file with pretty print for readability reasons
json, err := json.MarshalIndent(db.data, "", "\t")
if err != nil {
return err
}
@@ -42,10 +48,116 @@ func SaveDB(localDBPath string, db DB) error {
return err
}
func NewClientFromCredentials(config common.LocalDBConfig, username common.Username, password string) (common.Backend, int, error) {
if db != nil {
return *db, http.StatusOK, nil
} else {
// load localdb if this is the first time
db = &DB{}
err := db.load(config.Path)
if err != nil {
return *db, http.StatusInternalServerError, err
} else {
return *db, http.StatusOK, nil
}
}
}
func (localdb DB) GetPool(poolname string) (common.Pool, []string, int, error) {
pool, ok := localdb.data[poolname]
if !ok {
return pool, []string{}, http.StatusNotFound, fmt.Errorf("pool %s not in localdb", poolname)
return pool, []string{}, http.StatusNotFound, fmt.Errorf("localdb pool %s does not exist", poolname)
}
return pool, []string{}, http.StatusOK, nil
}
func (localdb DB) NewPool(poolname string, pool common.Pool) (int, error) {
_, ok := localdb.data[poolname]
if ok {
return http.StatusBadRequest, fmt.Errorf("localdb pool %s already exists", poolname)
}
localdb.data[poolname] = pool
err := localdb.save()
if err != nil {
return http.StatusInternalServerError, err
} else {
return http.StatusOK, nil
}
}
func (localdb DB) ModPool(poolname string, pool common.Pool) (int, error) {
_, ok := localdb.data[poolname]
if !ok {
return http.StatusBadRequest, fmt.Errorf("localdb pool %s does not exist", poolname)
}
old_pool := localdb.data[poolname]
MergeNonZero(&old_pool, &pool)
err := localdb.save()
if err != nil {
return http.StatusInternalServerError, err
} else {
return http.StatusOK, nil
}
}
func (localdb DB) DelPool(poolname string) (int, error) {
_, ok := localdb.data[poolname]
if !ok {
return http.StatusBadRequest, fmt.Errorf("localdb pool %s does not exist", poolname)
}
delete(localdb.data, poolname)
err := localdb.save()
if err != nil {
return http.StatusInternalServerError, err
} else {
return http.StatusOK, nil
}
}
func (localdb DB) NewGroup(groupname common.Groupname, group common.Group) (int, error) {
return http.StatusNotImplemented, fmt.Errorf("localdb does not implement groups")
}
func (localdb DB) ModGroup(groupname common.Groupname, group common.Group) (int, error) {
return http.StatusNotImplemented, fmt.Errorf("localdb does not implement groups")
}
func (localdb DB) GetGroup(groupname common.Groupname) (common.Group, []string, int, error) {
return common.Group{}, []string{}, http.StatusNotImplemented, fmt.Errorf("localdb does not implement groups")
}
func (localdb DB) DelGroup(groupname common.Groupname) (int, error) {
return http.StatusNotImplemented, fmt.Errorf("localdb does not implement groups")
}
func (localdb DB) AddGroupToPool(groupname common.Groupname, poolname string) (int, error) {
return http.StatusNotImplemented, fmt.Errorf("localdb does not implement groups")
}
func (localdb DB) DelGroupFromPool(groupname common.Groupname, poolname string) (int, error) {
return http.StatusNotImplemented, fmt.Errorf("localdb does not implement groups")
}
func (localdb DB) NewUser(username common.Username, user common.User) (int, error) {
return http.StatusNotImplemented, fmt.Errorf("localdb does not implement users")
}
func (localdb DB) ModUser(username common.Username, user common.User) (int, error) {
return http.StatusNotImplemented, fmt.Errorf("localdb does not implement users")
}
func (localdb DB) GetUser(username common.Username) (common.User, int, error) {
return common.User{}, http.StatusNotImplemented, fmt.Errorf("localdb does not implement users")
}
func (localdb DB) DelUser(username common.Username) (int, error) {
return http.StatusNotImplemented, fmt.Errorf("localdb does not implement users")
}
func (localdb DB) AddUserToGroup(username common.Username, groupname common.Groupname) (int, error) {
return http.StatusNotImplemented, fmt.Errorf("localdb does not implement users")
}
func (localdb DB) DelUserFromGroup(username common.Username, groupname common.Groupname) (int, error) {
return http.StatusNotImplemented, fmt.Errorf("localdb does not implement users")
}
+19
View File
@@ -0,0 +1,19 @@
package localdb
import "reflect"
// MergeNonZero overwrites fields in dst with fields in src iff src is not a zero value.
func MergeNonZero[T any](dst *T, src *T) {
vDst := reflect.ValueOf(dst).Elem()
vSrc := reflect.ValueOf(src).Elem()
for i := 0; i < vDst.NumField(); i++ {
dstField := vDst.Field(i)
srcField := vSrc.Field(i)
// Only set if the field is exported (CanSet) and the source is not a zero value
if dstField.CanSet() && !srcField.IsZero() {
dstField.Set(srcField)
}
}
}
+53 -488
View File
@@ -11,18 +11,27 @@ import (
"strconv"
common "access-manager-api/app/common"
ldap "access-manager-api/app/ldap"
localdb "access-manager-api/app/localdb"
pve "access-manager-api/app/pve"
paas "proxmoxaas-common-lib"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
"github.com/luthermonson/go-proxmox"
uuid "github.com/nu7hatch/gouuid"
)
type Realm struct {
Type string
Config any
}
type UserSession struct {
PVE common.Backend
Realm struct {
Name string
Handler common.Backend
}
DB common.Backend
}
var Version = "1.0.0"
var Config common.Config
var UserSessions map[string]*UserSession
@@ -30,25 +39,19 @@ var Realms map[string]Realm
func Run() {
configPath := flag.String("config", "config.json", "path to config.json file")
localDBPath := flag.String("localdb", "localdb.json", "path to localdb.json file")
flag.Parse()
// load config values
var err error
Config = common.GetConfig(*configPath)
// already exits if failed
log.Printf("Read in config from %s\n", *configPath)
// load localdb
db, err := localdb.LoadDB(*localDBPath)
if err != nil {
log.Fatalf("Error when reading localdb file: %s\n", err)
}
log.Printf("Read in localdb from %s\n", *localDBPath)
// setup router
// setup gin
gin.SetMode(gin.ReleaseMode)
router := SetupAPISessionStore(&Config)
router := gin.Default()
// setup api auth cookies
SetupAPISessionStore(router, &Config)
// get realms from proxmox
Realms = make(map[string]Realm)
@@ -61,486 +64,39 @@ func Run() {
c.JSON(http.StatusOK, gin.H{"version": Version})
})
router.POST("/ticket", func(c *gin.Context) {
body := common.Login{}
err := c.ShouldBind(&body)
if err != nil { // bad request from binding
c.JSON(http.StatusBadRequest, gin.H{"auth": false, "error": err.Error()})
return
}
// attempt to parse username
body.Username, err = paas.ParseUsername(body.UsernameRaw)
if err != nil { // username format incorrect
c.JSON(http.StatusBadRequest, gin.H{"auth": false, "error": err.Error()})
return
}
handler := Realms[body.Username.Realm].Type
userbackends := UserSession{}
// always bind proxmox backend
PVEClient, code, err := pve.NewClientFromCredentials(Config.PVE, body.Username, body.Password)
if err != nil { // pve client failed to bind
c.JSON(code, gin.H{"auth": false, "error": err.Error()})
return
}
userbackends.PVE = PVEClient
// bind backend by type
switch handler {
case "pve":
case "ldap":
config := Realms[body.Username.Realm].Config.(common.LDAPConfig)
LDAPClient, code, err := ldap.NewClientFromCredentials(config, body.Username, body.Password)
if err != nil { // ldap client failed to bind
c.JSON(code, gin.H{"auth": false, "error": err.Error()})
return
}
userbackends.Realm.Name = body.Username.Realm
userbackends.Realm.Handler = LDAPClient
default:
c.JSON(code, gin.H{"auth": false, "error": fmt.Errorf("user realm %s is not supported", body.Username.Realm)})
return
}
userbackends.DB = &db
// successful binding at this point
// create new session
session := sessions.Default(c)
// create random uuid to map user to backends
uuid, _ := uuid.NewV4()
// set uuid mapping in session
session.Set("SessionUUID", uuid.String())
// set uuid mapping in LDAPSessions
UserSessions[uuid.String()] = &userbackends
// save the session
err = session.Save()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"auth": false})
} else {
// return successful auth
c.JSON(http.StatusOK, gin.H{"auth": true})
}
})
router.DELETE("/ticket", func(c *gin.Context) {
// get session uuid from session cookie
session := sessions.Default(c)
SessionUUID := session.Get("SessionUUID")
if SessionUUID == nil {
c.JSON(http.StatusUnauthorized, gin.H{"auth": false})
return
}
uuid := SessionUUID.(string)
// delete uuid entry from user sessions
delete(UserSessions, uuid) // deletes uuid mapping
session.Options(sessions.Options{MaxAge: -1}) // set max age to -1 so session cookie is deleted
err := session.Save() // if save somehow fails, it should be ok since the uuid mapping is already deleted
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"auth": false})
} else {
c.JSON(http.StatusUnauthorized, gin.H{"auth": false})
}
})
router.GET("/pools/:poolid", func(c *gin.Context) {
poolid, ok := c.Params.Get("poolid")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter poolid")})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
pool, code, err := GetPool(backends, poolid)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.JSON(http.StatusOK, gin.H{"pool": pool})
}
})
router.POST("/pools/:poolid", func(c *gin.Context) {
poolid, ok := c.Params.Get("poolid")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter poolid")})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
code, err = NewPool(backends, poolid)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
})
router.DELETE("/pools/:poolid", func(c *gin.Context) {
poolid, ok := c.Params.Get("poolid")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter poolid")})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
code, err = DelPool(backends, poolid)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
})
router.GET("/groups/:groupname", func(c *gin.Context) {
groupname_str, ok := c.Params.Get("groupname")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter poolid")})
return
}
groupname, err := common.ParseGroupname(groupname_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
group, code, err := GetGroup(backends, groupname)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.JSON(http.StatusOK, gin.H{"group": group})
}
})
router.POST("/groups/:groupname", func(c *gin.Context) {
groupname_str, ok := c.Params.Get("groupname")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter groupname")})
return
}
groupname, err := paas.ParseGroupname(groupname_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
code, err = NewGroup(backends, groupname)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
})
router.DELETE("/groups/:groupname", func(c *gin.Context) {
groupname_str, ok := c.Params.Get("groupname")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter groupname")})
return
}
groupname, err := paas.ParseGroupname(groupname_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
code, err = DelGroup(backends, groupname)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
})
router.POST("/pools/:poolid/groups/:groupname", func(c *gin.Context) {
poolid, ok := c.Params.Get("poolid")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter poolid")})
return
}
groupname_str, ok := c.Params.Get("groupname")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter groupname")})
return
}
groupname, err := paas.ParseGroupname(groupname_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
code, err = AddGroupToPool(backends, groupname, poolid)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
})
router.DELETE("/pools/:poolid/groups/:groupname", func(c *gin.Context) {
poolid, ok := c.Params.Get("poolid")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter poolid")})
return
}
groupname_str, ok := c.Params.Get("groupname")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter groupname")})
return
}
groupname, err := paas.ParseGroupname(groupname_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
code, err = DelGroupFromPool(backends, groupname, poolid)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
})
router.GET("/users/:username", func(c *gin.Context) {
username_str, ok := c.Params.Get("username")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter poolid")})
return
}
username, err := common.ParseUsername(username_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
user, code, err := GetUser(backends, username)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.JSON(http.StatusOK, gin.H{"user": user})
}
})
router.POST("/users/:username", func(c *gin.Context) {
username_str, ok := c.Params.Get("username")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter groupname")})
return
}
username, err := paas.ParseUsername(username_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
form := common.UserFormRequired{}
err = c.ShouldBind(&form)
if err != nil { // failed binding, usually missing form field
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
user := common.User{}
user.CN = form.CN
user.SN = form.SN
user.Mail = form.Mail
user.Password = form.Password
code, err = NewUser(backends, username, user)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
})
router.DELETE("/users/:username", func(c *gin.Context) {
username_str, ok := c.Params.Get("username")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter groupname")})
return
}
username, err := paas.ParseUsername(username_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
code, err = DelUser(backends, username)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
})
router.POST("/groups/:groupname/users/:username", func(c *gin.Context) {
groupname_str, ok := c.Params.Get("groupname")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter groupname")})
return
}
username_str, ok := c.Params.Get("username")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter username")})
return
}
groupname, err := paas.ParseGroupname(groupname_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
return
}
username, err := paas.ParseUsername(username_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
code, err = AddUserToGroup(backends, username, groupname)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
})
router.DELETE("/groups/:groupname/users/:username", func(c *gin.Context) {
groupname_str, ok := c.Params.Get("groupname")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter groupname")})
return
}
username_str, ok := c.Params.Get("username")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter username")})
return
}
groupname, err := paas.ParseGroupname(groupname_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
return
}
username, err := paas.ParseUsername(username_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
code, err = DelUserFromGroup(backends, username, groupname)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
})
router.POST("/ticket", POST_Ticket)
router.DELETE("/ticket", DELETE_Ticket)
router.GET("/pools/:poolid", GET_Pool)
router.POST("/pools/:poolid", POST_Pool)
router.DELETE("/pools/:poolid", DELETE_Pool)
router.GET("/groups/:groupname", GET_Group)
router.POST("/groups/:groupname", POST_Group)
router.DELETE("/groups/:groupname", DELETE_Group)
router.POST("/pools/:poolid/groups/:groupname", POST_Pool_Group)
router.DELETE("/pools/:poolid/groups/:groupname")
router.GET("/users/:username", GET_User)
router.POST("/users/:username", POST_User)
router.DELETE("/users/:username", DELETE_User)
router.POST("/groups/:groupname/users/:username", POST_Group_User)
router.DELETE("/groups/:groupname/users/:username", DELETE_Group_User)
log.Printf("Starting Access Manager API on port %s\n", strconv.Itoa(Config.ListenPort))
err = router.Run("0.0.0.0:" + strconv.Itoa(Config.ListenPort))
err := router.Run("0.0.0.0:" + strconv.Itoa(Config.ListenPort))
if err != nil {
log.Fatalf("Error starting router: %s", err.Error())
}
}
func SetupAPISessionStore(config *common.Config) *gin.Engine {
secretKey := make([]byte, 256)
n, _ := rand.Read(secretKey) // rand Read never returns an error, always crashes on error
log.Printf("Generated session secret key of length %d\n", n)
func SetupAPISessionStore(router *gin.Engine, config *common.Config) {
authKey := make([]byte, 32)
encrKey := make([]byte, 32)
n, _ := rand.Read(authKey) // rand Read never returns an error, always crashes on error
log.Printf("Generated cookie session authentication key of length %d\n", n)
n, _ = rand.Read(encrKey) // rand Read never returns an error, always crashes on error
log.Printf("Generated cookie session encryption key of length %d\n", n)
router := gin.Default()
store := cookie.NewStore(secretKey)
store := cookie.NewStore(authKey, encrKey)
store.Options(sessions.Options{
Path: config.SessionCookie.Path,
HttpOnly: config.SessionCookie.HttpOnly,
@@ -550,8 +106,17 @@ func SetupAPISessionStore(config *common.Config) *gin.Engine {
router.Use(sessions.Sessions(config.SessionCookieName, store))
log.Printf("Started cookie store (Name: %s Params: %+v)\n", config.SessionCookieName, config.SessionCookie)
}
return router
func GetUserSessionFromContext(c *gin.Context) (*UserSession, int, error) {
session := sessions.Default(c)
SessionUUID := session.Get("SessionUUID")
if SessionUUID == nil {
return nil, http.StatusUnauthorized, fmt.Errorf("no auth session found")
}
uuid := SessionUUID.(string)
usersession := UserSessions[uuid]
return usersession, http.StatusOK, nil
}
func GetRealmsFromPVE(config *common.Config) map[string]Realm {
+97 -23
View File
@@ -3,13 +3,46 @@ package app
import (
common "access-manager-api/app/common"
"access-manager-api/app/ldap"
proxmox "access-manager-api/app/pve"
"fmt"
"net/http"
)
func NewPool(backends *UserSession, poolname string) (int, error) {
// only pve backend handles pools
return backends.PVE.NewPool(poolname)
func NewPool(backends *UserSession, poolname string, pool common.Pool) (int, error) {
code, err := backends.PVE.NewPool(poolname, pool)
if err != nil {
return code, err
}
code, err = backends.DB.NewPool(poolname, pool)
if err != nil {
// try to undo pve add pool operation
backends.PVE.DelPool(poolname)
return code, err
}
return http.StatusOK, nil
}
func ModPool(backends *UserSession, poolname string, pool common.Pool) (int, error) {
oldpool, code, err := GetPool(backends, poolname)
if err != nil {
return code, err
}
code, err = backends.PVE.ModPool(poolname, pool)
if err != nil {
return code, err
}
code, err = backends.DB.ModPool(poolname, pool)
if err != nil {
// try to undo pve mod pool operation
backends.PVE.ModPool(poolname, oldpool)
return code, err
}
return http.StatusOK, nil
}
// get pool recursive resolving groups
@@ -25,7 +58,8 @@ func GetPool(backends *UserSession, poolname string) (common.Pool, int, error) {
if err != nil {
return pool, code, err
}
// assign pool id from PVE, assign everything else from DB
// assign pool id and pool members from PVE, assign everything else from DB
pool.PoolID = pvepool.PoolID
pool.Resources = dbpool.Resources
pool.AllowedNodes = dbpool.AllowedNodes
@@ -43,6 +77,7 @@ func GetPool(backends *UserSession, poolname string) (common.Pool, int, error) {
if err != nil {
return pool, code, err
}
// members already are filtered by PAASClientRole
group.Role = Config.PVE.PAASClientRole
pool.Groups = append(pool.Groups, group)
}
@@ -50,20 +85,42 @@ func GetPool(backends *UserSession, poolname string) (common.Pool, int, error) {
return pool, http.StatusOK, nil
}
func DelPool(backends *UserSession, poolname string) (int, error) {
// only pve backend handles pools
return backends.PVE.DelPool(poolname)
codepve, errpve := backends.PVE.DelPool(poolname)
codedb, errdb := backends.DB.DelPool(poolname)
if errpve != nil || errdb != nil {
return http.StatusInternalServerError, fmt.Errorf("error deleting pool: (pve:%d, %s) (db:%d, %s)", codepve, errpve, codedb, errdb)
} else {
return http.StatusOK, nil
}
}
func NewGroup(backends *UserSession, groupname common.Groupname) (int, error) {
func NewGroup(backends *UserSession, groupname common.Groupname, group common.Group) (int, error) {
if groupname.Realm == "pve" {
return backends.PVE.NewGroup(groupname)
return backends.PVE.NewGroup(groupname, group)
} else if groupname.Realm == backends.Realm.Name {
realm_handler := backends.Realm.Handler.(common.Backend)
code, err := realm_handler.NewGroup(groupname)
realm_handler := backends.Realm.Handler
code, err := realm_handler.NewGroup(groupname, group)
if err != nil {
return code, err
}
return backends.PVE.SyncRealms()
return backends.PVE.(proxmox.ProxmoxClient).SyncRealms()
} else {
return http.StatusUnauthorized, fmt.Errorf("user is not in the same realm as requested group")
}
}
func ModGroup(backends *UserSession, groupname common.Groupname, group common.Group) (int, error) {
if groupname.Realm == "pve" {
return backends.PVE.ModGroup(groupname, group)
} else if groupname.Realm == backends.Realm.Name {
realm_handler := backends.Realm.Handler
code, err := realm_handler.ModGroup(groupname, group)
if err != nil {
return code, err
}
return backends.PVE.(proxmox.ProxmoxClient).SyncRealms()
} else {
return http.StatusUnauthorized, fmt.Errorf("user is not in the same realm as requested group")
}
@@ -92,7 +149,7 @@ func GetGroup(backends *UserSession, groupname common.Groupname) (common.Group,
return group, http.StatusOK, nil
} else if groupname.Realm == backends.Realm.Name {
group, members, code, err := backends.Realm.Handler.(common.Backend).GetGroup(groupname)
group, members, code, err := backends.Realm.Handler.GetGroup(groupname)
if err != nil {
return common.Group{}, code, err
}
@@ -127,12 +184,12 @@ func DelGroup(backends *UserSession, groupname common.Groupname) (int, error) {
if groupname.Realm == "pve" {
return backends.PVE.DelGroup(groupname)
} else if groupname.Realm == backends.Realm.Name {
realm_handler := backends.Realm.Handler.(common.Backend)
realm_handler := backends.Realm.Handler
code, err := realm_handler.DelGroup(groupname)
if err != nil {
return code, err
}
return backends.PVE.SyncRealms()
return backends.PVE.(proxmox.ProxmoxClient).SyncRealms()
} else {
return http.StatusUnauthorized, fmt.Errorf("user is not in the same realm as requested group")
}
@@ -152,12 +209,29 @@ func NewUser(backends *UserSession, username common.Username, user common.User)
if username.Realm == "pve" {
return backends.PVE.NewUser(username, user)
} else if username.Realm == backends.Realm.Name {
realm_handler := backends.Realm.Handler.(common.Backend)
realm_handler := backends.Realm.Handler
code, err := realm_handler.NewUser(username, user)
if err != nil {
return code, err
}
return backends.PVE.SyncRealms()
return backends.PVE.(proxmox.ProxmoxClient).SyncRealms()
} else {
return http.StatusUnauthorized, fmt.Errorf("user is not in the same realm as requested user")
}
}
func ModUser(backends *UserSession, username common.Username, user common.User) (int, error) {
if username.Realm == "pve" {
return backends.PVE.ModUser(username, user)
} else if username.Realm == backends.Realm.Name {
realm_handler := backends.Realm.Handler
code, err := realm_handler.ModUser(username, user)
if err != nil {
return code, err
}
// todo, most users will not have access to sync realms, but should be able to modify their own user
// will probably use priviledge escalation to give priviledge for modify user operations
return backends.PVE.(proxmox.ProxmoxClient).SyncRealms()
} else {
return http.StatusUnauthorized, fmt.Errorf("user is not in the same realm as requested user")
}
@@ -172,7 +246,7 @@ func GetUser(backends *UserSession, username common.Username) (common.User, int,
}
return pveuser, http.StatusOK, nil
} else if username.Realm == backends.Realm.Name {
realmuser, code, err := backends.Realm.Handler.(common.Backend).GetUser(username)
realmuser, code, err := backends.Realm.Handler.GetUser(username)
if err != nil {
return common.User{}, code, err
}
@@ -186,12 +260,12 @@ func DelUser(backends *UserSession, username common.Username) (int, error) {
if username.Realm == "pve" {
return backends.PVE.DelUser(username)
} else if username.Realm == backends.Realm.Name {
realm_handler := backends.Realm.Handler.(common.Backend)
realm_handler := backends.Realm.Handler
code, err := realm_handler.DelUser(username)
if err != nil {
return code, err
}
return backends.PVE.SyncRealms()
return backends.PVE.(proxmox.ProxmoxClient).SyncRealms()
} else {
return http.StatusUnauthorized, fmt.Errorf("user is not in the same realm as requested user")
}
@@ -207,12 +281,12 @@ func AddUserToGroup(backends *UserSession, username common.Username, groupname c
// in the future support may be removed
return backends.PVE.AddUserToGroup(username, groupname)
} else if username.Realm == backends.Realm.Name && groupname.Realm == backends.Realm.Name { // both req user and req group are in realm
realm_handler := backends.Realm.Handler.(common.Backend)
realm_handler := backends.Realm.Handler
code, err := realm_handler.AddUserToGroup(username, groupname)
if err != nil {
return code, err
}
return backends.PVE.SyncRealms()
return backends.PVE.(proxmox.ProxmoxClient).SyncRealms()
} else { // req user in proxmox and req group in realm (not possible to do)
return http.StatusUnauthorized, fmt.Errorf("cannot add %s to %s", username.ToString(), groupname.ToString())
}
@@ -228,12 +302,12 @@ func DelUserFromGroup(backends *UserSession, username common.Username, groupname
// in the future support may be removed
return backends.PVE.DelUserFromGroup(username, groupname)
} else if username.Realm == backends.Realm.Name && groupname.Realm == backends.Realm.Name { // both req user and req group are in realm
realm_handler := backends.Realm.Handler.(common.Backend)
realm_handler := backends.Realm.Handler
code, err := realm_handler.DelUserFromGroup(username, groupname)
if err != nil {
return code, err
}
return backends.PVE.SyncRealms()
return backends.PVE.(proxmox.ProxmoxClient).SyncRealms()
} else { // req user in proxmox and req group in realm (not possible to do)
return http.StatusUnauthorized, fmt.Errorf("cannot delete %s from %s", username.ToString(), groupname.ToString())
}
+50 -7
View File
@@ -21,13 +21,13 @@ type ProxmoxClient struct {
func IsProxmoxNotFound(err error) bool {
if err != nil {
// for whatever reason proxmox returns 500 for user/group/pool not found
return proxmox.IsNotFound(err) || strings.Contains(err.Error(), "no such user") || strings.Contains(err.Error(), "does not exist")
return proxmox.IsNotFound(err) || strings.Contains(err.Error(), "no such") || strings.Contains(err.Error(), "does not exist")
}
return false
}
// creates a new client binding with associated permissions
func NewClientFromCredentials(config common.PVEConfig, username common.Username, password string) (*ProxmoxClient, int, error) {
func NewClientFromCredentials(config common.PVEConfig, username common.Username, password string) (common.Backend, int, error) {
HTTPClient := http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{},
@@ -47,7 +47,7 @@ func NewClientFromCredentials(config common.PVEConfig, username common.Username,
return nil, http.StatusUnauthorized, err
}
return &ProxmoxClient{config: &config, client: client}, http.StatusOK, nil
return ProxmoxClient{config: &config, client: client}, http.StatusOK, nil
}
func (pve ProxmoxClient) SyncRealms() (int, error) {
@@ -60,7 +60,7 @@ func (pve ProxmoxClient) SyncRealms() (int, error) {
for _, domain := range domains {
if domain.Type != "pam" && domain.Type != "pve" { // pam and pve are not external realm types that require sync
e := proxmox.IntOrBool(true)
r := string("acl;entry;properties")
r := "acl;entry;properties"
err := domain.Sync(context.Background(), proxmox.DomainSyncOptions{
DryRun: false, // we want to make modifications
EnableNew: &e, // allow new users and groups
@@ -77,7 +77,7 @@ func (pve ProxmoxClient) SyncRealms() (int, error) {
return http.StatusOK, nil
}
func (pve ProxmoxClient) NewPool(poolname string) (int, error) {
func (pve ProxmoxClient) NewPool(poolname string, pool common.Pool) (int, error) {
err := pve.client.NewPool(context.Background(), poolname, "")
if proxmox.IsNotAuthorized(err) {
return http.StatusUnauthorized, err
@@ -88,6 +88,11 @@ func (pve ProxmoxClient) NewPool(poolname string) (int, error) {
}
}
func (pve ProxmoxClient) ModPool(poolname string, pool common.Pool) (int, error) {
// no-op
return http.StatusOK, nil
}
func (pve ProxmoxClient) GetPool(poolname string) (common.Pool, []string, int, error) {
pool := common.Pool{}
members := []string{}
@@ -138,8 +143,8 @@ func (pve ProxmoxClient) DelPool(poolname string) (int, error) {
}
}
func (pve ProxmoxClient) NewGroup(groupname common.Groupname) (int, error) {
// add new group ny ID only
func (pve ProxmoxClient) NewGroup(groupname common.Groupname, group common.Group) (int, error) {
// add new group by ID only
err := pve.client.NewGroup(context.Background(), groupname.GroupID, "")
if proxmox.IsNotAuthorized(err) {
return http.StatusUnauthorized, err
@@ -150,6 +155,11 @@ func (pve ProxmoxClient) NewGroup(groupname common.Groupname) (int, error) {
}
}
func (pve ProxmoxClient) ModGroup(groupname common.Groupname, group common.Group) (int, error) {
// no-op
return http.StatusOK, nil
}
func (pve ProxmoxClient) GetGroup(groupname common.Groupname) (common.Group, []string, int, error) {
group := common.Group{}
members := []string{}
@@ -224,6 +234,10 @@ func (pve ProxmoxClient) DelGroupFromPool(groupname common.Groupname, poolname s
}
func (pve ProxmoxClient) NewUser(username common.Username, user common.User) (int, error) {
if !common.RequireAll(user, "Username") {
return http.StatusBadRequest, fmt.Errorf("missing one of required fields: cn, sn, mail, userpassword")
}
err := pve.client.NewUser(context.Background(), &proxmox.NewUser{
UserID: username.ToString(),
Firstname: user.CN,
@@ -240,6 +254,35 @@ func (pve ProxmoxClient) NewUser(username common.Username, user common.User) (in
}
}
func (pve ProxmoxClient) ModUser(username common.Username, user common.User) (int, error) {
if !common.AtLeastOne(user, "Username") {
return http.StatusBadRequest, fmt.Errorf("requires one of fields: cn, sn, mail, userpassword")
}
pveuser, err := pve.client.User(context.Background(), username.ToString())
if proxmox.IsNotAuthorized(err) {
return http.StatusUnauthorized, err
} else if IsProxmoxNotFound(err) {
return http.StatusNotFound, err
} else if err != nil {
return http.StatusInternalServerError, err
}
err = pveuser.Update(context.Background(), proxmox.UserOptions{
Firstname: user.CN,
Lastname: user.SN,
Email: user.Mail,
// todo userpassword (its a separate pve endpoint)
})
if proxmox.IsNotAuthorized(err) {
return http.StatusUnauthorized, err
} else if err != nil {
return http.StatusInternalServerError, err
} else {
return http.StatusOK, nil
}
}
func (pve ProxmoxClient) GetUser(username common.Username) (common.User, int, error) {
user := common.User{}
pveuser, err := pve.client.User(context.Background(), username.ToString())
+529
View File
@@ -0,0 +1,529 @@
package app
import (
common "access-manager-api/app/common"
ldap "access-manager-api/app/ldap"
"access-manager-api/app/localdb"
pve "access-manager-api/app/pve"
"fmt"
"net/http"
paas "proxmoxaas-common-lib"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
uuid "github.com/nu7hatch/gouuid"
)
func POST_Ticket(c *gin.Context) {
body := common.Login{}
err := c.ShouldBindJSON(&body)
if err != nil { // bad request from binding
c.JSON(http.StatusBadRequest, gin.H{"auth": false, "error": err.Error()})
return
}
// attempt to parse username
body.Username, err = paas.ParseUsername(body.UsernameRaw)
if err != nil { // username format incorrect
c.JSON(http.StatusBadRequest, gin.H{"auth": false, "error": err.Error()})
return
}
handler := Realms[body.Username.Realm].Type
userbackends := UserSession{}
// always bind proxmox backend
PVEClient, code, err := pve.NewClientFromCredentials(Config.PVE, body.Username, body.Password)
if err != nil { // pve client failed to bind
c.JSON(code, gin.H{"auth": false, "error": err.Error()})
return
}
userbackends.PVE = PVEClient
// bind backend by type
switch handler {
case "pve":
case "ldap":
config := Realms[body.Username.Realm].Config.(common.LDAPConfig)
LDAPClient, code, err := ldap.NewClientFromCredentials(config, body.Username, body.Password)
if err != nil { // ldap client failed to bind
c.JSON(code, gin.H{"auth": false, "error": err.Error()})
return
}
userbackends.Realm.Name = body.Username.Realm
userbackends.Realm.Handler = LDAPClient
default:
c.JSON(code, gin.H{"auth": false, "error": fmt.Errorf("user realm %s is not supported", body.Username.Realm)})
return
}
// failing to load lcoaldb is a fatal error
userbackends.DB, code, err = localdb.NewClientFromCredentials(Config.LocalDB, body.Username, body.Password)
if err != nil {
c.JSON(code, gin.H{"auth": false, "error": err.Error()})
return
}
// successful binding at this point
// create new session
session := sessions.Default(c)
// create random uuid to map user to backends
uuid, _ := uuid.NewV4()
// set uuid mapping in session
session.Set("SessionUUID", uuid.String())
// set uuid mapping in LDAPSessions
UserSessions[uuid.String()] = &userbackends
// save the session
err = session.Save()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"auth": false, "error": err.Error()})
} else {
// return successful auth
c.JSON(http.StatusOK, gin.H{"auth": true})
}
}
func DELETE_Ticket(c *gin.Context) {
// get session uuid from session cookie
session := sessions.Default(c)
SessionUUID := session.Get("SessionUUID")
if SessionUUID == nil {
c.JSON(http.StatusUnauthorized, gin.H{"auth": false})
return
}
uuid := SessionUUID.(string)
// delete uuid entry from user sessions
delete(UserSessions, uuid) // deletes uuid mapping
session.Options(sessions.Options{MaxAge: -1}) // set max age to -1 so session cookie is deleted
err := session.Save() // if save somehow fails, it should be ok since the uuid mapping is already deleted
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"auth": false})
} else {
c.JSON(http.StatusUnauthorized, gin.H{"auth": false})
}
}
func GET_Pool(c *gin.Context) {
poolid, ok := c.Params.Get("poolid")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter poolid")})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
pool, code, err := GetPool(backends, poolid)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.JSON(http.StatusOK, gin.H{"pool": pool})
}
}
func POST_Pool(c *gin.Context) {
poolid, ok := c.Params.Get("poolid")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter poolid")})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
// read request body
pool := common.Pool{}
err = c.ShouldBindJSON(&pool)
if err != nil { // failed binding, usually missing user field
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
_, code, _ = GetPool(backends, poolid) // test pool existence
if code == http.StatusNotFound { // pool does not already exist, create new pool
code, err = NewPool(backends, poolid, pool)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
} else {
code, err = ModPool(backends, poolid, pool)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
}
}
func DELETE_Pool(c *gin.Context) {
poolid, ok := c.Params.Get("poolid")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter poolid")})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
code, err = DelPool(backends, poolid)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
}
func GET_Group(c *gin.Context) {
groupname_str, ok := c.Params.Get("groupname")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter poolid")})
return
}
groupname, err := common.ParseGroupname(groupname_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
group, code, err := GetGroup(backends, groupname)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.JSON(http.StatusOK, gin.H{"group": group})
}
}
func POST_Group(c *gin.Context) {
groupname_str, ok := c.Params.Get("groupname")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter groupname")})
return
}
groupname, err := paas.ParseGroupname(groupname_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
// read request body
group := common.Group{}
err = c.ShouldBindJSON(&group)
if err != nil { // failed binding, usually missing user field
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
_, code, _ = GetGroup(backends, groupname) // test group existence
if code == http.StatusNotFound { // group does not already exist, create new group
code, err = NewGroup(backends, groupname, group)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
} else {
code, err = ModGroup(backends, groupname, group)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
}
}
func DELETE_Group(c *gin.Context) {
groupname_str, ok := c.Params.Get("groupname")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter groupname")})
return
}
groupname, err := paas.ParseGroupname(groupname_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
code, err = DelGroup(backends, groupname)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
}
func POST_Pool_Group(c *gin.Context) {
poolid, ok := c.Params.Get("poolid")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter poolid")})
return
}
groupname_str, ok := c.Params.Get("groupname")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter groupname")})
return
}
groupname, err := paas.ParseGroupname(groupname_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
code, err = AddGroupToPool(backends, groupname, poolid)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
}
func DELETE_Pool_Group(c *gin.Context) {
poolid, ok := c.Params.Get("poolid")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter poolid")})
return
}
groupname_str, ok := c.Params.Get("groupname")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter groupname")})
return
}
groupname, err := paas.ParseGroupname(groupname_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
code, err = DelGroupFromPool(backends, groupname, poolid)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
}
func GET_User(c *gin.Context) {
username_str, ok := c.Params.Get("username")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter poolid")})
return
}
username, err := common.ParseUsername(username_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
user, code, err := GetUser(backends, username)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.JSON(http.StatusOK, gin.H{"user": user})
}
}
func POST_User(c *gin.Context) {
username_str, ok := c.Params.Get("username")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter groupname")})
return
}
username, err := paas.ParseUsername(username_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
// read request body
user := common.User{}
err = c.ShouldBindJSON(&user)
if err != nil { // failed binding, usually missing user field
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
_, code, _ = GetUser(backends, username) // test user existence
if code == http.StatusNotFound { // user does not already exist, create new user
code, err = NewUser(backends, username, user)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
} else {
code, err = ModUser(backends, username, user)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
}
}
func DELETE_User(c *gin.Context) {
username_str, ok := c.Params.Get("username")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter groupname")})
return
}
username, err := paas.ParseUsername(username_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
code, err = DelUser(backends, username)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
}
func POST_Group_User(c *gin.Context) {
groupname_str, ok := c.Params.Get("groupname")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter groupname")})
return
}
username_str, ok := c.Params.Get("username")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter username")})
return
}
groupname, err := paas.ParseGroupname(groupname_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
return
}
username, err := paas.ParseUsername(username_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
code, err = AddUserToGroup(backends, username, groupname)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
}
func DELETE_Group_User(c *gin.Context) {
groupname_str, ok := c.Params.Get("groupname")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter groupname")})
return
}
username_str, ok := c.Params.Get("username")
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("missing required path parameter username")})
return
}
groupname, err := paas.ParseGroupname(groupname_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
return
}
username, err := paas.ParseUsername(username_str)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
return
}
backends, code, err := GetUserSessionFromContext(c)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
return
}
code, err = DelUserFromGroup(backends, username, groupname)
if err != nil {
c.JSON(code, gin.H{"error": err.Error()})
} else {
c.Status(http.StatusOK)
}
}
-37
View File
@@ -1,37 +0,0 @@
package app
import (
"fmt"
"net/http"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
localdb "access-manager-api/app/localdb"
pve "access-manager-api/app/pve"
)
type Realm struct {
Type string
Config any
}
type UserSession struct {
PVE *pve.ProxmoxClient
Realm struct {
Name string
Handler any
}
DB *localdb.DB
}
func GetUserSessionFromContext(c *gin.Context) (*UserSession, int, error) {
session := sessions.Default(c)
SessionUUID := session.Get("SessionUUID")
if SessionUUID == nil {
return nil, http.StatusUnauthorized, fmt.Errorf("no auth session found")
}
uuid := SessionUUID.(string)
usersession := UserSessions[uuid]
return usersession, http.StatusOK, nil
}
+3
View File
@@ -16,5 +16,8 @@
"uuid": "<secret-uuid>"
},
"paas-client-role": "<PAAS Client Role>"
},
"localdb": {
"path": "localdb.json"
}
}