diff --git a/README.md b/README.md index 86a8e6f..5beb838 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,9 @@ Data } ``` -This operation can only be performed by a user with a role that is admin enabled. There are 3 built in roles, which can be viewed via the `/api/admin/roles` endpoint. +OLD: This operation can only be performed by a user with a role that is admin enabled. There are 3 built in roles, which can be viewed via the `/api/admin/roles` endpoint. + +NEW: This operation can only be performed by a user that is a member of a group with the admin flag enabled. #### Remove User POST `/api/admin/user/delete` diff --git a/controllers/auth.go b/controllers/auth.go index 5469420..3cf6393 100644 --- a/controllers/auth.go +++ b/controllers/auth.go @@ -16,9 +16,11 @@ import ( ) type RegisterInput struct { - UserName string `json:"userName" binding:"required"` - Password string `json:"password" binding:"required"` - RoleId int `json:"roleid"` + UserName string `json:"userName" binding:"required"` + Password string `json:"password" binding:"required"` + GroupId int `json:"groupId"` + GroupName string `json:"groupName"` + //RoleId int `json:"roleid"` } type LoginInput struct { @@ -52,7 +54,7 @@ func DeleteUser(c *gin.Context) { u.UserName = html.EscapeString(strings.TrimSpace(u.UserName)) // Confirm user account exists - testUser, _ := models.GetUserByName(u.UserName) + testUser, _ := models.UserGetByName(u.UserName) log.Printf("DeleteUser confirming user '%s' account exists\n", u.UserName) if (models.User{} == testUser) { err := errors.New("attempt to delete non-existing username '" + u.UserName + "'") @@ -90,23 +92,51 @@ func RegisterUser(c *gin.Context) { } u := models.User{} - //u.RoleId = 1 u.UserName = input.UserName u.Password = input.Password - // Default to regular user role if not specified - if input.RoleId == 0 { - log.Printf("Register no role specified, defaulting to builtin role UserRole with id 2.\n") - u.RoleId = 2 + // Determine which GroupId to save + // Can be specified either by GroupName or GroupId in the request + if len(input.GroupName) > 0 { + g, err := models.GroupGetByName(input.GroupName) + if err != nil { + errString := fmt.Sprintf("RegisterUser error looking up group by name : '%s'", err) + log.Println(errString) + c.JSON(http.StatusBadRequest, gin.H{"error": errString}) + return + } + if g == (models.Group{}) { + errString := fmt.Sprintf("RegisterUser specified group not found") + log.Println(errString) + c.JSON(http.StatusBadRequest, gin.H{"error": errString}) + return + } else { + u.GroupId = g.GroupId + } + } else if input.GroupId > 0 { + u.GroupId = input.GroupId } else { - u.RoleId = input.RoleId + errString := fmt.Sprintf("RegisterUser no group specified, must specify either GroupId or GroupName") + log.Println(errString) + c.JSON(http.StatusBadRequest, gin.H{"error": errString}) + return } + /* + // Default to regular user role if not specified + if input.RoleId == 0 { + log.Printf("Register no role specified, defaulting to builtin role UserRole with id 2.\n") + u.RoleId = 2 + } else { + u.RoleId = input.RoleId + } + */ + //remove spaces in username u.UserName = html.EscapeString(strings.TrimSpace(u.UserName)) // Check if user already exists - testUser, _ := models.GetUserByName(u.UserName) + testUser, _ := models.UserGetByName(u.UserName) log.Printf("Register checking if user '%s' already exists\n", u.UserName) if (models.User{} == testUser) { log.Printf("Register confirmed no existing username\n") @@ -214,7 +244,7 @@ func CurrentUser(c *gin.Context) { return } - u, err := models.GetUserByID(user_id) + u, err := models.UserGetByID(user_id) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) @@ -237,7 +267,7 @@ func GetRoles(c *gin.Context) { } func GetUsers(c *gin.Context) { - users, err := models.QueryUsers() + users, err := models.UserList() if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) diff --git a/controllers/retrieve_secrets.go b/controllers/retrieve_secrets.go index 77856c1..05167ef 100644 --- a/controllers/retrieve_secrets.go +++ b/controllers/retrieve_secrets.go @@ -13,11 +13,14 @@ type RetrieveInput struct { DeviceName string `json:"deviceName"` DeviceCategory string `json:"deviceCategory"` UserName string `json:"userName"` + SafeId int `json:"safeId"` + SafeName string `json:"safeName"` } type ListSecret struct { - SecretId int `db:"SecretId" json:"-"` - RoleId int `db:"RoleId" json:"-"` + SecretId int `db:"SecretId" json:"-"` + //RoleId int `db:"RoleId" json:"-"` + SafeId int `db:"SafeId"` DeviceName string `db:"DeviceName"` DeviceCategory string `db:"DeviceCategory"` UserName string `db:"UserName"` @@ -27,6 +30,7 @@ type ListSecret struct { func RetrieveSecret(c *gin.Context) { var input RetrieveInput var results []models.Secret + var userIsAdmin bool = false // Validate the input matches our struct if err := c.ShouldBindJSON(&input); err != nil { @@ -35,22 +39,55 @@ func RetrieveSecret(c *gin.Context) { } log.Printf("RetrieveSecret received JSON input '%v'\n", input) - // Get the user and role id of the requestor - u, err := models.GetUserRoleFromToken(c) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } + /* + // Get the user and role id of the requestor + u, err := models.UserGetRoleFromToken(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + */ // Populate fields s := models.Secret{} - s.RoleId = u.RoleId + //s.RoleId = u.RoleId s.DeviceName = input.DeviceName s.DeviceCategory = input.DeviceCategory s.UserName = input.UserName - // Don't apply a role filter if user has admin role - results, err = models.GetSecrets(&s, u.Admin) + user_id, err := token.ExtractTokenID(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"}) + return + } + + // Work out which safe to query for this user if the safe was not specified + safeList, err := models.UserGetSafesAllowed(int(user_id)) + + // If there was only one result then just use that + if len(safeList) == 0 { + // check if the user is an admin, if not then they seem to have access to zero safes + if !models.UserCheckIfAdmin(int(user_id)) { + c.JSON(http.StatusBadRequest, gin.H{"error": "user has no access to any secrets"}) + return + } + // Don't apply a role filter if user has admin role + results, err = models.GetSecrets(&s, userIsAdmin) + } else if len(safeList) == 1 { + s.SafeId = safeList[0].SafeId + userIsAdmin = safeList[0].AdminUser || safeList[0].AdminGroup + // Don't apply a role filter if user has admin role + results, err = models.GetSecrets(&s, userIsAdmin) + } else { + // TODO - this is tricky. How to query multiple safes? + + var safeIds []int + for _, safe := range safeList { + safeIds = append(safeIds, safe.SafeId) + } + + results, err = models.SecretsGetMultipleSafes(&s, false, safeIds) + } if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) @@ -99,18 +136,56 @@ func RetrieveSecretByDevicecategory(c *gin.Context) { } func retrieveSpecifiedSecret(s *models.Secret, c *gin.Context) { - // Get the user and role id of the requestor - u, err := models.GetUserRoleFromToken(c) + /* + // Get the user and role id of the requestor + u, err := models.UserGetRoleFromToken(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + s.RoleId = u.RoleId + + results, err := models.GetSecrets(s, false) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + */ + + var results []models.Secret + var userIsAdmin = false + user_id, err := token.ExtractTokenID(c) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"}) return } - s.RoleId = u.RoleId - results, err := models.GetSecrets(s, false) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return + // Work out which safe to query for this user if the safe was not specified + safeList, err := models.UserGetSafesAllowed(int(user_id)) + + // If there was only one result then just use that + if len(safeList) == 0 { + // check if the user is an admin, if not then they seem to have access to zero safes + if !models.UserCheckIfAdmin(int(user_id)) { + c.JSON(http.StatusBadRequest, gin.H{"error": "user has no access to any secrets"}) + return + } + // Don't apply a role filter if user has admin role + results, err = models.GetSecrets(s, userIsAdmin) + } else if len(safeList) == 1 { + s.SafeId = safeList[0].SafeId + userIsAdmin = safeList[0].AdminUser || safeList[0].AdminGroup + // Don't apply a role filter if user has admin role + results, err = models.GetSecrets(s, userIsAdmin) + } else { + // TODO - this is tricky. How to query multiple safes? + + var safeIds []int + for _, safe := range safeList { + safeIds = append(safeIds, safe.SafeId) + } + + results, err = models.SecretsGetMultipleSafes(s, false, safeIds) } if len(results) == 1 { @@ -125,69 +200,75 @@ func retrieveSpecifiedSecret(s *models.Secret, c *gin.Context) { } } func ListSecrets(c *gin.Context) { - var results []models.Secret + var output []ListSecret + // TODO implement with new schema + /* + var results []models.Secret + // Get the user and role id of the requestor + u, err := models.UserGetRoleFromToken(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } - // Get the user and role id of the requestor - u, err := models.GetUserRoleFromToken(c) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } + // If user is admin then list everything, otherwise only list for current role + results, err = models.GetSecrets(&models.Secret{RoleId: u.RoleId}, u.Admin) - // If user is admin then list everything, otherwise only list for current role - results, err = models.GetSecrets(&models.Secret{RoleId: u.RoleId}, u.Admin) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - for _, v := range results { - output = append(output, ListSecret(v)) - } + for _, v := range results { + output = append(output, ListSecret(v)) + } + */ // output results as json c.JSON(http.StatusOK, gin.H{"message": "success", "data": output}) } func RetrieveMultpleSecrets(c *gin.Context) { - var input RetrieveInput + // TODO implement with new schema + /* + var input RetrieveInput - // Validate the input matches our struct - if err := c.ShouldBindJSON(&input); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - log.Printf("StoreSecret received JSON input '%v'\n", input) + // Validate the input matches our struct + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + log.Printf("StoreSecret received JSON input '%v'\n", input) - // Get the user and role id of the requestor - user_id, err := token.ExtractTokenID(c) + // Get the user and role id of the requestor + user_id, err := token.ExtractTokenID(c) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } - u, err := models.GetUserRoleByID(user_id) + u, err := models.GetUserRoleByID(user_id) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } - // Populate fields - s := models.Secret{} - s.RoleId = u.RoleId - s.DeviceName = input.DeviceName - s.DeviceCategory = input.DeviceCategory + // Populate fields + s := models.Secret{} + s.RoleId = u.RoleId + s.DeviceName = input.DeviceName + s.DeviceCategory = input.DeviceCategory - results, err := models.GetSecrets(&s, false) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } + results, err := models.GetSecrets(&s, false) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } - // output results as json - c.JSON(http.StatusOK, gin.H{"message": "success", "data": results}) + // output results as json + c.JSON(http.StatusOK, gin.H{"message": "success", "data": results}) + */ } diff --git a/controllers/store_secrets.go b/controllers/store_secrets.go index 0c5e331..b2f58db 100644 --- a/controllers/store_secrets.go +++ b/controllers/store_secrets.go @@ -5,13 +5,16 @@ import ( "log" "net/http" "smt/models" + "smt/utils/token" "github.com/gin-gonic/gin" ) // bindings are validated by https://github.com/go-playground/validator type StoreInput struct { - RoleId int `json:"roleId"` + //RoleId int `json:"roleId"` + SafeId int `json:"safeId"` + SafeName string `json:"safeName"` DeviceName string `json:"deviceName"` DeviceCategory string `json:"deviceCategory"` UserName string `json:"userName" binding:"required"` @@ -36,15 +39,32 @@ func StoreSecret(c *gin.Context) { s.DeviceName = input.DeviceName s.DeviceCategory = input.DeviceCategory - // If RoleID is not defined then default to the same role as the user requesting secret to be stored - if input.RoleId != 0 { - s.RoleId = input.RoleId - } else { - ur, _ := models.GetUserRoleFromToken(c) - log.Printf("StoreSecret RoleId was not specified, setting to RoleId of '%d'\n", ur.RoleId) - s.RoleId = ur.RoleId + // Query which safes the current user is allowed to access + user_id, err := token.ExtractTokenID(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"}) + return } + safeId := SecretCheckSafeAllowed(int(user_id), input) + if safeId == 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "error determining safe"}) + return + } + + s.SafeId = safeId + + /* + // If RoleID is not defined then default to the same role as the user requesting secret to be stored + if input.RoleId != 0 { + s.RoleId = input.RoleId + } else { + ur, _ := models.GetUserRoleFromToken(c) + log.Printf("StoreSecret RoleId was not specified, setting to RoleId of '%d'\n", ur.RoleId) + s.RoleId = ur.RoleId + } + */ + if input.DeviceCategory == "" && input.DeviceName == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "cannot store secret with empty deviceName and empty deviceCategory"}) return @@ -91,17 +111,19 @@ func UpdateSecret(c *gin.Context) { log.Printf("UpdateSecret received JSON input '%v'\n", input) - // Get the user and role id of the requestor - u, err := models.GetUserRoleFromToken(c) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - // Verify that the user role is not readonly - if u.ReadOnly { - c.JSON(http.StatusForbidden, gin.H{"error": "UpdateSecret user role does not permit updates"}) - return - } + /* + // Get the user and role id of the requestor + u, err := models.UserGetRoleFromToken(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + // Verify that the user role is not readonly + if u.ReadOnly { + c.JSON(http.StatusForbidden, gin.H{"error": "UpdateSecret user role does not permit updates"}) + return + } + */ // Populate fields s := models.Secret{} @@ -110,13 +132,30 @@ func UpdateSecret(c *gin.Context) { s.DeviceName = input.DeviceName s.DeviceCategory = input.DeviceCategory - // Default role ID is 1 if not defined - if input.RoleId != 0 { - s.RoleId = input.RoleId - } else { - s.RoleId = 1 + /* + // Default role ID is 1 if not defined + if input.RoleId != 0 { + s.RoleId = input.RoleId + } else { + s.RoleId = 1 + } + */ + + // Query which safes the current user is allowed to access + user_id, err := token.ExtractTokenID(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"}) + return } + safeId := SecretCheckSafeAllowed(int(user_id), input) + if safeId == 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "error determining safe"}) + return + } + + s.SafeId = safeId + // Confirm that the secret already exists checkExists, err := models.GetSecrets(&s, false) if err != nil { @@ -165,3 +204,36 @@ func UpdateSecret(c *gin.Context) { } } + +func SecretCheckSafeAllowed(user_id int, input StoreInput) int { + // Query which safes the current user is allowed to access + + // SafeId is by default the same as the safe that the user belongs to + safeList, err := models.UserGetSafesAllowed(user_id) + if err != nil { + log.Printf("SecretCheckSafeAllowed error determining allowed safes for userId %d : '%s'\n", user_id, err) + return 0 + } + + // Verify user has access to specified safe + for _, safe := range safeList { + if len(input.SafeName) > 0 && safe.SafeName == input.SafeName { // Safe specifed by name + return safe.SafeId + } else if input.SafeId > 0 && safe.SafeId == input.SafeId { // Safe specified by id + return safe.SafeId + } + } + + // TODO what about Admin role + + return 0 + + /* + if !matchFound { + errString := "no safe specified or no access to specified safe" + log.Println(errString) + c.JSON(http.StatusBadRequest, gin.H{"error": errString}) + return + } + */ +} diff --git a/middlewares/middlewares.go b/middlewares/middlewares.go index fae48a0..766721c 100644 --- a/middlewares/middlewares.go +++ b/middlewares/middlewares.go @@ -44,17 +44,20 @@ func JwtAuthAdminMiddleware() gin.HandlerFunc { return } - ur, err := models.GetUserRoleByID(user_id) + // TODO determine user role + + //ur, err := models.GetUserRoleByID(user_id) + ug, err := models.UserGetGroupByID(user_id) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.Abort() return } - log.Printf("JwtAuthAdminMiddleware retrieved UserRole object for UserId '%d'\n", ur.UserId) + log.Printf("JwtAuthAdminMiddleware retrieved UserGroup object for UserId '%d'\n", ug.UserId) // Verify that the user has a role with the admin flag set - if !ur.Admin { + if !ug.Admin { c.String(http.StatusUnauthorized, "User role is Non-Admin") c.Abort() return diff --git a/models/audit.go b/models/audit.go new file mode 100644 index 0000000..4ecbc54 --- /dev/null +++ b/models/audit.go @@ -0,0 +1,3 @@ +package models + +// Define audit functions diff --git a/models/group.go b/models/group.go new file mode 100644 index 0000000..f8a4314 --- /dev/null +++ b/models/group.go @@ -0,0 +1,55 @@ +package models + +import ( + "errors" + "log" +) + +type Group struct { + GroupId int `db:"GroupId"` + GroupName string `db:"GroupName"` + LdapGroup bool `db:"LdapGroup"` + LdapDn string `db:"LdapDN"` + Admin bool `db:"Admin"` +} + +// GroupGetByName queries the database for the specified group name +func GroupGetByName(groupname string) (Group, error) { + var g Group + + // Query database for matching group object + err := db.QueryRowx("SELECT * FROM groups WHERE GroupName=?", groupname).StructScan(&g) + if err != nil { + return g, errors.New("group not found") + } + + return g, nil +} + +// GroupList returns a list of all groups in database +func GroupList() ([]Group, error) { + var results []Group + + // Query database for role definitions + rows, err := db.Queryx("SELECT * FROM groups") + + if err != nil { + log.Printf("GroupList error executing sql record : '%s'\n", err) + return results, err + } else { + // parse all the results into a slice + for rows.Next() { + var g Group + err = rows.StructScan(&g) + if err != nil { + log.Printf("GroupList error parsing sql record : '%s'\n", err) + return results, err + } + results = append(results, g) + + } + log.Printf("GroupList retrieved '%d' results\n", len(results)) + } + + return results, nil +} diff --git a/models/ldap.go b/models/ldap.go index 3ddcb6b..e583b84 100644 --- a/models/ldap.go +++ b/models/ldap.go @@ -227,7 +227,8 @@ func LookupNamingContext(ldaps *ldap.Conn) string { return defaultNamingContext } -func GetLdapGroupMembership(username string, password string) ([]string, error) { +// LdapGetGroupMembership returns a list of distinguishedNames for groups that a user is a member of +func LdapGetGroupMembership(username string, password string) ([]string, error) { var err error username = CheckUsername(username) diff --git a/models/permissions.go b/models/permissions.go new file mode 100644 index 0000000..f9cca50 --- /dev/null +++ b/models/permissions.go @@ -0,0 +1,8 @@ +package models + +type Permission struct { + PermissionId int `db:"PermissionId"` + RoleId int `db:"RoleId"` + SafeId int `db:"SafeId"` + GroupId int `db:"GroupId"` +} diff --git a/models/safe.go b/models/safe.go new file mode 100644 index 0000000..f86e73c --- /dev/null +++ b/models/safe.go @@ -0,0 +1,6 @@ +package models + +type Safe struct { + SafeId int `db:"SafeId"` + SafeName string `db:"SafeName"` +} diff --git a/models/secret.go b/models/secret.go index b4685be..e6e0111 100644 --- a/models/secret.go +++ b/models/secret.go @@ -6,16 +6,19 @@ import ( "crypto/rand" "encoding/hex" "errors" + "fmt" "io" "log" + "strings" "github.com/jmoiron/sqlx" ) // We use the json:"-" field tag to prevent showing these details to the user type Secret struct { - SecretId int `db:"SecretId" json:"-"` - RoleId int `db:"RoleId" json:"-"` + SecretId int `db:"SecretId" json:"-"` + //RoleId int `db:"RoleId" json:"-"` + SafeId int `db:"SafeId"` DeviceName string `db:"DeviceName"` DeviceCategory string `db:"DeviceCategory"` UserName string `db:"UserName"` @@ -29,7 +32,7 @@ func (s *Secret) SaveSecret() (*Secret, error) { var err error log.Printf("SaveSecret storing values '%v'\n", s) - result, err := db.NamedExec((`INSERT INTO secrets (RoleId, DeviceName, DeviceCategory, UserName, Secret) VALUES (:RoleId, :DeviceName, :DeviceCategory, :UserName, :Secret)`), s) + result, err := db.NamedExec((`INSERT INTO secrets (SafeId, DeviceName, DeviceCategory, UserName, Secret) VALUES (:SafeId, :DeviceName, :DeviceCategory, :UserName, :Secret)`), s) if err != nil { log.Printf("StoreSecret error executing sql record : '%s'\n", err) @@ -43,6 +46,96 @@ func (s *Secret) SaveSecret() (*Secret, error) { return s, nil } +// TODO - use this function when user has access to multiple safes +func SecretsGetMultipleSafes(s *Secret, adminRole bool, safeIds []int) ([]Secret, error) { + var err error + var secretResults []Secret + + /* + // First use an "In Query" to expand the list of safe Ids to query + // As per https://jmoiron.github.io/sqlx/#inQueries + + query, args, _ := sqlx.In("SELECT * FROM secrets WHERE DeviceName LIKE ? AND DeviceCategory LIKE ? AND UserName = ? and SafeId IN (?);", s.DeviceName, s.DeviceCategory, s.UserName, safeIds) + + // sqlx.In returns queries with the `?` bindvar, we can rebind it for our backend + query = db.Rebind(query) + rows, err := db.Queryx(query, args) + */ + + // Generate placeholders for the IN clause to match multiple SafeId values + placeholders := make([]string, len(safeIds)) + for i := range safeIds { + placeholders[i] = "?" + } + placeholderStr := strings.Join(placeholders, ",") + + query := fmt.Sprintf("SELECT * FROM secrets WHERE SafeId IN (%s) ", placeholderStr) + args := []interface{}{} + + // Add the Safe Ids + for _, g := range safeIds { + args = append(args, g) + } + + // Add any other parameters + if s.DeviceName != "" { + query += " AND DeviceName LIKE ? " + args = append(args, s.DeviceName) + } + + if s.DeviceCategory != "" { + query += " AND DeviceCategory LIKE ? " + args = append(args, s.DeviceCategory) + } + + if s.UserName != "" { + query += " AND UserName LIKE ? " + args = append(args, s.UserName) + } + + /* + // Construct the query + query := fmt.Sprintf("SELECT * FROM users WHERE username = ? AND group IN (%s)", placeholderStr) + args := make([]interface{}, 0, len(groups)+1) + args = append(args, username) + for _, g := range safeIds { + args = append(args, g) + } + */ + + // Execute the query + rows, err := db.Queryx(query, args...) + + if err != nil { + log.Printf("SecretsGetMultipleSafes error executing sql record : '%s'\n", err) + return secretResults, err + } else { + // parse all the results into a slice + for rows.Next() { + var r Secret + err = rows.StructScan(&r) + if err != nil { + log.Printf("SecretsGetMultipleSafes error parsing sql record : '%s'\n", err) + return secretResults, err + } + + // Decrypt the secret + _, err = r.DecryptSecret() + if err != nil { + //log.Printf("GetSecret unable to decrypt stored secret '%v' : '%s'\n", r.Secret, err) + log.Printf("SecretsGetMultipleSafes unable to decrypt stored secret : '%s'\n", err) + return secretResults, err + } else { + secretResults = append(secretResults, r) + } + + } + log.Printf("SecretsGetMultipleSafes retrieved '%d' results\n", len(secretResults)) + } + + return secretResults, nil +} + // Returns all matching secrets, up to caller to determine how to deal with multiple results func GetSecrets(s *Secret, adminRole bool) ([]Secret, error) { var err error @@ -79,21 +172,21 @@ func GetSecrets(s *Secret, adminRole bool) ([]Secret, error) { // Determine whether to query for a specific device or a category of devices // Prefer querying device name than category if s.DeviceName != "" && s.DeviceCategory != "" && s.UserName != "" { - rows, err = db.Queryx("SELECT * FROM secrets WHERE DeviceName LIKE ? AND DeviceCategory LIKE ? AND UserName = ? AND RoleId = ?", s.DeviceName, s.DeviceCategory, s.UserName, s.RoleId) + rows, err = db.Queryx("SELECT * FROM secrets WHERE DeviceName LIKE ? AND DeviceCategory LIKE ? AND UserName = ? AND SafeId = ?", s.DeviceName, s.DeviceCategory, s.UserName, s.SafeId) } else if s.DeviceName != "" && s.UserName != "" { - rows, err = db.Queryx("SELECT * FROM secrets WHERE DeviceName LIKE ? AND UserName = ? AND RoleId = ?", s.DeviceName, s.UserName, s.RoleId) + rows, err = db.Queryx("SELECT * FROM secrets WHERE DeviceName LIKE ? AND UserName = ? AND SafeId = ?", s.DeviceName, s.UserName, s.SafeId) } else if s.DeviceCategory != "" && s.UserName != "" { - rows, err = db.Queryx("SELECT * FROM secrets WHERE DeviceCategory LIKE ? AND UserName = ? AND RoleId = ?", s.DeviceCategory, s.UserName, s.RoleId) + rows, err = db.Queryx("SELECT * FROM secrets WHERE DeviceCategory LIKE ? AND UserName = ? AND SafeId = ?", s.DeviceCategory, s.UserName, s.SafeId) } else if s.DeviceName != "" && s.DeviceCategory != "" { - rows, err = db.Queryx("SELECT * FROM secrets WHERE DeviceName LIKE ? AND DeviceCategory LIKE ? AND RoleId = ?", s.DeviceName, s.DeviceCategory, s.RoleId) + rows, err = db.Queryx("SELECT * FROM secrets WHERE DeviceName LIKE ? AND DeviceCategory LIKE ? AND SafeId = ?", s.DeviceName, s.DeviceCategory, s.SafeId) } else if s.DeviceName != "" { - rows, err = db.Queryx("SELECT * FROM secrets WHERE DeviceName LIKE ? AND RoleId = ?", s.DeviceName, s.RoleId) + rows, err = db.Queryx("SELECT * FROM secrets WHERE DeviceName LIKE ? AND SafeId = ?", s.DeviceName, s.SafeId) } else if s.DeviceCategory != "" { - rows, err = db.Queryx("SELECT * FROM secrets WHERE DeviceCategory LIKE ? AND RoleId = ?", s.DeviceCategory, s.RoleId) + rows, err = db.Queryx("SELECT * FROM secrets WHERE DeviceCategory LIKE ? AND SafeId = ?", s.DeviceCategory, s.SafeId) } else if s.UserName != "" { - rows, err = db.Queryx("SELECT * FROM secrets WHERE UserName LIKE ? AND RoleId = ?", s.UserName, s.RoleId) + rows, err = db.Queryx("SELECT * FROM secrets WHERE UserName LIKE ? AND SafeId = ?", s.UserName, s.SafeId) } else { - rows, err = db.Queryx("SELECT * FROM secrets WHERE RoleId = ?", s.RoleId) + rows, err = db.Queryx("SELECT * FROM secrets WHERE RoleId = ?", s.SafeId) //log.Printf("GetSecret no valid search options specified\n") //err = errors.New("no valid search options specified") //return secretResults, err diff --git a/models/setup.go b/models/setup.go index 7a57b67..23c3204 100644 --- a/models/setup.go +++ b/models/setup.go @@ -26,8 +26,7 @@ const createRoles string = ` CREATE TABLE IF NOT EXISTS roles ( RoleId INTEGER PRIMARY KEY ASC, RoleName VARCHAR, - ReadOnly BOOLEAN, - Admin BOOLEAN + ReadOnly BOOLEAN ); ` @@ -37,8 +36,8 @@ const createUsers string = ` GroupId INTEGER, UserName VARCHAR, Password VARCHAR, + Admin BOOLEAN DEFAULT 0, LdapUser BOOLEAN DEFAULT 0, - LdapDN VARCHAR DEFAULT '', FOREIGN KEY (GroupId) REFERENCES groups(GroupId) ); ` @@ -55,7 +54,8 @@ const createGroups string = ` GroupId INTEGER PRIMARY KEY ASC, GroupName VARCHAR, LdapGroup BOOLEAN DEFAULT 0, - LdapDN VARCHAR DEFAULT '' + LdapDN VARCHAR DEFAULT '', + Admin BOOLEAN DEFAULT 0 ); ` @@ -213,6 +213,36 @@ func CreateTables() { os.Exit(1) } + // Add initial groups + rowCount, _ = CheckCount("groups") + if rowCount == 0 { + if _, err = db.Exec("INSERT INTO groups (GroupId, GroupName, Admin) VALUES(1, 'Administrators', 1);"); err != nil { + log.Printf("Error adding initial group entry id 1 : '%s'", err) + os.Exit(1) + } + if _, err = db.Exec("INSERT INTO groups (GroupId, GroupName, Admin) VALUES(2, 'Users', 0);"); err != nil { + log.Printf("Error adding initial group entry id 2 : '%s'", err) + os.Exit(1) + } + } + + // Add initial permissions + rowCount, _ = CheckCount("permissions") + if rowCount == 0 { + if _, err = db.Exec("INSERT INTO permissions (RoleId, SafeId, UserId) VALUES(1, 1, 1);"); err != nil { + log.Printf("Error adding initial permissions entry userid 1 : '%s'", err) + os.Exit(1) + } + if _, err = db.Exec("INSERT INTO permissions (RoleId, SafeId, UserId) VALUES(1, 1, 2);"); err != nil { + log.Printf("Error adding initial permissions entry userid 2 : '%s'", err) + os.Exit(1) + } + if _, err = db.Exec("INSERT INTO permissions (RoleId, SafeId, UserId) VALUES(1, 1, 3);"); err != nil { + log.Printf("Error adding initial permissions entry userid 3 : '%s'", err) + os.Exit(1) + } + } + // Schema table should go last so we know if the database has a value in the schema table then everything was created properly if _, err = db.Exec(createSchema); err != nil { log.Printf("Error checking schema table : '%s'", err) @@ -246,8 +276,8 @@ func CreateTables() { GroupId INTEGER, UserName VARCHAR, Password VARCHAR, - LdapUser BOOLEAN DEFAULT 0, - LdapDN VARCHAR DEFAULT '', + Admin BOOLEAN DEFAULT 0, + LdapUser BOOLEAN DEFAULT 0 FOREIGN KEY (GroupId) REFERENCES groups(GroupId) ); INSERT INTO users SELECT * FROM _users_old; diff --git a/models/user.go b/models/user.go index a75f762..b443a30 100644 --- a/models/user.go +++ b/models/user.go @@ -5,20 +5,19 @@ import ( "errors" "fmt" "log" - "net/http" "smt/utils/token" - "github.com/gin-gonic/gin" "golang.org/x/crypto/bcrypt" ) type User struct { UserId int `db:"UserId" json:"userId"` - RoleId int `db:"RoleId" json:"roleId"` + GroupId int `db:"GroupId" json:"groupId"` UserName string `db:"UserName" json:"userName"` Password string `db:"Password" json:"-"` LdapUser bool `db:"LdapUser" json:"ldapUser"` - LdapDn string `db:"LdapDN" json:"ldapDn"` + Admin bool `db:"Admin"` + //LdapDn string `db:"LdapDN" json:"ldapDn"` } type UserRole struct { @@ -28,15 +27,32 @@ type UserRole struct { Admin bool `db:"Admin"` } +type UserGroup struct { + User + GroupName string `db:"GroupName"` + LdapGroup bool `db:"LdapGroup"` + LdapDn string `db:"LdapDN"` + Admin bool `db:"Admin"` +} + +type UserSafe struct { + User + AdminUser bool `db:"AdminUser"` + AdminGroup bool `db:"AdminGroup"` + SafeId int `db:"SafeId"` + SafeName string `db:"SafeName"` + GroupId int `db:"GroupId"` +} + func (u *User) SaveUser() (*User, error) { var err error // Validate username not already in use - _, err = GetUserByName(u.UserName) + _, err = UserGetByName(u.UserName) if err != nil && err.Error() == "user not found" { log.Printf("SaveUser confirmed no existing user, continuing with creation of user '%s'\n", u.UserName) - result, err := db.NamedExec((`INSERT INTO users (RoleId, UserName, Password, LdapUser, LdapDn) VALUES (:RoleId, :UserName, :Password, :LdapUser, :LdapDN)`), u) + result, err := db.NamedExec((`INSERT INTO users (GroupId, UserName, Password, LdapUser) VALUES (:GroupId, :UserName, :Password, :LdapUser`), u) if err != nil { log.Printf("SaveUser error executing sql record : '%s'\n", err) @@ -56,7 +72,7 @@ func (u *User) SaveUser() (*User, error) { func (u *User) DeleteUser() error { // Validate username exists - _, err := GetUserByName(u.UserName) + _, err := UserGetByName(u.UserName) if err != nil { log.Printf("DeleteUser error finding user account to remove : '%s'\n", err) return err @@ -180,7 +196,7 @@ func LdapLoginCheck(username string, password string) (User, error) { u.UserName = username // try to get LDAP group membership - groups, err := GetLdapGroupMembership(username, password) + ldapGroups, err := LdapGetGroupMembership(username, password) if err != nil { if err.Error() == "invalid user credentials" { return u, nil @@ -190,22 +206,37 @@ func LdapLoginCheck(username string, password string) (User, error) { } // Compare all roles against the list of user's group membership - roleList, err := QueryRoles() + //roleList, err := QueryRoles() + groupList, err := GroupList() if err != nil { return u, err } matchFound := false - for _, role := range roleList { - for _, group := range groups { - if role.LdapGroup == group { - log.Printf("Found match with role '%s' and LDAP group '%s', user is allowed role ID '%d'\n", role.RoleName, role.LdapGroup, role.RoleId) - u.RoleId = role.RoleId + /* + for _, role := range roleList { + for _, group := range groups { + if role.LdapGroup == group { + log.Printf("Found match with role '%s' and LDAP group '%s', user is allowed role ID '%d'\n", role.RoleName, role.LdapGroup, role.RoleId) + u.RoleId = role.RoleId + matchFound = true + break + } //else { + //log.Printf("Role '%s' with LDAP group '%s' not match user group '%s'\n", role.RoleName, role.LdapGroup, group) + //} + } + } + */ + for _, group := range groupList { + for _, lg := range ldapGroups { + if group.LdapDn == lg { + log.Printf("Found match with groupname '%s' and LDAP group DN '%s', user is a member of group ID '%d'\n", group.GroupName, group.LdapDn, group.GroupId) + u.GroupId = group.GroupId matchFound = true break - } //else { - //log.Printf("Role '%s' with LDAP group '%s' not match user group '%s'\n", role.RoleName, role.LdapGroup, group) - //} + } else { + log.Printf("Groupname '%s' with LDAP group '%s' not match user group '%s'\n", group.GroupName, group.LdapDn, lg) + } } } @@ -226,7 +257,7 @@ func StoreLdapUser(u *User) error { return nil } -func GetUserByID(uid uint) (User, error) { +func UserGetByID(uid uint) (User, error) { var u User @@ -246,7 +277,7 @@ func GetUserByID(uid uint) (User, error) { } -func GetUserByName(username string) (User, error) { +func UserGetByName(username string) (User, error) { var u User @@ -259,6 +290,7 @@ func GetUserByName(username string) (User, error) { return u, nil } +/* func GetUserRoleByID(uid uint) (UserRole, error) { var ur UserRole @@ -273,8 +305,26 @@ func GetUserRoleByID(uid uint) (UserRole, error) { return ur, nil } +*/ -func GetUserRoleFromToken(c *gin.Context) (UserRole, error) { +func UserGetGroupByID(uid uint) (UserGroup, error) { + + var ug UserGroup + + // Query database for matching user object + log.Printf("UserGetGroupByID querying for userid '%d'\n", uid) + + err := db.QueryRowx("SELECT users.UserId, users.GroupId, users.UserName, users.Password, users.LdapUser, groups.GroupName, groups.LdapGroup, groups.LdapDN, groups.Admin FROM users INNER JOIN groups ON users.GroupId = groups.GroupId WHERE users.UserId=?", uid).StructScan(&ug) + if err != nil { + log.Printf("UserGetGroupByID received error when querying database : '%s'\n", err) + return ug, errors.New("UserGetGroupByID user id not found") + } + + return ug, nil +} + +/* +func UserGetRoleFromToken(c *gin.Context) (UserRole, error) { var ur UserRole @@ -286,17 +336,45 @@ func GetUserRoleFromToken(c *gin.Context) (UserRole, error) { } // Query database for matching user object - log.Printf("GetUserRoleFromToken querying for userid '%d'\n", user_id) + log.Printf("UserGetRoleFromToken querying for userid '%d'\n", user_id) err = db.QueryRowx("SELECT users.UserId, users.RoleId, users.UserName, users.Password, users.LdapUser, users.LdapDN, roles.RoleName, roles.ReadOnly, roles.Admin FROM users INNER JOIN roles ON users.RoleId = roles.RoleId WHERE users.UserId=?", user_id).StructScan(&ur) if err != nil { - log.Printf("GetUserRoleFromToken received error when querying database : '%s'\n", err) - return ur, errors.New("GetUserRoleFromToken user not found") + log.Printf("UserGetRoleFromToken received error when querying database : '%s'\n", err) + return ur, errors.New("UserGetRoleFromToken user not found") } return ur, nil } +*/ -func QueryUsers() ([]User, error) { +func UserGetSafesAllowed(userId int) ([]UserSafe, error) { + + var results []UserSafe + + // join users, groups and permissions + rows, err := db.Queryx("SELECT users.UserId, users.GroupId, users.Admin as AdminUser, groups.Admin as AdminGroup, permissions.SafeId, safe.SafeName FROM users INNER JOIN groups ON users.GroupId = groups.GroupId INNER JOIN permissions ON groups.GroupId = permissions.GroupId INNER JOIN safes on permissions.SafeId = safes.SafeId WHERE users.UserId=?", userId) + if err != nil { + log.Printf("UserGetSafesAllowed error executing sql record : '%s'\n", err) + return results, err + } else { + // parse all the results into a slice + for rows.Next() { + var us UserSafe + err = rows.StructScan(&us) + if err != nil { + log.Printf("UserGetSafesAllowed error parsing sql record : '%s'\n", err) + return results, err + } + results = append(results, us) + + } + log.Printf("UserGetSafesAllowed retrieved '%d' results\n", len(results)) + } + + return results, nil +} + +func UserList() ([]User, error) { var results []User // Query database for role definitions @@ -322,3 +400,23 @@ func QueryUsers() ([]User, error) { return results, nil } + +func UserCheckIfAdmin(userId int) bool { + // TODO + + u, err := UserGetByID(uint(userId)) + if err != nil { + log.Printf("UserCheckIfAdmin received error : '%s'\n", err) + return false + } + + return u.Admin +} + +func UserGetSafe() { + +} + +// need a way of checking what safe a user has access to +// if they only have access to one then that is easy +// if they are an admin then they have access to everything diff --git a/www/database.png b/www/database.png index c14fa81..d7cea2f 100644 Binary files a/www/database.png and b/www/database.png differ