From 20dc745a64e79ec678474f99e0353db2e078a2f7 Mon Sep 17 00:00:00 2001 From: Nathan Coad Date: Mon, 8 Jan 2024 21:13:04 +1100 Subject: [PATCH] test --- controllers/store_secrets.go | 151 +++++++++++++++++++++++++++-------- models/secret.go | 66 +++++++++++++++ models/user.go | 3 +- 3 files changed, 184 insertions(+), 36 deletions(-) diff --git a/controllers/store_secrets.go b/controllers/store_secrets.go index 5caae18..7ee531b 100644 --- a/controllers/store_secrets.go +++ b/controllers/store_secrets.go @@ -2,6 +2,7 @@ package controllers import ( "errors" + "fmt" "log" "net/http" "smt/models" @@ -108,6 +109,89 @@ func StoreSecret(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "secret stored successfully"}) } +// CheckUpdateSecretAllowed checks to see if a user has access to the specified secret. If so, the corresponding SafeId is returned +func CheckUpdateSecretAllowed(s *models.Secret, user_id int) (int, error) { + // Query all safes for secrets matching parameters specified + matchingSecrets, err := models.SecretsSearchAllSafes(s) + if err != nil { + errString := fmt.Sprintf("CheckUpdateSecretAllowed error getting matching secrets : '%s'\n", err) + log.Println(errString) + return 0, errors.New(errString) + } + + // Query which safes user has access to + userSafes, err := models.UserGetSafesAllowed(int(user_id)) + if err != nil { + errString := fmt.Sprintf("CheckUpdateSecretAllowed error getting safes that user has access to : '%s'\n", err) + log.Println(errString) + return 0, errors.New(errString) + } + + if len(matchingSecrets) == 0 { + errString := "CheckUpdateSecretAllowed found zero secrets matching supplied parameters" + log.Println(errString) + return 0, errors.New(errString) + } else if len(matchingSecrets) == 1 { + log.Printf("CheckUpdateSecretAllowed found a single matching secret :\n'%+v'\n", matchingSecrets[0]) + // Check if user is admin + for _, val := range userSafes { + if val.User.Admin || val.AdminGroup { + return matchingSecrets[0].SafeId, nil + } + } + + // If we reach here then user is not admin + // Check to see user is allowed to access the safe holding the secret + for _, secret := range matchingSecrets { + for _, user := range userSafes { + if user.SafeId == secret.SafeId { + return user.SafeId, nil + } + } + } + } else { // Multiple matching secrets are found + log.Printf("CheckUpdateSecretAllowed found multiple matching secrets\n") + matchFound := false + matchingSafeId := 0 + for _, secret := range matchingSecrets { + for _, user := range userSafes { + if user.SafeId == secret.SafeId { + log.Printf("CheckUpdateSecretAllowed match found for SafeId '%d':\n'%+v'\n", user.SafeId, secret) + if !matchFound { + matchFound = true + matchingSafeId = user.SafeId + } else { + // Found more than one applicable secret, how do we know which one to update? + errString := "CheckUpdateSecretAllowed found multiple secrets matching supplied parameters, supply more specific parameters" + log.Println(errString) + return 0, errors.New(errString) + } + } + + if user.User.Admin || user.AdminGroup { + log.Printf("CheckUpdateSecretAllowed found user to be admin, assuming SafeId '%d'\n", user.SafeId) + if !matchFound { + matchFound = true + matchingSafeId = user.SafeId + } else { + // Found more than one applicable secret, how do we know which one to update? + errString := "CheckUpdateSecretAllowed found multiple secrets matching supplied parameters, supply more specific parameters" + log.Println(errString) + return 0, errors.New(errString) + } + } + } + } + + // only one match was found, so we are safe to return that value + if matchFound { + return matchingSafeId, nil + } + } + + return 0, nil +} + func UpdateSecret(c *gin.Context) { var err error var input StoreInput @@ -129,19 +213,11 @@ func UpdateSecret(c *gin.Context) { } */ - /* - // 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 - } - */ + user_id, err := token.ExtractTokenID(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"}) + return + } // Populate fields s := models.Secret{} @@ -150,29 +226,32 @@ 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 - } - */ + allowedUpdate, err := CheckUpdateSecretAllowed(&s, int(user_id)) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("error determining secret : '%s'", err)}) + return + } + + if allowedUpdate != 0 { + s.SafeId = allowedUpdate + } + + // If user has Admin access then perform update + // If user has normal access to the safe the secret is stored in then perform update + // If matching secret is found in multiple safes then generate error + // If user doesn't have access to the safe the matching secret is in then generate error // 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 - } + /* + safeId := SecretCheckSafeAllowed(int(user_id), input) + if safeId == 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "error determining safe"}) + return + } - s.SafeId = safeId + s.SafeId = safeId + */ // TODO - replace this with a call to SecretsGetMultipleSafes @@ -182,7 +261,6 @@ func UpdateSecret(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - if len(checkExists) == 0 { err = errors.New("UpdateSecret could not find existing secret to update") c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) @@ -241,11 +319,14 @@ func SecretCheckSafeAllowed(user_id int, input StoreInput) int { return safe.SafeId } else if input.SafeId > 0 && safe.SafeId == input.SafeId { // Safe specified by id return safe.SafeId + } else if safe.User.Admin || safe.AdminGroup { // User has admin role so they're allowed this safe anyway + return safe.SafeId + } else { + log.Printf("SecretCheckSafeAllowed ") } } - // TODO what about Admin role - + log.Printf("SecretCheckSafeAllowed didn't find any safes\n") return 0 /* diff --git a/models/secret.go b/models/secret.go index 7cdb193..e44224d 100644 --- a/models/secret.go +++ b/models/secret.go @@ -46,6 +46,70 @@ func (s *Secret) SaveSecret() (*Secret, error) { return s, nil } +func SecretsSearchAllSafes(s *Secret) ([]Secret, error) { + var err error + var secretResults []Secret + + args := []interface{}{} + query := "SELECT * FROM secrets WHERE 1=1 " + + // Make sure at least one parameter was specified + if s.DeviceName == "" && s.DeviceCategory == "" && s.UserName == "" { + err = errors.New("no search parameters specified") + log.Println(err) + return secretResults, err + } + + // Add any other arguments to the query if they were specified + 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) + } + + // Execute the query + log.Printf("SecretsSearchAllSafes query string : '%s'\n%+v\n", query, args) + rows, err := db.Queryx(query, args...) + + if err != nil { + log.Printf("SecretsSearchAllSafes 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("SecretsSearchAllSafes 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("SecretsSearchAllSafes unable to decrypt stored secret : '%s'\n", err) + return secretResults, err + } else { + secretResults = append(secretResults, r) + } + + } + log.Printf("SecretsSearchAllSafes retrieved '%d' results\n", len(secretResults)) + } + + return secretResults, nil +} + // SecretsGetMultipleSafes queries the specified safes for matching secrets func SecretsGetMultipleSafes(s *Secret, adminRole bool, safeIds []int) ([]Secret, error) { var err error @@ -62,6 +126,8 @@ func SecretsGetMultipleSafes(s *Secret, adminRole bool, safeIds []int) ([]Secret rows, err := db.Queryx(query, args) */ + // TODO use SecretsSearchAllSafes for adminRole of true + args := []interface{}{} var query string if adminRole { diff --git a/models/user.go b/models/user.go index 3be3efc..abadf6f 100644 --- a/models/user.go +++ b/models/user.go @@ -359,7 +359,8 @@ func UserGetSafesAllowed(userId int) ([]UserSafe, error) { // join users, groups and permissions rows, err := db.Queryx(` - SELECT users.UserId, users.GroupId, users.Admin as AdminUser, groups.Admin as AdminGroup, + SELECT users.UserId, users.GroupId, + groups.Admin as AdminGroup, permissions.SafeId, safes.SafeName FROM users INNER JOIN groups ON users.GroupId = groups.GroupId INNER JOIN permissions ON groups.GroupId = permissions.GroupId