From de7ac282db01c9ec543c018ed9348e4c43f437dd Mon Sep 17 00:00:00 2001 From: Arthur Lu Date: Wed, 29 Apr 2026 21:15:20 +0000 Subject: [PATCH] implement pool group and user get routes, improvements to http return codes, add localdb backend handler --- app/common/config.go | 6 ++ app/common/types.go | 11 +++ app/ldap/ldap.go | 26 ++++-- app/ldap/utils.go | 21 +++-- app/localdb/localdb.go | 46 +++++++++++ app/main.go | 111 ++++++++++++++++++++++---- app/operations.go | 112 ++++++++++++++++++++++++++ app/pve/pve.go | 174 +++++++++++++++++++++++++++++------------ app/utils.go | 29 +------ user-manager-api.go | 3 +- 10 files changed, 434 insertions(+), 105 deletions(-) create mode 100644 app/localdb/localdb.go diff --git a/app/common/config.go b/app/common/config.go index 9aeb358..4a32a09 100644 --- a/app/common/config.go +++ b/app/common/config.go @@ -16,6 +16,12 @@ type PVEConfig struct { PAASClientRole string `json:"paas-client-role"` } +type LDAPConfig struct { + BaseDN string `json:""` + LdapURL string + StartTLS bool +} + type Config struct { ListenPort int `json:"listenPort"` SessionCookieName string `json:"sessionCookieName"` diff --git a/app/common/types.go b/app/common/types.go index b7017b9..ec985cb 100644 --- a/app/common/types.go +++ b/app/common/types.go @@ -4,12 +4,15 @@ import paas "proxmoxaas-common-lib" type Backend interface { NewPool(poolname string) (int, error) + GetPool(poolname string) (Pool, []string, int, error) // []string members DelPool(poolname string) (int, error) NewGroup(groupname Groupname) (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) + GetUser(username Username) (User, int, error) DelUser(username Username) (int, error) AddUserToGroup(username Username, groupname Groupname) (int, error) DelUserFromGroup(username Username, groupname Groupname) (int, error) @@ -28,3 +31,11 @@ type SimpleLimit = paas.SimpleLimit type MatchResource = paas.MatchResource type MatchLimit = paas.MatchLimit type ResourceTemplate = paas.ResourceTemplate + +func ParseGroupname(groupname string) (Groupname, error) { + return paas.ParseGroupname(groupname) +} + +func ParseUsername(username string) (Username, error) { + return paas.ParseUsername(username) +} diff --git a/app/ldap/ldap.go b/app/ldap/ldap.go index c8de7c4..b2a686c 100644 --- a/app/ldap/ldap.go +++ b/app/ldap/ldap.go @@ -13,12 +13,12 @@ import ( // LDAPClient wrapper struct containing the connection, baseDN, peopleDN, and groupsDN type LDAPClient struct { - config *LDAPConfig + config *common.LDAPConfig client *ldap.Conn } // returns a new LDAPClient from the config -func NewClientFromCredentials(config LDAPConfig, username common.Username, password string) (*LDAPClient, int, error) { +func NewClientFromCredentials(config common.LDAPConfig, username common.Username, password string) (*LDAPClient, int, error) { LDAPConn, err := ldap.DialURL(config.LdapURL) if err != nil { return nil, http.StatusInternalServerError, err @@ -65,6 +65,7 @@ func (l LDAPClient) GetUser(username common.Username) (common.User, int, error) entry := searchResponse.Entries[0] user = LDAPEntryToUser(entry) + user.Username = username return user, http.StatusOK, nil } @@ -146,8 +147,9 @@ func (l LDAPClient) DelUser(username common.Username) (int, error) { return http.StatusOK, nil } -func (l LDAPClient) GetGroup(groupname common.Groupname) (common.Group, int, error) { +func (l LDAPClient) GetGroup(groupname common.Groupname) (common.Group, []string, int, error) { group := common.Group{} + members := []string{} searchRequest := ldap.NewSearchRequest( // setup search for user by uid fmt.Sprintf("cn=%s,ou=groups,%s", groupname.GroupID, l.config.BaseDN), // The base dn to search @@ -159,13 +161,18 @@ func (l LDAPClient) GetGroup(groupname common.Groupname) (common.Group, int, err searchResponse, err := l.client.Search(searchRequest) // perform search if err != nil { - return group, http.StatusBadRequest, err + return group, members, http.StatusBadRequest, err } entry := searchResponse.Entries[0] group = LDAPEntryToGroup(entry) - - return group, http.StatusOK, nil + group.Groupname = groupname + for _, member := range entry.GetAttributeValues("member") { + if member != "" { + members = append(members, member) + } + } + return group, members, http.StatusOK, nil } func (l LDAPClient) NewGroup(groupname common.Groupname) (int, error) { @@ -261,12 +268,19 @@ func (l LDAPClient) DelUserFromGroup(username common.Username, groupname common. func (l LDAPClient) NewPool(poolname string) (int, error) { return http.StatusNotImplemented, fmt.Errorf("ldap does not implement pools") } + +func (l LDAPClient) GetPool(poolname string) (common.Pool, []string, int, error) { + return common.Pool{}, []string{}, http.StatusNotImplemented, fmt.Errorf("ldap does not implement pools") +} + func (l LDAPClient) DelPool(poolname string) (int, error) { return http.StatusNotImplemented, fmt.Errorf("ldap does not implement pools") } + func (l LDAPClient) AddGroupToPool(groupname common.Groupname, poolname string) (int, error) { return http.StatusNotImplemented, fmt.Errorf("ldap does not implement pools") } + func (l LDAPClient) DelGroupFromPool(groupname common.Groupname, poolname string) (int, error) { return http.StatusNotImplemented, fmt.Errorf("ldap does not implement pools") } diff --git a/app/ldap/utils.go b/app/ldap/utils.go index 9ca7f14..910f628 100644 --- a/app/ldap/utils.go +++ b/app/ldap/utils.go @@ -1,18 +1,16 @@ package ldap import ( + "fmt" + "net/http" + "regexp" + "github.com/gin-gonic/gin" "github.com/go-ldap/ldap/v3" common "user-manager-api/app/common" ) -type LDAPConfig struct { - BaseDN string - LdapURL string - StartTLS bool -} - func LDAPEntryToUser(entry *ldap.Entry) common.User { return common.User{ CN: entry.GetAttributeValue("cn"), @@ -37,9 +35,18 @@ func ParseLDAPError(err error) gin.H { } else { return gin.H{ "ok": true, - "code": 200, + "code": http.StatusOK, "result": "OK", "message": "", } } } + +func ExtractUIDFromUserDN(dn string) (string, error) { + m := regexp.MustCompilePOSIX("uid=([[:alnum:]]+)") + x := m.FindStringSubmatch(dn) + if len(x) != 2 { + return "", fmt.Errorf("could not find uid in dn %s", dn) + } + return x[1], nil +} diff --git a/app/localdb/localdb.go b/app/localdb/localdb.go new file mode 100644 index 0000000..604841b --- /dev/null +++ b/app/localdb/localdb.go @@ -0,0 +1,46 @@ +package localdb + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + common "user-manager-api/app/common" +) + +type DB struct { + data map[string]common.Pool +} + +func LoadDB(localDBPath string) (DB, error) { + db := DB{} + content, err := os.ReadFile(localDBPath) + if err != nil { + //log.Fatal("Error when opening file: ", err) + return db, err + } + err = json.Unmarshal(content, &db.data) + if err != nil { + //log.Fatal("Error during Unmarshal(): ", err) + return db, err + } + return db, nil +} + +func SaveDB(localDBPath string, db DB) error { + json, err := json.Marshal(db.data) + if err != nil { + return err + } + err = os.WriteFile(localDBPath, []byte(json), 0644) + return err +} + +func (localdb DB) GetPool(poolname string) (common.Pool, []string, int, error) { + pool := common.Pool{} + pool, ok := localdb.data[poolname] + if !ok { + return pool, []string{}, http.StatusNotFound, fmt.Errorf("Pool %s not in localdb", poolname) + } + return pool, []string{}, http.StatusOK, nil +} diff --git a/app/main.go b/app/main.go index 63b06de..c33bfad 100644 --- a/app/main.go +++ b/app/main.go @@ -12,6 +12,7 @@ import ( paas "proxmoxaas-common-lib" common "user-manager-api/app/common" ldap "user-manager-api/app/ldap" + localdb "user-manager-api/app/localdb" pve "user-manager-api/app/pve" "github.com/gin-contrib/sessions" @@ -26,7 +27,7 @@ var Config common.Config var UserSessions map[string]*UserSession var Realms map[string]Realm -func Run(configPath *string) { +func Run(configPath *string, localDBPath *string) { // load config values var err error Config, err = common.GetConfig(*configPath) @@ -35,6 +36,13 @@ func Run(configPath *string) { } 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 gin.SetMode(gin.ReleaseMode) router := SetupAPISessionStore(&Config) @@ -47,7 +55,7 @@ func Run(configPath *string) { UserSessions = make(map[string]*UserSession) router.GET("/version", func(c *gin.Context) { - c.JSON(200, gin.H{"version": Version}) + c.JSON(http.StatusOK, gin.H{"version": Version}) }) router.POST("/ticket", func(c *gin.Context) { @@ -78,7 +86,7 @@ func Run(configPath *string) { // bind ldap backend if backend is ldap if handler == "ldap" { - config := Realms[body.Username.Realm].Config.(ldap.LDAPConfig) + 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()}) @@ -88,6 +96,8 @@ func Run(configPath *string) { userbackends.Realm.Handler = LDAPClient } + userbackends.DB = &db + // successful binding at this point // create new session session := sessions.Default(c) @@ -120,6 +130,27 @@ func Run(configPath *string) { 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 { @@ -137,7 +168,7 @@ func Run(configPath *string) { if err != nil { c.JSON(code, gin.H{"error": err.Error()}) } else { - c.Status(200) + c.Status(http.StatusOK) } }) @@ -158,7 +189,33 @@ func Run(configPath *string) { if err != nil { c.JSON(code, gin.H{"error": err.Error()}) } else { - c.Status(200) + c.Status(http.StatusOK) + } + }) + + router.GET("/groups/:groupid", func(c *gin.Context) { + groupid, ok := c.Params.Get("groupid") + if !ok { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("Missing required path parameter poolid")}) + return + } + + groupname, err := common.ParseGroupname(groupid) + 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}) } }) @@ -185,7 +242,7 @@ func Run(configPath *string) { if err != nil { c.JSON(code, gin.H{"error": err.Error()}) } else { - c.Status(200) + c.Status(http.StatusOK) } }) @@ -212,7 +269,7 @@ func Run(configPath *string) { if err != nil { c.JSON(code, gin.H{"error": err.Error()}) } else { - c.Status(200) + c.Status(http.StatusOK) } }) @@ -245,7 +302,7 @@ func Run(configPath *string) { if err != nil { c.JSON(code, gin.H{"error": err.Error()}) } else { - c.Status(200) + c.Status(http.StatusOK) } }) @@ -278,7 +335,33 @@ func Run(configPath *string) { if err != nil { c.JSON(code, gin.H{"error": err.Error()}) } else { - c.Status(200) + c.Status(http.StatusOK) + } + }) + + router.GET("/users/:userid", func(c *gin.Context) { + userid, ok := c.Params.Get("userid") + if !ok { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("Missing required path parameter poolid")}) + return + } + + username, err := common.ParseUsername(userid) + 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}) } }) @@ -318,7 +401,7 @@ func Run(configPath *string) { if err != nil { c.JSON(code, gin.H{"error": err.Error()}) } else { - c.Status(200) + c.Status(http.StatusOK) } }) @@ -345,7 +428,7 @@ func Run(configPath *string) { if err != nil { c.JSON(code, gin.H{"error": err.Error()}) } else { - c.Status(200) + c.Status(http.StatusOK) } }) @@ -384,7 +467,7 @@ func Run(configPath *string) { if err != nil { c.JSON(code, gin.H{"error": err.Error()}) } else { - c.Status(200) + c.Status(http.StatusOK) } }) @@ -423,7 +506,7 @@ func Run(configPath *string) { if err != nil { c.JSON(code, gin.H{"error": err.Error()}) } else { - c.Status(200) + c.Status(http.StatusOK) } }) @@ -496,7 +579,7 @@ func GetRealmsFromPVE(config *common.Config) map[string]Realm { } if realm.Type == "ldap" { - ldapconfig := ldap.LDAPConfig{ + ldapconfig := common.LDAPConfig{ BaseDN: realm.BaseDN, LdapURL: fmt.Sprintf("ldap://%s", realm.Server1), StartTLS: realm.Mode == "ldap+starttls", diff --git a/app/operations.go b/app/operations.go index ea16249..532247e 100644 --- a/app/operations.go +++ b/app/operations.go @@ -4,12 +4,51 @@ import ( "fmt" "net/http" common "user-manager-api/app/common" + "user-manager-api/app/ldap" ) func NewPool(backends *UserSession, poolname string) (int, error) { // only pve backend handles pools return backends.PVE.NewPool(poolname) } + +// get pool recursive resolving groups +func GetPool(backends *UserSession, poolname string) (common.Pool, int, error) { + pool := common.Pool{} + // get pool from PVE + pvepool, members, code, err := backends.PVE.GetPool(poolname) + if err != nil { + return pool, code, err + } + // get pool from DB + dbpool, _, code, err := backends.DB.GetPool(poolname) + if err != nil { + return pool, code, err + } + // assign pool id from PVE, assign everything else from DB + pool.PoolID = pvepool.PoolID + pool.Resources = dbpool.Resources + pool.AllowedNodes = dbpool.AllowedNodes + pool.AllowedVMIDRange = dbpool.AllowedVMIDRange + pool.AllowedBackups = dbpool.AllowedBackups + pool.Templates = dbpool.Templates + + // pool members are groups + for _, groupid := range members { + groupname, err := common.ParseGroupname(groupid) + if err != nil { + return pool, code, fmt.Errorf("pool %s had member %s which is not a valid groupname", poolname, groupid) + } + group, code, err := GetGroup(backends, groupname) + if err != nil { + return pool, code, err + } + group.Role = Config.PVE.PAASClientRole + pool.Groups = append(pool.Groups, group) + } + + return pool, http.StatusOK, nil +} func DelPool(backends *UserSession, poolname string) (int, error) { // only pve backend handles pools return backends.PVE.DelPool(poolname) @@ -30,6 +69,60 @@ func NewGroup(backends *UserSession, groupname common.Groupname) (int, error) { } } +func GetGroup(backends *UserSession, groupname common.Groupname) (common.Group, int, error) { + // resolve group from relevant backend + if groupname.Realm == "pve" { + group, members, code, err := backends.PVE.GetGroup(groupname) + if err != nil { + return group, code, err + } + // group members are users + for _, userid := range members { + username, err := common.ParseUsername(userid) + if err != nil { + return group, http.StatusInternalServerError, fmt.Errorf("group %s had member %s which is not a valid username", groupname.ToString(), userid) + } + // fetch and append user + user, code, err := GetUser(backends, username) + if err != nil { + return group, code, err + } + group.Users = append(group.Users, user) + } + + return group, http.StatusOK, nil + } else if groupname.Realm == backends.Realm.Name { + group, members, code, err := backends.Realm.Handler.(common.Backend).GetGroup(groupname) + if err != nil { + return common.Group{}, code, err + } + // group mambers are users + for _, userdn := range members { + // member list is of ldap user DN instead of pve userid + ldapuid, err := ldap.ExtractUIDFromUserDN(userdn) + if err != nil { + return group, http.StatusInternalServerError, fmt.Errorf("group %s had member %s which is not a valid user DN", groupname.ToString(), userdn) + } + // generate username from user DN (slightly inefficient) + userid := fmt.Sprintf("%s@%s", ldapuid, backends.Realm.Name) + username, err := common.ParseUsername(userid) + if err != nil { + return group, http.StatusInternalServerError, fmt.Errorf("group %s had member %s which is not a valid username", groupname.ToString(), userid) + } + // fetch and append user + user, code, err := GetUser(backends, username) + if err != nil { + return group, code, err + } + group.Users = append(group.Users, user) + } + + return group, http.StatusOK, nil + } else { + return common.Group{}, http.StatusUnauthorized, fmt.Errorf("user is not in the same realm as requested group") + } +} + func DelGroup(backends *UserSession, groupname common.Groupname) (int, error) { if groupname.Realm == "pve" { return backends.PVE.DelGroup(groupname) @@ -70,6 +163,25 @@ func NewUser(backends *UserSession, username common.Username, user common.User) } } +func GetUser(backends *UserSession, username common.Username) (common.User, int, error) { + // fetch user from relevant realm + if username.Realm == "pve" { + pveuser, code, err := backends.PVE.GetUser(username) + if err != nil { + return common.User{}, code, err + } + return pveuser, http.StatusOK, nil + } else if username.Realm == backends.Realm.Name { + realmuser, code, err := backends.Realm.Handler.(common.Backend).GetUser(username) + if err != nil { + return common.User{}, code, err + } + return realmuser, http.StatusOK, nil + } else { + return common.User{}, http.StatusUnauthorized, fmt.Errorf("user is not in the same realm as requested user") + } +} + func DelUser(backends *UserSession, username common.Username) (int, error) { if username.Realm == "pve" { return backends.PVE.DelUser(username) diff --git a/app/pve/pve.go b/app/pve/pve.go index bd043c6..22488ca 100644 --- a/app/pve/pve.go +++ b/app/pve/pve.go @@ -39,16 +39,15 @@ func NewClientFromCredentials(config common.PVEConfig, username common.Username, return nil, http.StatusUnauthorized, err } - // todo this should return an error code if the binding failed (ie fetch version to check if the auth was actually ok) return &ProxmoxClient{config: &config, client: client}, http.StatusOK, nil } func (pve ProxmoxClient) SyncRealms() (int, error) { domains, err := pve.client.Domains(context.Background()) if proxmox.IsNotAuthorized(err) { - return 401, err + return http.StatusUnauthorized, err } else if err != nil { - return 500, err + return http.StatusInternalServerError, err } for _, domain := range domains { if domain.Type != "pam" && domain.Type != "pve" { // pam and pve are not external realm types that require sync @@ -59,41 +58,73 @@ func (pve ProxmoxClient) SyncRealms() (int, error) { RemoveVanished: "acl;entry;properties", // remove deleted objects from ACL, entry in pve, and remove properties (probably not necessary) }) if proxmox.IsNotAuthorized(err) { - return 401, err + return http.StatusUnauthorized, err } else if err != nil { - return 500, err + return http.StatusInternalServerError, err } } } - return 200, nil + return http.StatusOK, nil } func (pve ProxmoxClient) NewPool(poolname string) (int, error) { err := pve.client.NewPool(context.Background(), poolname, "") if proxmox.IsNotAuthorized(err) { - return 401, err + return http.StatusUnauthorized, err } else if err != nil { - return 500, err + return http.StatusInternalServerError, err } else { - return 200, nil + return http.StatusOK, nil } } +func (pve ProxmoxClient) GetPool(poolname string) (common.Pool, []string, int, error) { + pool := common.Pool{} + members := []string{} + + pvepool, err := pve.client.Pool(context.Background(), poolname) + if proxmox.IsNotFound(err) { // errors if pool does not exist + return pool, members, http.StatusNotFound, err + } else if err != nil { + return pool, members, http.StatusInternalServerError, err + } + + pool.PoolID = pvepool.PoolID + + acls, err := pve.client.ACL(context.Background()) + if proxmox.IsNotAuthorized(err) { // errors if pool does not exist + return pool, members, http.StatusUnauthorized, err + } else if err != nil { + return pool, members, http.StatusInternalServerError, err + } + + // iterate through ACLs and get superficial pool - group membershipm (just group names) + for _, acl := range acls { + if acl.Type == "group" && acl.RoleID == pve.config.PAASClientRole && acl.Path == fmt.Sprintf("/pool/%s", poolname) { + members = append(members, acl.UGID) + } + } + + return pool, members, http.StatusOK, nil +} + func (pve ProxmoxClient) DelPool(poolname string) (int, error) { pvepool, err := pve.client.Pool(context.Background(), poolname) - if proxmox.IsNotFound(err) { // errors if pool does not exist - return 404, err + if proxmox.IsNotAuthorized(err) { // not authorized to delete + return http.StatusUnauthorized, err + } else if proxmox.IsNotFound(err) { // errors if pool does not exist + return http.StatusNotFound, err } else if err != nil { - return 500, err + return http.StatusInternalServerError, err } err = pvepool.Delete(context.Background()) if proxmox.IsNotAuthorized(err) { // not authorized to delete - return 401, err + return http.StatusUnauthorized, err } else if err != nil { - return 500, err + return http.StatusInternalServerError, err } else { - return 200, nil + return http.StatusOK, nil } } @@ -101,29 +132,50 @@ func (pve ProxmoxClient) NewGroup(groupname common.Groupname) (int, error) { // add new group ny ID only err := pve.client.NewGroup(context.Background(), groupname.GroupID, "") if proxmox.IsNotAuthorized(err) { - return 401, err + return http.StatusUnauthorized, err } else if err != nil { - return 500, err + return http.StatusInternalServerError, err } else { - return 200, nil + return http.StatusOK, nil } } +func (pve ProxmoxClient) GetGroup(groupname common.Groupname) (common.Group, []string, int, error) { + group := common.Group{} + members := []string{} + pvegroup, err := pve.client.Group(context.Background(), groupname.ToString()) + if proxmox.IsNotFound(err) { // errors if pool does not exist + return group, members, http.StatusNotFound, err + } else if err != nil { + return group, members, http.StatusInternalServerError, err + } + + group.Groupname, _ = common.ParseGroupname(pvegroup.GroupID) + + for _, userid := range pvegroup.Members { + members = append(members, userid) + } + + return group, members, http.StatusOK, nil +} + func (pve ProxmoxClient) DelGroup(groupname common.Groupname) (int, error) { pvegroup, err := pve.client.Group(context.Background(), groupname.GroupID) - if proxmox.IsNotFound(err) { // errors if group does not exist - return 404, err + if proxmox.IsNotAuthorized(err) { + return http.StatusUnauthorized, err + } else if proxmox.IsNotFound(err) { + return http.StatusNotFound, err } else if err != nil { - return 500, err + return http.StatusInternalServerError, err } err = pvegroup.Delete(context.Background()) if proxmox.IsNotAuthorized(err) { // not authorized to delete - return 401, err + return http.StatusUnauthorized, err } else if err != nil { - return 500, err + return http.StatusInternalServerError, err } else { - return 200, nil + return http.StatusOK, nil } } @@ -137,11 +189,11 @@ func (pve ProxmoxClient) AddGroupToPool(groupname common.Groupname, poolname str }) if proxmox.IsNotAuthorized(err) { - return 401, err + return http.StatusUnauthorized, err } else if err != nil { - return 500, err + return http.StatusInternalServerError, err } else { - return 200, nil + return http.StatusOK, nil } } @@ -155,11 +207,11 @@ func (pve ProxmoxClient) DelGroupFromPool(groupname common.Groupname, poolname s }) if proxmox.IsNotAuthorized(err) { - return 401, err + return http.StatusUnauthorized, err } else if err != nil { - return 500, err + return http.StatusInternalServerError, err } else { - return 200, nil + return http.StatusOK, nil } } @@ -172,39 +224,59 @@ func (pve ProxmoxClient) NewUser(username common.Username, user common.User) (in Password: user.Password, }) if proxmox.IsNotAuthorized(err) { - return 401, err + return http.StatusUnauthorized, err } else if err != nil { - return 500, err + return http.StatusInternalServerError, err } else { - return 200, nil + 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()) + if proxmox.IsNotFound(err) { // errors if pool does not exist + return user, http.StatusNotFound, err + } else if err != nil { + return user, http.StatusInternalServerError, err + } else { + user.Username, _ = common.ParseUsername(pveuser.UserID) + user.CN = pveuser.Firstname + user.SN = pveuser.Lastname + user.Mail = pveuser.Email + return user, http.StatusOK, nil } } func (pve ProxmoxClient) DelUser(username common.Username) (int, error) { user, err := pve.client.User(context.Background(), username.ToString()) if proxmox.IsNotAuthorized(err) { - return 401, err // not authorized to read the user = not authorized to delete the user, this may be slightly different from ldap + return http.StatusUnauthorized, err + } else if proxmox.IsNotFound(err) { + return http.StatusNotFound, err } else if err != nil { - return 500, err + return http.StatusInternalServerError, err } // assume that user cannot be nil if no error was returned err = user.Delete(context.Background()) if proxmox.IsNotAuthorized(err) { - return 401, err // not authorized to delete the user + return http.StatusUnauthorized, err // not authorized to delete the user } else if err != nil { - return 500, err + return http.StatusInternalServerError, err } else { - return 200, nil + return http.StatusOK, nil } } func (pve ProxmoxClient) AddUserToGroup(username common.Username, groupname common.Groupname) (int, error) { - user, err := pve.client.User(context.Background(), groupname.ToString()) + user, err := pve.client.User(context.Background(), username.ToString()) if proxmox.IsNotAuthorized(err) { - return 401, err // not authorized to read the user = not authorized to delete the user, this may be slightly different from ldap + return http.StatusUnauthorized, err + } else if proxmox.IsNotFound(err) { + return http.StatusNotFound, err } else if err != nil { - return 500, err + return http.StatusInternalServerError, err } newGroups := append(user.Groups, groupname.ToString()) @@ -213,20 +285,22 @@ func (pve ProxmoxClient) AddUserToGroup(username common.Username, groupname comm Groups: newGroups, }) if proxmox.IsNotAuthorized(err) { - return 401, err // not authorized to delete the user + return http.StatusUnauthorized, err // not authorized to delete the user } else if err != nil { - return 500, err + return http.StatusInternalServerError, err } else { - return 200, nil + return http.StatusOK, nil } } func (pve ProxmoxClient) DelUserFromGroup(username common.Username, groupname common.Groupname) (int, error) { - user, err := pve.client.User(context.Background(), groupname.ToString()) + user, err := pve.client.User(context.Background(), username.ToString()) if proxmox.IsNotAuthorized(err) { - return 401, err // not authorized to read the user = not authorized to delete the user, this may be slightly different from ldap + return http.StatusUnauthorized, err + } else if proxmox.IsNotFound(err) { + return http.StatusNotFound, err } else if err != nil { - return 500, err + return http.StatusInternalServerError, err } idx := slices.Index(user.Groups, groupname.ToString()) @@ -239,10 +313,10 @@ func (pve ProxmoxClient) DelUserFromGroup(username common.Username, groupname co Groups: newGroups, }) if proxmox.IsNotAuthorized(err) { - return 401, err // not authorized to delete the user + return http.StatusUnauthorized, err // not authorized to delete the user } else if err != nil { - return 500, err + return http.StatusInternalServerError, err } else { - return 200, nil + return http.StatusOK, nil } } diff --git a/app/utils.go b/app/utils.go index a840c4f..2a02a29 100644 --- a/app/utils.go +++ b/app/utils.go @@ -1,15 +1,13 @@ package app import ( - "encoding/json" "fmt" "net/http" - "os" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" - common "user-manager-api/app/common" + localdb "user-manager-api/app/localdb" pve "user-manager-api/app/pve" ) @@ -24,6 +22,7 @@ type UserSession struct { Name string Handler any } + DB *localdb.DB } func GetUserSessionFromContext(c *gin.Context) (*UserSession, int, error) { @@ -36,27 +35,3 @@ func GetUserSessionFromContext(c *gin.Context) (*UserSession, int, error) { usersession := UserSessions[uuid] return usersession, http.StatusOK, nil } - -func LoadLocaldb(dbPath string) (map[string]common.User, error) { - users := map[string]common.User{} - content, err := os.ReadFile(dbPath) - if err != nil { - //log.Fatal("Error when opening file: ", err) - return users, err - } - err = json.Unmarshal(content, &users) - if err != nil { - //log.Fatal("Error during Unmarshal(): ", err) - return users, err - } - return users, nil -} - -func SaveLocaldb(configPath string, users map[string]common.User) error { - json, err := json.Marshal(users) - if err != nil { - return err - } - err = os.WriteFile(configPath, []byte(json), 0644) - return err -} diff --git a/user-manager-api.go b/user-manager-api.go index eee70f9..cfde46d 100644 --- a/user-manager-api.go +++ b/user-manager-api.go @@ -7,6 +7,7 @@ import ( func main() { configPath := flag.String("config", "config.json", "path to config.json file") + localDBPath := flag.String("localdb", "localdb.json", "path to localdb.json file") flag.Parse() - app.Run(configPath) + app.Run(configPath, localDBPath) }