new schema initial commit
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2024-01-08 14:30:16 +11:00
parent 7ecf27f7dc
commit d1eecc5c4f
14 changed files with 631 additions and 149 deletions

View File

@@ -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 #### Remove User
POST `/api/admin/user/delete` POST `/api/admin/user/delete`

View File

@@ -16,9 +16,11 @@ import (
) )
type RegisterInput struct { type RegisterInput struct {
UserName string `json:"userName" binding:"required"` UserName string `json:"userName" binding:"required"`
Password string `json:"password" binding:"required"` Password string `json:"password" binding:"required"`
RoleId int `json:"roleid"` GroupId int `json:"groupId"`
GroupName string `json:"groupName"`
//RoleId int `json:"roleid"`
} }
type LoginInput struct { type LoginInput struct {
@@ -52,7 +54,7 @@ func DeleteUser(c *gin.Context) {
u.UserName = html.EscapeString(strings.TrimSpace(u.UserName)) u.UserName = html.EscapeString(strings.TrimSpace(u.UserName))
// Confirm user account exists // 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) log.Printf("DeleteUser confirming user '%s' account exists\n", u.UserName)
if (models.User{} == testUser) { if (models.User{} == testUser) {
err := errors.New("attempt to delete non-existing username '" + u.UserName + "'") err := errors.New("attempt to delete non-existing username '" + u.UserName + "'")
@@ -90,23 +92,51 @@ func RegisterUser(c *gin.Context) {
} }
u := models.User{} u := models.User{}
//u.RoleId = 1
u.UserName = input.UserName u.UserName = input.UserName
u.Password = input.Password u.Password = input.Password
// Default to regular user role if not specified // Determine which GroupId to save
if input.RoleId == 0 { // Can be specified either by GroupName or GroupId in the request
log.Printf("Register no role specified, defaulting to builtin role UserRole with id 2.\n") if len(input.GroupName) > 0 {
u.RoleId = 2 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 { } 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 //remove spaces in username
u.UserName = html.EscapeString(strings.TrimSpace(u.UserName)) u.UserName = html.EscapeString(strings.TrimSpace(u.UserName))
// Check if user already exists // 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) log.Printf("Register checking if user '%s' already exists\n", u.UserName)
if (models.User{} == testUser) { if (models.User{} == testUser) {
log.Printf("Register confirmed no existing username\n") log.Printf("Register confirmed no existing username\n")
@@ -214,7 +244,7 @@ func CurrentUser(c *gin.Context) {
return return
} }
u, err := models.GetUserByID(user_id) u, err := models.UserGetByID(user_id)
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
@@ -237,7 +267,7 @@ func GetRoles(c *gin.Context) {
} }
func GetUsers(c *gin.Context) { func GetUsers(c *gin.Context) {
users, err := models.QueryUsers() users, err := models.UserList()
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})

View File

@@ -13,11 +13,14 @@ type RetrieveInput struct {
DeviceName string `json:"deviceName"` DeviceName string `json:"deviceName"`
DeviceCategory string `json:"deviceCategory"` DeviceCategory string `json:"deviceCategory"`
UserName string `json:"userName"` UserName string `json:"userName"`
SafeId int `json:"safeId"`
SafeName string `json:"safeName"`
} }
type ListSecret struct { type ListSecret struct {
SecretId int `db:"SecretId" json:"-"` SecretId int `db:"SecretId" json:"-"`
RoleId int `db:"RoleId" json:"-"` //RoleId int `db:"RoleId" json:"-"`
SafeId int `db:"SafeId"`
DeviceName string `db:"DeviceName"` DeviceName string `db:"DeviceName"`
DeviceCategory string `db:"DeviceCategory"` DeviceCategory string `db:"DeviceCategory"`
UserName string `db:"UserName"` UserName string `db:"UserName"`
@@ -27,6 +30,7 @@ type ListSecret struct {
func RetrieveSecret(c *gin.Context) { func RetrieveSecret(c *gin.Context) {
var input RetrieveInput var input RetrieveInput
var results []models.Secret var results []models.Secret
var userIsAdmin bool = false
// Validate the input matches our struct // Validate the input matches our struct
if err := c.ShouldBindJSON(&input); err != nil { 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) log.Printf("RetrieveSecret received JSON input '%v'\n", input)
// Get the user and role id of the requestor /*
u, err := models.GetUserRoleFromToken(c) // Get the user and role id of the requestor
if err != nil { u, err := models.UserGetRoleFromToken(c)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) if err != nil {
return c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
} return
}
*/
// Populate fields // Populate fields
s := models.Secret{} s := models.Secret{}
s.RoleId = u.RoleId //s.RoleId = u.RoleId
s.DeviceName = input.DeviceName s.DeviceName = input.DeviceName
s.DeviceCategory = input.DeviceCategory s.DeviceCategory = input.DeviceCategory
s.UserName = input.UserName s.UserName = input.UserName
// Don't apply a role filter if user has admin role user_id, err := token.ExtractTokenID(c)
results, err = models.GetSecrets(&s, u.Admin) 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 { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 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) { 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 { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"})
return return
} }
s.RoleId = u.RoleId
results, err := models.GetSecrets(s, false) // Work out which safe to query for this user if the safe was not specified
if err != nil { safeList, err := models.UserGetSafesAllowed(int(user_id))
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return // 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 { if len(results) == 1 {
@@ -125,69 +200,75 @@ func retrieveSpecifiedSecret(s *models.Secret, c *gin.Context) {
} }
} }
func ListSecrets(c *gin.Context) { func ListSecrets(c *gin.Context) {
var results []models.Secret
var output []ListSecret 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 // If user is admin then list everything, otherwise only list for current role
u, err := models.GetUserRoleFromToken(c) 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 user is admin then list everything, otherwise only list for current role if err != nil {
results, err = models.GetSecrets(&models.Secret{RoleId: u.RoleId}, u.Admin) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err != nil { for _, v := range results {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) output = append(output, ListSecret(v))
return }
} */
for _, v := range results {
output = append(output, ListSecret(v))
}
// output results as json // output results as json
c.JSON(http.StatusOK, gin.H{"message": "success", "data": output}) c.JSON(http.StatusOK, gin.H{"message": "success", "data": output})
} }
func RetrieveMultpleSecrets(c *gin.Context) { func RetrieveMultpleSecrets(c *gin.Context) {
var input RetrieveInput // TODO implement with new schema
/*
var input RetrieveInput
// Validate the input matches our struct // Validate the input matches our struct
if err := c.ShouldBindJSON(&input); err != nil { if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return return
} }
log.Printf("StoreSecret received JSON input '%v'\n", input) log.Printf("StoreSecret received JSON input '%v'\n", input)
// Get the user and role id of the requestor // Get the user and role id of the requestor
user_id, err := token.ExtractTokenID(c) user_id, err := token.ExtractTokenID(c)
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return return
} }
u, err := models.GetUserRoleByID(user_id) u, err := models.GetUserRoleByID(user_id)
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return return
} }
// Populate fields // Populate fields
s := models.Secret{} s := models.Secret{}
s.RoleId = u.RoleId s.RoleId = u.RoleId
s.DeviceName = input.DeviceName s.DeviceName = input.DeviceName
s.DeviceCategory = input.DeviceCategory s.DeviceCategory = input.DeviceCategory
results, err := models.GetSecrets(&s, false) results, err := models.GetSecrets(&s, false)
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return return
} }
// output results as json // output results as json
c.JSON(http.StatusOK, gin.H{"message": "success", "data": results}) c.JSON(http.StatusOK, gin.H{"message": "success", "data": results})
*/
} }

View File

@@ -5,13 +5,16 @@ import (
"log" "log"
"net/http" "net/http"
"smt/models" "smt/models"
"smt/utils/token"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// bindings are validated by https://github.com/go-playground/validator // bindings are validated by https://github.com/go-playground/validator
type StoreInput struct { type StoreInput struct {
RoleId int `json:"roleId"` //RoleId int `json:"roleId"`
SafeId int `json:"safeId"`
SafeName string `json:"safeName"`
DeviceName string `json:"deviceName"` DeviceName string `json:"deviceName"`
DeviceCategory string `json:"deviceCategory"` DeviceCategory string `json:"deviceCategory"`
UserName string `json:"userName" binding:"required"` UserName string `json:"userName" binding:"required"`
@@ -36,15 +39,32 @@ func StoreSecret(c *gin.Context) {
s.DeviceName = input.DeviceName s.DeviceName = input.DeviceName
s.DeviceCategory = input.DeviceCategory s.DeviceCategory = input.DeviceCategory
// If RoleID is not defined then default to the same role as the user requesting secret to be stored // Query which safes the current user is allowed to access
if input.RoleId != 0 { user_id, err := token.ExtractTokenID(c)
s.RoleId = input.RoleId if err != nil {
} else { c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"})
ur, _ := models.GetUserRoleFromToken(c) return
log.Printf("StoreSecret RoleId was not specified, setting to RoleId of '%d'\n", ur.RoleId)
s.RoleId = ur.RoleId
} }
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 == "" { if input.DeviceCategory == "" && input.DeviceName == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "cannot store secret with empty deviceName and empty deviceCategory"}) c.JSON(http.StatusBadRequest, gin.H{"error": "cannot store secret with empty deviceName and empty deviceCategory"})
return return
@@ -91,17 +111,19 @@ func UpdateSecret(c *gin.Context) {
log.Printf("UpdateSecret received JSON input '%v'\n", input) log.Printf("UpdateSecret received JSON input '%v'\n", input)
// Get the user and role id of the requestor /*
u, err := models.GetUserRoleFromToken(c) // Get the user and role id of the requestor
if err != nil { u, err := models.UserGetRoleFromToken(c)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) if err != nil {
return c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
} return
// Verify that the user role is not readonly }
if u.ReadOnly { // Verify that the user role is not readonly
c.JSON(http.StatusForbidden, gin.H{"error": "UpdateSecret user role does not permit updates"}) if u.ReadOnly {
return c.JSON(http.StatusForbidden, gin.H{"error": "UpdateSecret user role does not permit updates"})
} return
}
*/
// Populate fields // Populate fields
s := models.Secret{} s := models.Secret{}
@@ -110,13 +132,30 @@ func UpdateSecret(c *gin.Context) {
s.DeviceName = input.DeviceName s.DeviceName = input.DeviceName
s.DeviceCategory = input.DeviceCategory s.DeviceCategory = input.DeviceCategory
// Default role ID is 1 if not defined /*
if input.RoleId != 0 { // Default role ID is 1 if not defined
s.RoleId = input.RoleId if input.RoleId != 0 {
} else { s.RoleId = input.RoleId
s.RoleId = 1 } 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 // Confirm that the secret already exists
checkExists, err := models.GetSecrets(&s, false) checkExists, err := models.GetSecrets(&s, false)
if err != nil { 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
}
*/
}

View File

@@ -44,17 +44,20 @@ func JwtAuthAdminMiddleware() gin.HandlerFunc {
return 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 { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
c.Abort() c.Abort()
return 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 // 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.String(http.StatusUnauthorized, "User role is Non-Admin")
c.Abort() c.Abort()
return return

3
models/audit.go Normal file
View File

@@ -0,0 +1,3 @@
package models
// Define audit functions

55
models/group.go Normal file
View File

@@ -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
}

View File

@@ -227,7 +227,8 @@ func LookupNamingContext(ldaps *ldap.Conn) string {
return defaultNamingContext 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 var err error
username = CheckUsername(username) username = CheckUsername(username)

8
models/permissions.go Normal file
View File

@@ -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"`
}

6
models/safe.go Normal file
View File

@@ -0,0 +1,6 @@
package models
type Safe struct {
SafeId int `db:"SafeId"`
SafeName string `db:"SafeName"`
}

View File

@@ -6,16 +6,19 @@ import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt"
"io" "io"
"log" "log"
"strings"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
) )
// We use the json:"-" field tag to prevent showing these details to the user // We use the json:"-" field tag to prevent showing these details to the user
type Secret struct { type Secret struct {
SecretId int `db:"SecretId" json:"-"` SecretId int `db:"SecretId" json:"-"`
RoleId int `db:"RoleId" json:"-"` //RoleId int `db:"RoleId" json:"-"`
SafeId int `db:"SafeId"`
DeviceName string `db:"DeviceName"` DeviceName string `db:"DeviceName"`
DeviceCategory string `db:"DeviceCategory"` DeviceCategory string `db:"DeviceCategory"`
UserName string `db:"UserName"` UserName string `db:"UserName"`
@@ -29,7 +32,7 @@ func (s *Secret) SaveSecret() (*Secret, error) {
var err error var err error
log.Printf("SaveSecret storing values '%v'\n", s) 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 { if err != nil {
log.Printf("StoreSecret error executing sql record : '%s'\n", err) log.Printf("StoreSecret error executing sql record : '%s'\n", err)
@@ -43,6 +46,96 @@ func (s *Secret) SaveSecret() (*Secret, error) {
return s, nil 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 // Returns all matching secrets, up to caller to determine how to deal with multiple results
func GetSecrets(s *Secret, adminRole bool) ([]Secret, error) { func GetSecrets(s *Secret, adminRole bool) ([]Secret, error) {
var err 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 // Determine whether to query for a specific device or a category of devices
// Prefer querying device name than category // Prefer querying device name than category
if s.DeviceName != "" && s.DeviceCategory != "" && s.UserName != "" { 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 != "" { } 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 != "" { } 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 != "" { } 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 != "" { } 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 != "" { } 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 != "" { } 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 { } 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") //log.Printf("GetSecret no valid search options specified\n")
//err = errors.New("no valid search options specified") //err = errors.New("no valid search options specified")
//return secretResults, err //return secretResults, err

View File

@@ -26,8 +26,7 @@ const createRoles string = `
CREATE TABLE IF NOT EXISTS roles ( CREATE TABLE IF NOT EXISTS roles (
RoleId INTEGER PRIMARY KEY ASC, RoleId INTEGER PRIMARY KEY ASC,
RoleName VARCHAR, RoleName VARCHAR,
ReadOnly BOOLEAN, ReadOnly BOOLEAN
Admin BOOLEAN
); );
` `
@@ -37,8 +36,8 @@ const createUsers string = `
GroupId INTEGER, GroupId INTEGER,
UserName VARCHAR, UserName VARCHAR,
Password VARCHAR, Password VARCHAR,
Admin BOOLEAN DEFAULT 0,
LdapUser BOOLEAN DEFAULT 0, LdapUser BOOLEAN DEFAULT 0,
LdapDN VARCHAR DEFAULT '',
FOREIGN KEY (GroupId) REFERENCES groups(GroupId) FOREIGN KEY (GroupId) REFERENCES groups(GroupId)
); );
` `
@@ -55,7 +54,8 @@ const createGroups string = `
GroupId INTEGER PRIMARY KEY ASC, GroupId INTEGER PRIMARY KEY ASC,
GroupName VARCHAR, GroupName VARCHAR,
LdapGroup BOOLEAN DEFAULT 0, LdapGroup BOOLEAN DEFAULT 0,
LdapDN VARCHAR DEFAULT '' LdapDN VARCHAR DEFAULT '',
Admin BOOLEAN DEFAULT 0
); );
` `
@@ -213,6 +213,36 @@ func CreateTables() {
os.Exit(1) 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 // 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 { if _, err = db.Exec(createSchema); err != nil {
log.Printf("Error checking schema table : '%s'", err) log.Printf("Error checking schema table : '%s'", err)
@@ -246,8 +276,8 @@ func CreateTables() {
GroupId INTEGER, GroupId INTEGER,
UserName VARCHAR, UserName VARCHAR,
Password VARCHAR, Password VARCHAR,
LdapUser BOOLEAN DEFAULT 0, Admin BOOLEAN DEFAULT 0,
LdapDN VARCHAR DEFAULT '', LdapUser BOOLEAN DEFAULT 0
FOREIGN KEY (GroupId) REFERENCES groups(GroupId) FOREIGN KEY (GroupId) REFERENCES groups(GroupId)
); );
INSERT INTO users SELECT * FROM _users_old; INSERT INTO users SELECT * FROM _users_old;

View File

@@ -5,20 +5,19 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"net/http"
"smt/utils/token" "smt/utils/token"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
type User struct { type User struct {
UserId int `db:"UserId" json:"userId"` UserId int `db:"UserId" json:"userId"`
RoleId int `db:"RoleId" json:"roleId"` GroupId int `db:"GroupId" json:"groupId"`
UserName string `db:"UserName" json:"userName"` UserName string `db:"UserName" json:"userName"`
Password string `db:"Password" json:"-"` Password string `db:"Password" json:"-"`
LdapUser bool `db:"LdapUser" json:"ldapUser"` 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 { type UserRole struct {
@@ -28,15 +27,32 @@ type UserRole struct {
Admin bool `db:"Admin"` 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) { func (u *User) SaveUser() (*User, error) {
var err error var err error
// Validate username not already in use // Validate username not already in use
_, err = GetUserByName(u.UserName) _, err = UserGetByName(u.UserName)
if err != nil && err.Error() == "user not found" { if err != nil && err.Error() == "user not found" {
log.Printf("SaveUser confirmed no existing user, continuing with creation of user '%s'\n", u.UserName) 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 { if err != nil {
log.Printf("SaveUser error executing sql record : '%s'\n", err) 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 { func (u *User) DeleteUser() error {
// Validate username exists // Validate username exists
_, err := GetUserByName(u.UserName) _, err := UserGetByName(u.UserName)
if err != nil { if err != nil {
log.Printf("DeleteUser error finding user account to remove : '%s'\n", err) log.Printf("DeleteUser error finding user account to remove : '%s'\n", err)
return err return err
@@ -180,7 +196,7 @@ func LdapLoginCheck(username string, password string) (User, error) {
u.UserName = username u.UserName = username
// try to get LDAP group membership // try to get LDAP group membership
groups, err := GetLdapGroupMembership(username, password) ldapGroups, err := LdapGetGroupMembership(username, password)
if err != nil { if err != nil {
if err.Error() == "invalid user credentials" { if err.Error() == "invalid user credentials" {
return u, nil 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 // Compare all roles against the list of user's group membership
roleList, err := QueryRoles() //roleList, err := QueryRoles()
groupList, err := GroupList()
if err != nil { if err != nil {
return u, err return u, err
} }
matchFound := false matchFound := false
for _, role := range roleList { /*
for _, group := range groups { for _, role := range roleList {
if role.LdapGroup == group { for _, group := range groups {
log.Printf("Found match with role '%s' and LDAP group '%s', user is allowed role ID '%d'\n", role.RoleName, role.LdapGroup, role.RoleId) if role.LdapGroup == group {
u.RoleId = role.RoleId 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 matchFound = true
break break
} //else { } else {
//log.Printf("Role '%s' with LDAP group '%s' not match user group '%s'\n", role.RoleName, role.LdapGroup, group) 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 return nil
} }
func GetUserByID(uid uint) (User, error) { func UserGetByID(uid uint) (User, error) {
var u User 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 var u User
@@ -259,6 +290,7 @@ func GetUserByName(username string) (User, error) {
return u, nil return u, nil
} }
/*
func GetUserRoleByID(uid uint) (UserRole, error) { func GetUserRoleByID(uid uint) (UserRole, error) {
var ur UserRole var ur UserRole
@@ -273,8 +305,26 @@ func GetUserRoleByID(uid uint) (UserRole, error) {
return ur, nil 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 var ur UserRole
@@ -286,17 +336,45 @@ func GetUserRoleFromToken(c *gin.Context) (UserRole, error) {
} }
// Query database for matching user object // 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) 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 { if err != nil {
log.Printf("GetUserRoleFromToken received error when querying database : '%s'\n", err) log.Printf("UserGetRoleFromToken received error when querying database : '%s'\n", err)
return ur, errors.New("GetUserRoleFromToken user not found") return ur, errors.New("UserGetRoleFromToken user not found")
} }
return ur, nil 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 var results []User
// Query database for role definitions // Query database for role definitions
@@ -322,3 +400,23 @@ func QueryUsers() ([]User, error) {
return results, nil 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 83 KiB