diff --git a/controllers/auth.go b/controllers/auth.go index 3e4ed15..845d962 100644 --- a/controllers/auth.go +++ b/controllers/auth.go @@ -168,44 +168,45 @@ func RegisterUser(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "user registration success"}) } -func AddRole(c *gin.Context) { - var input AddRoleInput +/* + func AddRole(c *gin.Context) { + var input AddRoleInput - if err := c.ShouldBindJSON(&input); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // define the new role properties + r := models.Role{} + r.RoleName = input.RoleName + r.ReadOnly = input.ReadOnly + r.Admin = input.Admin + r.LdapGroup = input.LdapGroup + + // Check if role already exists + testRole, _ := models.GetRoleByName(r.RoleName) + log.Printf("AddRole checking if role '%s' already exists\n", r.RoleName) + + if (models.Role{} == testRole) { + log.Printf("AddRole confirmed no existing rolename\n") + } else { + errorString := fmt.Sprintf("attempt to register conflicting rolename '%s'", r.RoleName) + log.Printf("Register error : '%s'\n", errorString) + c.JSON(http.StatusBadRequest, gin.H{"error": errorString}) + return + } + + _, err := r.AddRole() + + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"Error creating role": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "role creation success"}) } - - // define the new role properties - r := models.Role{} - r.RoleName = input.RoleName - r.ReadOnly = input.ReadOnly - r.Admin = input.Admin - r.LdapGroup = input.LdapGroup - - // Check if role already exists - testRole, _ := models.GetRoleByName(r.RoleName) - log.Printf("AddRole checking if role '%s' already exists\n", r.RoleName) - - if (models.Role{} == testRole) { - log.Printf("AddRole confirmed no existing rolename\n") - } else { - errorString := fmt.Sprintf("attempt to register conflicting rolename '%s'", r.RoleName) - log.Printf("Register error : '%s'\n", errorString) - c.JSON(http.StatusBadRequest, gin.H{"error": errorString}) - return - } - - _, err := r.AddRole() - - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"Error creating role": err.Error()}) - return - } - - c.JSON(http.StatusOK, gin.H{"message": "role creation success"}) -} - +*/ func Login(c *gin.Context) { var input LoginInput @@ -254,17 +255,18 @@ func CurrentUser(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "success", "data": u}) } -func GetRoles(c *gin.Context) { - roles, err := models.QueryRoles() +/* + func GetRoles(c *gin.Context) { + roles, err := models.QueryRoles() - 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 + } + + c.JSON(http.StatusOK, gin.H{"message": "success", "data": roles}) } - - c.JSON(http.StatusOK, gin.H{"message": "success", "data": roles}) - -} +*/ func GetUsers(c *gin.Context) { users, err := models.UserList() diff --git a/controllers/retrieve_secrets.go b/controllers/retrieve_secrets.go index 19fe194..d00bcf1 100644 --- a/controllers/retrieve_secrets.go +++ b/controllers/retrieve_secrets.go @@ -4,7 +4,6 @@ import ( "log" "net/http" "smt/models" - "smt/utils/token" "github.com/gin-gonic/gin" ) @@ -165,12 +164,14 @@ func retrieveSpecifiedSecret(s *models.Secret, c *gin.Context) { */ var results []models.Secret - var userIsAdmin = false - user_id, err := token.ExtractTokenID(c) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"}) - return - } + /* + user_id, err := token.ExtractTokenID(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"}) + return + } + */ + user_id := c.GetInt("user-id") // Work out which safe to query for this user if the safe was not specified safeList, err := models.UserGetSafesAllowed(int(user_id)) @@ -182,21 +183,13 @@ func retrieveSpecifiedSecret(s *models.Secret, c *gin.Context) { // 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 - } else { - // Don't apply a role filter if user has admin role - results, err = models.SecretsGetMultipleSafes(s, true, []int{}) - } - + errString := "no matching secret or user has no access to specified secret" + log.Printf("retrieveSpecifiedSecret %s\n", errString) + c.JSON(http.StatusBadRequest, gin.H{"error": errString}) + return } 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) - results, err = models.SecretsGetMultipleSafes(s, userIsAdmin, []int{s.SafeId}) + results, err = models.SecretsGetMultipleSafes(s, []int{s.SafeId}) } else { // Create a list of all the safes this user can access var safeIds []int @@ -204,7 +197,7 @@ func retrieveSpecifiedSecret(s *models.Secret, c *gin.Context) { safeIds = append(safeIds, safe.SafeId) } - results, err = models.SecretsGetMultipleSafes(s, false, safeIds) + results, err = models.SecretsGetMultipleSafes(s, safeIds) } if err != nil { @@ -250,12 +243,8 @@ func ListSecrets(c *gin.Context) { */ var results []models.Secret - var userIsAdmin = false - user_id, err := token.ExtractTokenID(c) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"}) - return - } + s := models.Secret{} + user_id := c.GetInt("user-id") // Work out which safe to query for this user if the safe was not specified safeList, err := models.UserGetSafesAllowed(int(user_id)) @@ -267,18 +256,13 @@ func ListSecrets(c *gin.Context) { // 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 - } else { - // Don't apply a role filter if user has admin role - results, err = models.SecretsGetMultipleSafes(&models.Secret{}, true, []int{}) - } - + errString := "no matching secret or user has no access to specified secret" + log.Printf("ListSecrets %s\n", errString) + c.JSON(http.StatusBadRequest, gin.H{"error": errString}) + return } else if len(safeList) == 1 { - userIsAdmin = safeList[0].AdminUser || safeList[0].AdminGroup - results, err = models.SecretsGetMultipleSafes(&models.Secret{}, userIsAdmin, []int{safeList[0].SafeId}) + s.SafeId = safeList[0].SafeId + results, err = models.SecretsGetMultipleSafes(&s, []int{s.SafeId}) } else { // Create a list of all the safes this user can access var safeIds []int @@ -286,7 +270,7 @@ func ListSecrets(c *gin.Context) { safeIds = append(safeIds, safe.SafeId) } - results, err = models.SecretsGetMultipleSafes(&models.Secret{}, false, safeIds) + results, err = models.SecretsGetMultipleSafes(&s, safeIds) } if err != nil { diff --git a/controllers/store_secrets.go b/controllers/store_secrets.go index 7ee531b..047d546 100644 --- a/controllers/store_secrets.go +++ b/controllers/store_secrets.go @@ -111,6 +111,23 @@ func StoreSecret(c *gin.Context) { // 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) { + + // 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 + + // NO. That is too complicated! + + // Lets try to make this more simple + // A user can only be in one group + // A group can have permissions on multiple safes + + // If a user is an admin they can do user related functions like create users, groups, assign permissions + // But a user has to have a permission that maps the group to the safe in order to perform CRUD operations + + // What does a group being an admin give them? All users in that group can do user related function + // Query all safes for secrets matching parameters specified matchingSecrets, err := models.SecretsSearchAllSafes(s) if err != nil { @@ -133,14 +150,7 @@ func CheckUpdateSecretAllowed(s *models.Secret, user_id int) (int, error) { 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 { @@ -167,19 +177,6 @@ func CheckUpdateSecretAllowed(s *models.Secret, user_id int) (int, error) { 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) - } - } } } @@ -213,11 +210,14 @@ func UpdateSecret(c *gin.Context) { } */ - user_id, err := token.ExtractTokenID(c) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"}) - return - } + /* + user_id, err := token.ExtractTokenID(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"}) + return + } + */ + user_id := c.GetInt("user-id") // Populate fields s := models.Secret{} @@ -226,6 +226,15 @@ 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 + allowedUpdate, err := CheckUpdateSecretAllowed(&s, int(user_id)) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("error determining secret : '%s'", err)}) @@ -236,11 +245,6 @@ func UpdateSecret(c *gin.Context) { 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 /* @@ -319,10 +323,8 @@ 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 ") + log.Printf("SecretCheckSafeAllowed unexpected\n") } } diff --git a/main.go b/main.go index b70b9fb..56cafb8 100644 --- a/main.go +++ b/main.go @@ -246,11 +246,13 @@ func main() { adminOnly.POST("/user/add", controllers.RegisterUser) // TODO //adminOnly.POST("/user/update", controllers.UpdateUser) - adminOnly.GET("/roles", controllers.GetRoles) - adminOnly.POST("/role/add", controllers.AddRole) adminOnly.GET("/users", controllers.GetUsers) adminOnly.POST("/unlock", controllers.Unlock) + // Deprecated + //adminOnly.GET("/roles", controllers.GetRoles) + //adminOnly.POST("/role/add", controllers.AddRole) + // Get secrets protected := router.Group("/api/secret") protected.Use(middlewares.JwtAuthMiddleware()) diff --git a/middlewares/middlewares.go b/middlewares/middlewares.go index 223d1fb..3ffbcdc 100644 --- a/middlewares/middlewares.go +++ b/middlewares/middlewares.go @@ -19,6 +19,19 @@ func JwtAuthMiddleware() gin.HandlerFunc { c.Abort() return } + + // Token is valid, extract user_id + user_id, err := token.ExtractTokenID(c) + if err != nil { + log.Printf("JwtAuthMiddleware user_id could not be parsed : '%s'\n", err) + c.String(http.StatusUnauthorized, "Unauthorized") + c.Abort() + return + } + // Store user id in context for accessing later + log.Printf("JwtAuthMiddleware storing user-id '%d'\n", user_id) + c.Set("user-id", user_id) + c.Next() } } diff --git a/models/role.go b/models/role.go.txt similarity index 100% rename from models/role.go rename to models/role.go.txt diff --git a/models/secret.go b/models/secret.go index e44224d..45ceb18 100644 --- a/models/secret.go +++ b/models/secret.go @@ -111,7 +111,7 @@ func SecretsSearchAllSafes(s *Secret) ([]Secret, error) { } // SecretsGetMultipleSafes queries the specified safes for matching secrets -func SecretsGetMultipleSafes(s *Secret, adminRole bool, safeIds []int) ([]Secret, error) { +func SecretsGetMultipleSafes(s *Secret, safeIds []int) ([]Secret, error) { var err error var secretResults []Secret @@ -130,25 +130,19 @@ func SecretsGetMultipleSafes(s *Secret, adminRole bool, safeIds []int) ([]Secret args := []interface{}{} var query string - if adminRole { - log.Printf("SecretsGetMultipleSafes using admin role so not limiting to specific safes\n") - // No need to limit query to any safe - query = "SELECT * FROM secrets WHERE 1=1 " - } else { - // 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, ",") + // 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, ",") - // Create query with the necessary placeholders - query = fmt.Sprintf("SELECT * FROM secrets WHERE SafeId IN (%s) ", placeholderStr) + // Create query with the necessary placeholders + query = fmt.Sprintf("SELECT * FROM secrets WHERE SafeId IN (%s) ", placeholderStr) - // Add the Safe Ids to the arguments list - for _, g := range safeIds { - args = append(args, g) - } + // Add the Safe Ids to the arguments list + for _, g := range safeIds { + args = append(args, g) } // Add any other arguments to the query if they were specified diff --git a/models/setup.go b/models/setup.go index 600ed28..f527834 100644 --- a/models/setup.go +++ b/models/setup.go @@ -20,8 +20,7 @@ const ( sqlFile = "smt.db" ) -// TODO drop LdapGroup column - +/* const createRoles string = ` CREATE TABLE IF NOT EXISTS roles ( RoleId INTEGER PRIMARY KEY ASC, @@ -29,6 +28,7 @@ const createRoles string = ` ReadOnly BOOLEAN ); ` +*/ const createUsers string = ` CREATE TABLE IF NOT EXISTS users ( @@ -62,11 +62,11 @@ const createGroups string = ` const createPermissions = ` CREATE TABLE IF NOT EXISTS permissions ( PermissionId INTEGER PRIMARY KEY ASC, - RoleId INTEGER, + Description VARCHAR DEFAULT '', + ReadOnly BOOLEAN DEFAULT 0, SafeId INTEGER, UserId INTEGER, GroupId INTEGER, - FOREIGN KEY (RoleId) REFERENCES roles(RoleId), FOREIGN KEY (SafeId) REFERENCES safes(SafeId), FOREIGN KEY (UserId) REFERENCES users(UserId), FOREIGN KEY (GroupId) REFERENCES groups(GroupId) @@ -131,23 +131,44 @@ func CreateTables() { var err error var rowCount int // Create database tables if it doesn't exist - // Roles table should go first since other tables refer to it - if _, err = db.Exec(createRoles); err != nil { - log.Printf("Error checking roles table : '%s'", err) + /* + // Roles table should go first since other tables refer to it + if _, err = db.Exec(createRoles); err != nil { + log.Printf("Error checking roles table : '%s'", err) + os.Exit(1) + } + rowCount, _ = CheckCount("roles") + if rowCount == 0 { + if _, err = db.Exec("INSERT INTO roles VALUES(1, 'Admin', false);"); err != nil { + log.Printf("Error adding initial admin role : '%s'", err) + os.Exit(1) + } + if _, err = db.Exec("INSERT INTO roles VALUES(2, 'UserRole', false);"); err != nil { + log.Printf("Error adding initial user role : '%s'", err) + os.Exit(1) + } + if _, err = db.Exec("INSERT INTO roles VALUES(3, 'GuestRole', true);"); err != nil { + log.Printf("Error adding initial guest role : '%s'", err) + os.Exit(1) + } + } + */ + + // groups table + if _, err = db.Exec(createGroups); err != nil { + log.Printf("Error checking groups table : '%s'", err) os.Exit(1) } - rowCount, _ = CheckCount("roles") + + // Add initial groups + rowCount, _ = CheckCount("groups") if rowCount == 0 { - if _, err = db.Exec("INSERT INTO roles VALUES(1, 'Admin', false);"); err != nil { - log.Printf("Error adding initial admin role : '%s'", err) + 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 roles VALUES(2, 'UserRole', false);"); err != nil { - log.Printf("Error adding initial user role : '%s'", err) - os.Exit(1) - } - if _, err = db.Exec("INSERT INTO roles VALUES(3, 'GuestRole', true);"); err != nil { - log.Printf("Error adding initial guest role : '%s'", err) + 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) } } @@ -169,7 +190,11 @@ func CreateTables() { cryptText, _ := bcrypt.GenerateFromPassword([]byte(initialPassword), bcrypt.DefaultCost) initialPassword = string(cryptText) } - if _, err = db.Exec("INSERT INTO users (RoleId, UserName, Password, LdapUser) VALUES(1, 1, 'Administrator', ?, 0);", initialPassword); err != nil { + if _, err = db.Exec("INSERT INTO users (UserId, GroupId, UserName, Password, LdapUser, Admin) VALUES(1, 1, 'Administrator', ?, false, true);", initialPassword); err != nil { + log.Printf("Error adding initial admin role : '%s'", err) + os.Exit(1) + } + if _, err = db.Exec("INSERT INTO users (UserId, GroupId, UserName, Password, LdapUser, Admin) VALUES(2, 2, 'User', ?, false, false);", initialPassword); err != nil { log.Printf("Error adding initial admin role : '%s'", err) os.Exit(1) } @@ -201,46 +226,23 @@ func CreateTables() { os.Exit(1) } - // groups table - if _, err = db.Exec(createGroups); err != nil { - log.Printf("Error checking groups table : '%s'", err) - os.Exit(1) - } - // permissions table if _, err = db.Exec(createPermissions); err != nil { log.Printf("Error checking permissions table : '%s'", err) 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 { + if _, err = db.Exec("INSERT INTO permissions (Description, ReadOnly, GroupId, SafeId) VALUES('Default Admin Group Permission', false, 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 { + if _, err = db.Exec("INSERT INTO permissions (Description, ReadOnly, SafeId, GroupId) VALUES('Default User Group Permission', false, 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 @@ -326,7 +328,7 @@ func CreateTables() { DROP TABLE _secrets_old; `) if err != nil { - log.Printf("Error altering secrets table to renmove RoleId column : '%s'\n", err) + log.Printf("Error altering secrets table to remove RoleId column : '%s'\n", err) os.Exit(1) } } @@ -342,6 +344,47 @@ func CreateTables() { } } + // Remove the Admin column from roles table + rolesAdminCheck, _ := CheckColumnExists("roles", "Admin") + if rolesAdminCheck { + _, err := db.Exec("ALTER TABLE roles DROP COLUMN Admin;") + if err != nil { + log.Printf("Error altering roles table to remove Admin column : '%s'\n", err) + os.Exit(1) + } + } + + // Remove the RoleId from permissiosn table + permissionsRoleIdCheck, _ := CheckColumnExists("permissions", "RoleId") + if permissionsRoleIdCheck { + _, err := db.Exec(` + PRAGMA foreign_keys=off; + BEGIN TRANSACTION; + ALTER TABLE permissions RENAME TO _permissions_old; + CREATE TABLE permissions + ( + PermissionId INTEGER PRIMARY KEY ASC, + Description VARCHAR DEFAULT '', + ReadOnly BOOLEAN DEFAULT 0, + SafeId INTEGER, + UserId INTEGER, + GroupId INTEGER, + FOREIGN KEY (SafeId) REFERENCES safes(SafeId), + FOREIGN KEY (UserId) REFERENCES users(UserId), + FOREIGN KEY (GroupId) REFERENCES groups(GroupId) + ); + INSERT INTO permissions SELECT * FROM _permissions_old; + ALTER TABLE permissions DROP COLUMN RoleId; + COMMIT; + PRAGMA foreign_keys=on; + DROP TABLE _permissions_old; + `) + if err != nil { + log.Printf("Error altering permissions table to remove RoleId column : '%s'\n", err) + os.Exit(1) + } + } + /* // Database updates added after initial version released ldapCheck, _ := CheckColumnExists("roles", "LdapGroup") diff --git a/models/user.go b/models/user.go index abadf6f..1d36361 100644 --- a/models/user.go +++ b/models/user.go @@ -37,11 +37,9 @@ type UserGroup struct { type UserSafe struct { User - AdminUser bool `db:"AdminUser"` - AdminGroup bool `db:"AdminGroup"` - SafeId int `db:"SafeId"` - SafeName string `db:"SafeName"` - GroupId int `db:"GroupId"` + SafeId int `db:"SafeId"` + SafeName string `db:"SafeName"` + GroupId int `db:"GroupId"` } func (u *User) SaveUser() (*User, error) { @@ -360,7 +358,6 @@ func UserGetSafesAllowed(userId int) ([]UserSafe, error) { // join users, groups and permissions rows, err := db.Queryx(` 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 diff --git a/www/database.png b/www/database.png index d7cea2f..d618a08 100644 Binary files a/www/database.png and b/www/database.png differ