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:
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -16,5 +16,8 @@
|
||||
"uuid": "<secret-uuid>"
|
||||
},
|
||||
"paas-client-role": "<PAAS Client Role>"
|
||||
},
|
||||
"localdb": {
|
||||
"path": "localdb.json"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user