diff --git a/controllers/store_secrets.go b/controllers/store_secrets.go index f19c7b4..893a6b6 100644 --- a/controllers/store_secrets.go +++ b/controllers/store_secrets.go @@ -6,13 +6,13 @@ 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 { +/* +type StoreSecretInput struct { SafeId int `json:"safeId"` SafeName string `json:"safeName"` DeviceName string `json:"deviceName"` @@ -20,20 +20,78 @@ type StoreInput struct { UserName string `json:"userName" binding:"required"` SecretValue string `json:"secretValue" binding:"required"` } +*/ +type SecretInput struct { + SafeId int `json:"safeId"` + SafeName string `json:"safeName"` + DeviceName string `json:"deviceName"` + DeviceCategory string `json:"deviceCategory"` + UserName string `json:"userName"` + SecretValue string `json:"secretValue"` +} + +func FindSafeId(UserId int, input SecretInput) (int, error) { + + // Check which safes a user is allowed to access + allowedSafes, err := models.UserGetSafesAllowed(UserId) + if err != nil { + errString := fmt.Sprintf("error determining safe access : '%s'", err) + log.Printf("StoreSecret %s\n", errString) + return 0, errors.New(errString) + } + + // Make sure that the specified safe is in the list of allowed safes + if len(allowedSafes) == 0 { + errString := "error accessing specified safe" + log.Printf("StoreSecret %s\n", errString) + return 0, errors.New(errString) + } else if len(allowedSafes) == 1 && input.SafeId == 0 && len(input.SafeName) == 0 { + log.Printf("StoreSecret user did not specify safe but has access to only one safe '%d'\n", allowedSafes[0].SafeId) + return allowedSafes[0].SafeId, nil + } else { + for _, safe := range allowedSafes { + if input.SafeId > 0 && len(input.SafeName) > 0 && safe.SafeId == input.SafeId && safe.SafeName == input.SafeName { + return safe.SafeId, nil + } else if input.SafeId > 0 && safe.SafeId == input.SafeId { + return safe.SafeId, nil + } else if len(input.SafeName) > 0 && safe.SafeName == input.SafeName { + return safe.SafeId, nil + } + } + + errString := "error accessing specified safe" + log.Printf("StoreSecret %s\n", errString) + return 0, errors.New(errString) + } +} + +// TODO update to match UpdateSecret func StoreSecret(c *gin.Context) { var err error - var input StoreInput + var input SecretInput + var UserId int if err := c.ShouldBindJSON(&input); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON received : " + err.Error()}) return } - if input.SafeId == 0 && len(input.SafeName) == 0 { - errString := "StoreSecret no safe specified\n" - log.Print(errString) - c.JSON(http.StatusBadRequest, gin.H{"error": errString}) + // Perform some input validation + /* + if input.SafeId == 0 && len(input.SafeName) == 0 { + errString := "StoreSecret no safe specified\n" + log.Print(errString) + c.JSON(http.StatusBadRequest, gin.H{"error": errString}) + return + } + */ + if input.DeviceCategory == "" && input.DeviceName == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "cannot store secret with empty deviceName and empty deviceCategory"}) + return + } + if len(input.UserName) == 0 || len(input.SecretValue) == 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "cannot store secret with empty UserName or SecretValue"}) return } @@ -47,37 +105,29 @@ func StoreSecret(c *gin.Context) { s.DeviceCategory = input.DeviceCategory // 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 + user_id, err := token.ExtractTokenID(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"}) + return } */ - if input.DeviceCategory == "" && input.DeviceName == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "cannot store secret with empty deviceName and empty deviceCategory"}) + // Get userId that we stored in the context earlier + if val, ok := c.Get("user-id"); !ok { + c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"}) return + } else { + UserId = val.(int) + //log.Printf("user_id: %v\n", user_id) } - // TODO - replace this with a call to SecretsGetMultipleSafes + safeId, err := FindSafeId(UserId, input) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + s.SafeId = safeId // If this secret already exists in the database then generate an error checkExists, err := models.GetSecrets(&s, false) @@ -108,6 +158,7 @@ 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) { @@ -187,10 +238,11 @@ func CheckUpdateSecretAllowed(s *models.Secret, user_id int) (int, error) { return 0, nil } +*/ func UpdateSecret(c *gin.Context) { var err error - var input StoreInput + var input SecretInput var user_id int if err := c.ShouldBindJSON(&input); err != nil { @@ -241,12 +293,14 @@ func UpdateSecret(c *gin.Context) { } if len(secretList) == 0 { + + // TODO - also check secrets allowed for user + c.JSON(http.StatusBadRequest, gin.H{"error": "no secret matching search parameters"}) return } else if len(secretList) == 1 { // Update secret - //log.Printf("mock updating secret\n") - log.Printf("secretList[0]: %v\n", secretList[0]) + //log.Printf("secretList[0]: %v\n", secretList[0]) s.SecretId = secretList[0].SecretId @@ -280,78 +334,87 @@ func UpdateSecret(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": "multiple secrets matched search parameters, be more specific"}) return } +} + +func DeleteSecret(c *gin.Context) { + var err error + var input SecretInput + var UserId int + + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON received : " + err.Error()}) + return + } + + // Input validation + if input.DeviceCategory == "" && input.DeviceName == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "cannot store secret with empty deviceName and empty deviceCategory"}) + 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()}) + if input.SafeId == 0 && len(input.SafeName) == 0 { + errString := "StoreSecret no safe specified\n" + log.Print(errString) + c.JSON(http.StatusBadRequest, gin.H{"error": errString}) return } */ + // Don't log this since it contains plaintext secrets + //log.Printf("StoreSecret received JSON input '%v'\n", input) + + // Populate fields + s := models.Secret{} + s.UserName = input.UserName + s.DeviceName = input.DeviceName + s.DeviceCategory = input.DeviceCategory + + // 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 + } + */ + + // Get userId that we stored in the context earlier + if val, ok := c.Get("user-id"); !ok { + c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"}) + return + } else { + UserId = val.(int) + //log.Printf("user_id: %v\n", user_id) + } + + safeId, err := FindSafeId(UserId, input) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + s.SafeId = safeId + + // If this secret already exists in the database then generate an error + checkExists, err := models.GetSecrets(&s, false) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + if len(checkExists) == 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "no secrets matched search parameters"}) + return + } else if len(checkExists) == 1 { + c.JSON(http.StatusOK, gin.H{"message": "mock secret deleted successfully"}) + + // TODO delete secret + } else { + c.JSON(http.StatusBadRequest, gin.H{"error": "multiple secrets matched search parameters, be more specific"}) + return + } } -func SecretCheckSafeAllowed(user_id int, input StoreInput) int { +func SecretCheckSafeAllowed(user_id int, input SecretInput) 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 diff --git a/main.go b/main.go index 56cafb8..669885e 100644 --- a/main.go +++ b/main.go @@ -262,7 +262,7 @@ func main() { protected.POST("/store", controllers.StoreSecret) protected.POST("/update", controllers.UpdateSecret) // TODO - //protected.POST("/delete", controllers.DeleteSecret) + protected.POST("/delete", controllers.DeleteSecret) // Support parameters in path // See https://gin-gonic.com/docs/examples/param-in-path/