From 07fd43bf33c3c0d9d76354763594f99cfd36cddf Mon Sep 17 00:00:00 2001 From: Nathan Coad Date: Tue, 9 Jan 2024 10:59:27 +1100 Subject: [PATCH] work on determining which secrets accessible to user --- controllers/store_secrets.go | 142 ++++++++++++++++++----------------- models/permissions.go | 10 ++- models/secret.go | 78 ++++++++++++++++++- models/setup.go | 2 + models/user.go | 8 ++ 5 files changed, 167 insertions(+), 73 deletions(-) diff --git a/controllers/store_secrets.go b/controllers/store_secrets.go index 047d546..9b7e75e 100644 --- a/controllers/store_secrets.go +++ b/controllers/store_secrets.go @@ -226,85 +226,93 @@ func UpdateSecret(c *gin.Context) { s.DeviceName = input.DeviceName s.DeviceCategory = input.DeviceCategory - // TODO: - // Get a list of matching secrets - SecretsSearchAllSafes - //secretList, err := models.SecretsSearchAllSafes(&s) - // Check if user has access to the safes containing those secrets - something like UserGetSafesAllowed but not quite - //allowedSafes, err := models.UserGetSafesAllowed(user_id) - // Make sure that the access is not readonly - // If user has access to more than one safe containing the secret, generate an error - // Otherwise, update the secret + secretList, err := models.SecretsGetAllowedForGroup(&s, user_id) - 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 - } - - // Query which safes the current user is allowed to access - - /* - safeId := SecretCheckSafeAllowed(int(user_id), input) - if safeId == 0 { - c.JSON(http.StatusBadRequest, gin.H{"error": "error determining safe"}) - return - } - - s.SafeId = safeId - */ - - // TODO - replace this with a call to SecretsGetMultipleSafes - - // Confirm that the secret already exists - checkExists, err := models.GetSecrets(&s, false) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + if len(secretList) == 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "no secret matching search parameters"}) 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()}) - return - } else if len(checkExists) == 1 { - // Set the secret id with the one retrieved from the database - s.SecretId = checkExists[0].SecretId - - // check for empty fields in the update request and update from the existing record - if s.UserName == "" { - s.UserName = checkExists[0].UserName - } - if s.DeviceCategory == "" { - s.DeviceCategory = checkExists[0].DeviceCategory - } - if s.DeviceName == "" { - s.DeviceName = checkExists[0].DeviceName - } - - // Encrypt secret - s.Secret = input.SecretValue - _, err = s.EncryptSecret() - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "UpdateSecret error encrypting secret : " + err.Error()}) - return - } - - _, err = s.UpdateSecret() - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "UpdateSecret error saving secret : " + err.Error()}) - return - } - + } else if len(secretList) == 1 { + // Update secret + log.Printf("mock updating secret\n") c.JSON(http.StatusOK, gin.H{"message": "secret updated successfully"}) } else { - err = errors.New("UpdateSecret found multiple secrets matching input data, be more specific") - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + c.JSON(http.StatusBadRequest, gin.H{"error": "multiple secrets matched search parameters, be more specific"}) return } + /* + // TODO: + // Get a list of matching secrets - SecretsSearchAllSafes + //secretList, err := models.SecretsSearchAllSafes(&s) + // Check if user has access to the safes containing those secrets - something like UserGetSafesAllowed but not quite + //allowedSafes, err := models.UserGetSafesAllowed(user_id) + // Make sure that the access is not readonly + // If user has access to more than one safe containing the secret, generate an error + // Otherwise, update the secret + + 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 + } + // TODO - replace this with a call to SecretsGetMultipleSafes + + // Confirm that the secret already exists + checkExists, err := models.GetSecrets(&s, false) + if err != nil { + 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()}) + return + } else if len(checkExists) == 1 { + // Set the secret id with the one retrieved from the database + s.SecretId = checkExists[0].SecretId + + // check for empty fields in the update request and update from the existing record + if s.UserName == "" { + s.UserName = checkExists[0].UserName + } + if s.DeviceCategory == "" { + s.DeviceCategory = checkExists[0].DeviceCategory + } + if s.DeviceName == "" { + s.DeviceName = checkExists[0].DeviceName + } + + // Encrypt secret + s.Secret = input.SecretValue + _, err = s.EncryptSecret() + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "UpdateSecret error encrypting secret : " + err.Error()}) + return + } + + _, err = s.UpdateSecret() + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "UpdateSecret error saving secret : " + err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "secret updated successfully"}) + } else { + err = errors.New("UpdateSecret found multiple secrets matching input data, be more specific") + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + */ + } func SecretCheckSafeAllowed(user_id int, input StoreInput) int { diff --git a/models/permissions.go b/models/permissions.go index f9cca50..6aa1d9c 100644 --- a/models/permissions.go +++ b/models/permissions.go @@ -1,8 +1,10 @@ package models type Permission struct { - PermissionId int `db:"PermissionId"` - RoleId int `db:"RoleId"` - SafeId int `db:"SafeId"` - GroupId int `db:"GroupId"` + PermissionId int `db:"PermissionId"` + Description string `db:"Description"` + ReadOnly bool `db:"ReadOnly"` + RoleId int `db:"RoleId"` + SafeId int `db:"SafeId"` + GroupId int `db:"GroupId"` } diff --git a/models/secret.go b/models/secret.go index 45ceb18..4359283 100644 --- a/models/secret.go +++ b/models/secret.go @@ -16,8 +16,7 @@ import ( // 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:"-"` SafeId int `db:"SafeId"` DeviceName string `db:"DeviceName"` DeviceCategory string `db:"DeviceCategory"` @@ -46,6 +45,81 @@ func (s *Secret) SaveSecret() (*Secret, error) { return s, nil } +func SecretsGetAllowedForUser(s *Secret, userId string) ([]UserSecret, error) { + // Query based on group + // SELECT users.UserId, users.GroupId, permissions.ReadOnly, permissions.SafeId, safes.SafeName, secrets.* 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 INNER JOIN secrets on secrets.SafeId = safes.SafeId WHERE users.UserId = 2 + var secretResults []UserSecret + + return secretResults, nil +} + +func SecretsGetAllowedForGroup(s *Secret, userId int) ([]UserSecret, error) { + // Query based on group + // SELECT users.UserId, users.GroupId, permissions.ReadOnly, permissions.SafeId, safes.SafeName, secrets.* 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 INNER JOIN secrets on secrets.SafeId = safes.SafeId WHERE users.UserId = 2 + var err error + var secretResults []UserSecret + + args := []interface{}{} + query := "users.UserId, users.GroupId, permissions.ReadOnly, permissions.SafeId, safes.SafeName, secrets.* 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 INNER JOIN secrets on secrets.SafeId = safes.SafeId WHERE users.UserId = ? " + args = append(args, userId) + + // 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("SecretsGetAllowedForGroup query string : '%s'\n%+v\n", query, args) + rows, err := db.Queryx(query, args...) + + if err != nil { + log.Printf("SecretsGetAllowedForGroup error executing sql record : '%s'\n", err) + return secretResults, err + } else { + // parse all the results into a slice + for rows.Next() { + var r UserSecret + err = rows.StructScan(&r) + if err != nil { + log.Printf("SecretsGetAllowedForGroup 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("SecretsGetAllowedForGroup unable to decrypt stored secret : '%s'\n", err) + return secretResults, err + } else { + secretResults = append(secretResults, r) + } + + } + log.Printf("SecretsGetAllowedForGroup retrieved '%d' results\n", len(secretResults)) + } + + return secretResults, nil +} + func SecretsSearchAllSafes(s *Secret) ([]Secret, error) { var err error var secretResults []Secret diff --git a/models/setup.go b/models/setup.go index 8fcb5cf..ddabe77 100644 --- a/models/setup.go +++ b/models/setup.go @@ -374,6 +374,8 @@ func CreateTables() { FOREIGN KEY (GroupId) REFERENCES groups(GroupId) ); INSERT INTO permissions SELECT PermissionId, SafeId, UserId, GroupId, '' AS Description, 0 as ReadOnly FROM _permissions_old; + UPDATE permissions SET ReadOnly = 0 WHERE ReadOnly is null; + UPDATE permissions SET Description = '' WHERE Description is null; COMMIT; PRAGMA foreign_keys=on; DROP TABLE _permissions_old; diff --git a/models/user.go b/models/user.go index 1d36361..f0824f4 100644 --- a/models/user.go +++ b/models/user.go @@ -35,6 +35,7 @@ type UserGroup struct { Admin bool `db:"Admin"` } +// Combine Users and Safes to determine which safes a user has access to type UserSafe struct { User SafeId int `db:"SafeId"` @@ -42,6 +43,13 @@ type UserSafe struct { GroupId int `db:"GroupId"` } +// Used for querying all secrets the user has access to +type UserSecret struct { + User + Group + Secret +} + func (u *User) SaveUser() (*User, error) { var err error