From cac2986b59344292c5d0c4118d66c37be7dde423 Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Sat, 14 Dec 2024 22:38:06 -0500 Subject: [PATCH 1/3] authority system start --- models/permission.go | 16 ++ models/role-permissions.go | 16 ++ models/roles.go | 16 ++ models/user-roles.go | 16 ++ run-auto-migrations.go | 4 + utils/authority.go | 408 +++++++++++++++++++++++++++++++++++++ 6 files changed, 476 insertions(+) create mode 100644 models/permission.go create mode 100644 models/role-permissions.go create mode 100644 models/roles.go create mode 100644 models/user-roles.go create mode 100644 utils/authority.go diff --git a/models/permission.go b/models/permission.go new file mode 100644 index 0000000..933b16d --- /dev/null +++ b/models/permission.go @@ -0,0 +1,16 @@ +// Copyright (c) 2025 Zeni Kim +// Use of this source code is governed by MIT-style +// license that can be found in the LICENSE file. + +package models + +type Permission struct { + BaseModel + Name string + Slug string +} + +// TableName sets the table name +func (Permission) TableName() string { + return "permissions" +} diff --git a/models/role-permissions.go b/models/role-permissions.go new file mode 100644 index 0000000..99cf3fe --- /dev/null +++ b/models/role-permissions.go @@ -0,0 +1,16 @@ +// Copyright (c) 2025 Zeni Kim +// Use of this source code is governed by MIT-style +// license that can be found in the LICENSE file. + +package models + +type RolePermission struct { + BaseModel + RoleID uint // Role id + PermissionID uint // Permission id +} + +// TableName sets the table name +func (RolePermission) TableName() string { + return "role_permissions" +} diff --git a/models/roles.go b/models/roles.go new file mode 100644 index 0000000..decd675 --- /dev/null +++ b/models/roles.go @@ -0,0 +1,16 @@ +// Copyright (c) 2025 Zeni Kim +// Use of this source code is governed by MIT-style +// license that can be found in the LICENSE file. + +package models + +type Role struct { + BaseModel + Name string // The name of the role + Slug string // String based unique identifier of the role, (use hyphen seperated role name '-', instead of space) +} + +// TableName sets the table name +func (Role) TableName() string { + return "roles" +} diff --git a/models/user-roles.go b/models/user-roles.go new file mode 100644 index 0000000..9c69ba7 --- /dev/null +++ b/models/user-roles.go @@ -0,0 +1,16 @@ +// Copyright (c) 2025 Zeni Kim +// Use of this source code is governed by MIT-style +// license that can be found in the LICENSE file. + +package models + +type UserRole struct { + BaseModel + UserID string // The user id + RoleID uint // The role id +} + +// TableName sets the table name +func (UserRole) TableName() string { + return "user_roles" +} diff --git a/run-auto-migrations.go b/run-auto-migrations.go index 8410637..119a44c 100644 --- a/run-auto-migrations.go +++ b/run-auto-migrations.go @@ -18,4 +18,8 @@ func RunAutoMigrations() { // Add auto migrations for your models here... db.AutoMigrate(&models.User{}) + db.AutoMigrate(&models.UserRole{}) + db.AutoMigrate(&models.Role{}) + db.AutoMigrate(&models.RolePermission{}) + db.AutoMigrate(&models.Permission{}) } diff --git a/utils/authority.go b/utils/authority.go new file mode 100644 index 0000000..401f36b --- /dev/null +++ b/utils/authority.go @@ -0,0 +1,408 @@ +// Copyright (c) 2025 Zeni Kim +// Use of this source code is governed by MIT-style +// license that can be found in the LICENSE file. + +package utils + +import ( + "errors" + "fmt" + + "git.smarteching.com/goffee/core" + "git.smarteching.com/goffee/cup/models" + "gorm.io/gorm" +) + +type Authority struct{} + +var auth *Authority + +var ( + ErrPermissionInUse = errors.New("cannot delete assigned permission") + ErrPermissionNotFound = errors.New("permission not found") + ErrRoleInUse = errors.New("cannot delete assigned role") + ErrRoleNotFound = errors.New("role not found") +) + +// Add a new role to the database +func (a *Authority) CreateRole(c *core.Context, r models.Role) error { + roleSlug := r.Slug + var dbRole models.Role + res := c.GetGorm().Where("slug = ?", roleSlug).First(&dbRole) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + // create + createRes := c.GetGorm().Create(&r) + if createRes.Error != nil { + return createRes.Error + } + return nil + } + return res.Error + } + + return errors.New(fmt.Sprintf("role '%v' already exists", roleSlug)) +} + +// Add a new permission to the database +func (a *Authority) CreatePermission(c *core.Context, p models.Permission) error { + permSlug := p.Slug + var dbPerm models.Permission + res := c.GetGorm().Where("slug = ?", permSlug).First(&dbPerm) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + // create + createRes := c.GetGorm().Create(&p) + if createRes.Error != nil { + return createRes.Error + } + return nil + } + return res.Error + } + + return errors.New(fmt.Sprintf("permission '%v' already exists", permSlug)) +} + +// Assigns a group of permissions to a given role +func (a *Authority) AssignPermissionsToRole(c *core.Context, roleSlug string, permSlugs []string) error { + var role models.Role + rRes := c.GetGorm().Where("slug = ?", roleSlug).First(&role) + if rRes.Error != nil { + if errors.Is(rRes.Error, gorm.ErrRecordNotFound) { + return ErrRoleNotFound + } + return rRes.Error + } + var perms []models.Permission + for _, permSlug := range permSlugs { + var perm models.Permission + pRes := c.GetGorm().Where("slug = ?", permSlug).First(&perm) + if pRes.Error != nil { + if errors.Is(pRes.Error, gorm.ErrRecordNotFound) { + return ErrPermissionNotFound + } + return pRes.Error + } + perms = append(perms, perm) + } + tx := c.GetGorm().Begin() + for _, perm := range perms { + var rolePerm models.RolePermission + res := c.GetGorm().Where("role_id = ?", role.ID).Where("permission_id =?", perm.ID).First(&rolePerm) + if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) { + cRes := tx.Create(&models.RolePermission{RoleID: role.ID, PermissionID: perm.ID}) + if cRes.Error != nil { + tx.Rollback() + return cRes.Error + } + } + if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) { + tx.Rollback() + return res.Error + } + if rolePerm != (models.RolePermission{}) { + tx.Rollback() + return errors.New(fmt.Sprintf("permission '%v' is aleady assigned to the role '%v'", perm.Name, role.Name)) + } + rolePerm = models.RolePermission{} + } + return tx.Commit().Error +} + +// Assigns a role to a given user +func (a *Authority) AssignRoleToUser(c *core.Context, userID interface{}, roleSlug string) error { + userIDStr := fmt.Sprintf("%v", userID) + var role models.Role + res := c.GetGorm().Where("slug = ?", roleSlug).First(&role) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return ErrRoleNotFound + } + return res.Error + } + var userRole models.UserRole + res = c.GetGorm().Where("user_id = ?", userIDStr).Where("role_id = ?", role.ID).First(&userRole) + if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) { + c.GetGorm().Create(&models.UserRole{UserID: userIDStr, RoleID: role.ID}) + return nil + } + if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) { + return res.Error + } + + return errors.New(fmt.Sprintf("this role '%v' is aleady assigned to the user", roleSlug)) +} + +// Checks if a role is assigned to a user +func (a *Authority) CheckUserRole(c *core.Context, userID interface{}, roleSlug string) (bool, error) { + userIDStr := fmt.Sprintf("%v", userID) + // find the role + var role models.Role + res := c.GetGorm().Where("slug = ?", roleSlug).First(&role) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return false, ErrRoleNotFound + } + return false, res.Error + } + + // check if the role is a assigned + var userRole models.UserRole + res = c.GetGorm().Where("user_id = ?", userIDStr).Where("role_id = ?", role.ID).First(&userRole) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return false, nil + } + return false, res.Error + } + + return true, nil +} + +// Checks if a permission is assigned to a user +func (a *Authority) CheckUserPermission(c *core.Context, userID interface{}, permSlug string) (bool, error) { + userIDStr := fmt.Sprintf("%v", userID) + // the user role + var userRoles []models.UserRole + res := c.GetGorm().Where("user_id = ?", userIDStr).Find(&userRoles) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return false, nil + } + return false, res.Error + } + + //prepare an array of role ids + var roleIDs []interface{} + for _, r := range userRoles { + roleIDs = append(roleIDs, r.RoleID) + } + + // find the permission + var perm models.Permission + res = c.GetGorm().Where("slug = ?", permSlug).First(&perm) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return false, ErrPermissionNotFound + } + return false, res.Error + } + + // find the role permission + var rolePermission models.RolePermission + res = c.GetGorm().Where("role_id IN (?)", roleIDs).Where("permission_id = ?", perm.ID).First(&rolePermission) + if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) { + return false, nil + } + if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) { + return false, res.Error + } + + return true, nil +} + +// Checks if a permission is assigned to a role +func (a *Authority) CheckRolePermission(c *core.Context, roleSlug string, permSlug string) (bool, error) { + // find the role + var role models.Role + res := c.GetGorm().Where("slug = ?", roleSlug).First(&role) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return false, ErrRoleNotFound + } + return false, res.Error + } + + // find the permission + var perm models.Permission + res = c.GetGorm().Where("slug = ?", permSlug).First(&perm) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return false, ErrPermissionNotFound + } + return false, res.Error + } + + // find the rolePermission + var rolePermission models.RolePermission + res = c.GetGorm().Where("role_id = ?", role.ID).Where("permission_id = ?", perm.ID).First(&rolePermission) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return false, nil + } + return false, res.Error + } + + return true, nil +} + +// Revokes a roles's permission +func (a *Authority) RevokeRolePermission(c *core.Context, roleSlug string, permSlug string) error { + // find the role + var role models.Role + res := c.GetGorm().Where("slug = ?", roleSlug).First(&role) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return ErrRoleNotFound + } + return res.Error + } + + // find the permission + var perm models.Permission + res = c.GetGorm().Where("slug = ?", permSlug).First(&perm) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return ErrPermissionNotFound + } + return res.Error + } + + // revoke the permission + rRes := c.GetGorm().Where("role_id = ?", role.ID).Where("permission_id = ?", perm.ID).Delete(models.RolePermission{}) + if rRes.Error != nil { + return rRes.Error + } + + return nil +} + +// Returns all stored roles +func (a *Authority) GetAllRoles(c *core.Context) ([]models.Role, error) { + var roles []models.Role + res := c.GetGorm().Find(&roles) + if res.Error != nil { + return nil, res.Error + } + + return roles, nil +} + +// Returns all user assigned roles +func (a *Authority) GetUserRoles(c *core.Context, userID interface{}) ([]models.Role, error) { + userIDStr := fmt.Sprintf("%v", userID) + var userRoles []models.UserRole + res := c.GetGorm().Where("user_id = ?", userIDStr).Find(&userRoles) + if res.Error != nil { + return nil, res.Error + } + + var roleIDs []interface{} + for _, r := range userRoles { + roleIDs = append(roleIDs, r.RoleID) + } + + var roles []models.Role + res = c.GetGorm().Where("id IN (?)", roleIDs).Find(&roles) + if res.Error != nil { + return nil, res.Error + } + + return roles, nil +} + +// Returns all role assigned permissions +func (a *Authority) GetRolePermissions(c *core.Context, roleSlug string) ([]models.Permission, error) { + var role models.Role + res := c.GetGorm().Where("slug = ?", roleSlug).Find(&role) + if res.Error != nil { + return nil, res.Error + } + + var rolePerms []models.RolePermission + res = c.GetGorm().Where("role_id = ?", role.ID).Find(&rolePerms) + if res.Error != nil { + return nil, res.Error + } + var permIDs []interface{} + for _, rolePerm := range rolePerms { + permIDs = append(permIDs, rolePerm.PermissionID) + } + + var perms []models.Permission + res = c.GetGorm().Where("id IN (?)", permIDs).Find(&perms) + if res.Error != nil { + return nil, res.Error + } + + return perms, nil +} + +// Returns all stored permissions +func (a *Authority) GetAllPermissions(c *core.Context) ([]models.Permission, error) { + var perms []models.Permission + res := c.GetGorm().Find(&perms) + if res.Error != nil { + return nil, res.Error + } + + return perms, nil +} + +// Deletes a given role even if it's has assigned permissions +func (a *Authority) DeleteRole(c *core.Context, roleSlug string) error { + // find the role + var role models.Role + res := c.GetGorm().Where("slug = ?", roleSlug).First(&role) + if res.Error != nil { + return res.Error + } + + // check if the role is assigned to a user + var ca int64 + res = c.GetGorm().Model(models.UserRole{}).Where("role_id = ?", role.ID).Count(&ca) + if res.Error != nil { + return res.Error + } + + if ca != 0 { + // role is assigned + return ErrRoleInUse + } + tx := c.GetGorm().Begin() + // revoke the assignment of permissions before deleting the role + dRes := tx.Where("role_id = ?", role.ID).Delete(models.RolePermission{}) + if dRes.Error != nil { + tx.Rollback() + return dRes.Error + } + + // delete the role + dRes = c.GetGorm().Where("slug = ?", roleSlug).Delete(models.Role{}) + if dRes.Error != nil { + tx.Rollback() + return dRes.Error + } + + return tx.Commit().Error +} + +// Deletes a given permission +func (a *Authority) DeletePermission(c *core.Context, permSlug string) error { + // find the permission + var perm models.Permission + res := c.GetGorm().Where("slug = ?", permSlug).First(&perm) + if res.Error != nil { + return res.Error + } + + // check if the permission is assigned to a role + var rolePermission models.RolePermission + res = c.GetGorm().Where("permission_id = ?", perm.ID).First(&rolePermission) + if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) { + return res.Error + } + + if res.Error == nil { + return ErrPermissionInUse + } + + // delete the permission + dRes := c.GetGorm().Where("slug = ?", permSlug).Delete(models.Permission{}) + if dRes.Error != nil { + return dRes.Error + } + + return nil +} -- 2.39.2 From 43f3ad986e7492b54b8d87ca38dd9596c2003ab7 Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Mon, 16 Dec 2024 20:05:37 -0500 Subject: [PATCH 2/3] fix user id, seed data autority --- models/user-roles.go | 4 +-- run-auto-migrations.go | 15 ++++++++++- utils/authority.go | 22 +++++++--------- utils/helpers.go | 60 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 16 deletions(-) diff --git a/models/user-roles.go b/models/user-roles.go index 9c69ba7..1dfcc18 100644 --- a/models/user-roles.go +++ b/models/user-roles.go @@ -6,8 +6,8 @@ package models type UserRole struct { BaseModel - UserID string // The user id - RoleID uint // The role id + UserID uint // The user id + RoleID uint // The role id } // TableName sets the table name diff --git a/run-auto-migrations.go b/run-auto-migrations.go index 119a44c..996ae15 100644 --- a/run-auto-migrations.go +++ b/run-auto-migrations.go @@ -6,8 +6,12 @@ package main import ( + "errors" + "git.smarteching.com/goffee/core" "git.smarteching.com/goffee/cup/models" + "git.smarteching.com/goffee/cup/utils" + "gorm.io/gorm" ) func RunAutoMigrations() { @@ -17,9 +21,18 @@ func RunAutoMigrations() { //############################## // Add auto migrations for your models here... - db.AutoMigrate(&models.User{}) db.AutoMigrate(&models.UserRole{}) db.AutoMigrate(&models.Role{}) db.AutoMigrate(&models.RolePermission{}) db.AutoMigrate(&models.Permission{}) + + // End your auto migrations + + // Create seed data data, DO NOT TOUCH + if err := db.AutoMigrate(&models.User{}); err == nil && db.Migrator().HasTable(&models.User{}) { + if err := db.First(&models.User{}).Error; errors.Is(err, gorm.ErrRecordNotFound) { + utils.CreateSeedData() + } + } + } diff --git a/utils/authority.go b/utils/authority.go index 401f36b..33124e8 100644 --- a/utils/authority.go +++ b/utils/authority.go @@ -111,8 +111,7 @@ func (a *Authority) AssignPermissionsToRole(c *core.Context, roleSlug string, pe } // Assigns a role to a given user -func (a *Authority) AssignRoleToUser(c *core.Context, userID interface{}, roleSlug string) error { - userIDStr := fmt.Sprintf("%v", userID) +func (a *Authority) AssignRoleToUser(c *core.Context, userID uint, roleSlug string) error { var role models.Role res := c.GetGorm().Where("slug = ?", roleSlug).First(&role) if res.Error != nil { @@ -122,9 +121,9 @@ func (a *Authority) AssignRoleToUser(c *core.Context, userID interface{}, roleSl return res.Error } var userRole models.UserRole - res = c.GetGorm().Where("user_id = ?", userIDStr).Where("role_id = ?", role.ID).First(&userRole) + res = c.GetGorm().Where("user_id = ?", userID).Where("role_id = ?", role.ID).First(&userRole) if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) { - c.GetGorm().Create(&models.UserRole{UserID: userIDStr, RoleID: role.ID}) + c.GetGorm().Create(&models.UserRole{UserID: userID, RoleID: role.ID}) return nil } if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) { @@ -135,8 +134,7 @@ func (a *Authority) AssignRoleToUser(c *core.Context, userID interface{}, roleSl } // Checks if a role is assigned to a user -func (a *Authority) CheckUserRole(c *core.Context, userID interface{}, roleSlug string) (bool, error) { - userIDStr := fmt.Sprintf("%v", userID) +func (a *Authority) CheckUserRole(c *core.Context, userID uint, roleSlug string) (bool, error) { // find the role var role models.Role res := c.GetGorm().Where("slug = ?", roleSlug).First(&role) @@ -149,7 +147,7 @@ func (a *Authority) CheckUserRole(c *core.Context, userID interface{}, roleSlug // check if the role is a assigned var userRole models.UserRole - res = c.GetGorm().Where("user_id = ?", userIDStr).Where("role_id = ?", role.ID).First(&userRole) + res = c.GetGorm().Where("user_id = ?", userID).Where("role_id = ?", role.ID).First(&userRole) if res.Error != nil { if errors.Is(res.Error, gorm.ErrRecordNotFound) { return false, nil @@ -161,11 +159,10 @@ func (a *Authority) CheckUserRole(c *core.Context, userID interface{}, roleSlug } // Checks if a permission is assigned to a user -func (a *Authority) CheckUserPermission(c *core.Context, userID interface{}, permSlug string) (bool, error) { - userIDStr := fmt.Sprintf("%v", userID) +func (a *Authority) CheckUserPermission(c *core.Context, userID uint, permSlug string) (bool, error) { // the user role var userRoles []models.UserRole - res := c.GetGorm().Where("user_id = ?", userIDStr).Find(&userRoles) + res := c.GetGorm().Where("user_id = ?", userID).Find(&userRoles) if res.Error != nil { if errors.Is(res.Error, gorm.ErrRecordNotFound) { return false, nil @@ -280,10 +277,9 @@ func (a *Authority) GetAllRoles(c *core.Context) ([]models.Role, error) { } // Returns all user assigned roles -func (a *Authority) GetUserRoles(c *core.Context, userID interface{}) ([]models.Role, error) { - userIDStr := fmt.Sprintf("%v", userID) +func (a *Authority) GetUserRoles(c *core.Context, userID uint) ([]models.Role, error) { var userRoles []models.UserRole - res := c.GetGorm().Where("user_id = ?", userIDStr).Find(&userRoles) + res := c.GetGorm().Where("user_id = ?", userID).Find(&userRoles) if res.Error != nil { return nil, res.Error } diff --git a/utils/helpers.go b/utils/helpers.go index 36ac76b..8ca7dd8 100644 --- a/utils/helpers.go +++ b/utils/helpers.go @@ -8,9 +8,69 @@ package utils import ( "crypto/md5" "fmt" + "log" "time" + + "git.smarteching.com/goffee/core" + "git.smarteching.com/goffee/cup/models" ) +func CreateSeedData() { + + db := core.ResolveGorm() + var hashing = new(core.Hashing) + var role models.Role + + // seed user + password := "goffee" + name := "admin" + fullname := "Goffee administrator" + email := "change@me.com" + passwordHashed, _ := hashing.HashPassword(password) + + user := models.User{ + Name: name, + Fullname: fullname, + Email: email, + Password: passwordHashed, + } + result := db.Create(&user) + if result.Error != nil { + log.Fatal("Can't create seed user in database") + } + // seed roles + roles := []models.Role{ + {Name: "Administrator", Slug: "admin"}, + {Name: "Authenticated", Slug: "authenticated"}, + } + + for _, role := range roles { + result = db.Create(&role) + if result.Error != nil { + log.Fatal("Can't create seed role in database") + } + } + + // seed permission + permission := models.Permission{Name: "Users administration", Slug: "admin-users"} + result = db.Create(&permission) + if result.Error != nil { + log.Fatal("Can't create seed permission in database") + } + result = db.Where("slug = ?", "admin").First(&role) + if result.Error != nil { + log.Fatal("Can't find user admin in database") + } + result = db.Create(&models.RolePermission{RoleID: role.ID, PermissionID: permission.ID}) + if result.Error != nil { + log.Fatal("Can't register permission role in database") + } + result = db.Create(&models.UserRole{UserID: user.ID, RoleID: role.ID}) + if result.Error != nil { + log.Fatal("Can't assign role administrator to user in database") + } +} + // generate a hashed string to be used as key for caching auth jwt token func CreateAuthTokenHashedCacheKey(userID uint, userAgent string) string { cacheKey := fmt.Sprintf("userid:_%v_useragent:_%v_jwt_token", userID, userAgent) -- 2.39.2 From d43196318178d1684b5dee618e516c2924ba8ebc Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Tue, 17 Dec 2024 14:23:07 -0500 Subject: [PATCH 3/3] Update sample user admin to user authority, add revoke role to auth --- controllers/adminusers.go | 128 +++++++++++++++++++++++++- storage/templates/admin_useradd.html | 3 +- storage/templates/admin_useredit.html | 3 +- storage/templates/nopermission.html | 12 +++ utils/authority.go | 32 +++++++ 5 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 storage/templates/nopermission.html diff --git a/controllers/adminusers.go b/controllers/adminusers.go index b051308..9ffb762 100644 --- a/controllers/adminusers.go +++ b/controllers/adminusers.go @@ -19,6 +19,29 @@ import ( func AdminUsersList(c *core.Context) *core.Response { + // initiate authority + auth := new(utils.Authority) + var session = new(utils.SessionUser) + // true if session is active + hassession := session.Init(c) + + if !hassession { + type emptytemplate struct{} + emptyData := emptytemplate{} + return c.Response.Template("nopermission.html", emptyData) + } + + session_uid := session.GetUserID() + // check if user has role admin + is_admin, _ := auth.CheckUserRole(c, session_uid, "admin") + + if !is_admin { + type emptytemplate struct{} + emptyData := emptytemplate{} + return c.Response.Template("nopermission.html", emptyData) + } + + // continue if has session and is admin var users []models.User db := c.GetGorm() db.Find(&users) @@ -46,6 +69,10 @@ func AdminUsersList(c *core.Context) *core.Response { Value: "Email", ValueType: "string", }, + { + Value: "Roles", + ValueType: "string", + }, { Value: "Created", }, @@ -57,13 +84,26 @@ func AdminUsersList(c *core.Context) *core.Response { }, } + var listroles string rows := make([][]components.ContentTableTD, len(users)) for i, u := range users { + + roles, _ := auth.GetUserRoles(c, u.ID) + listroles = "" + + for _, role := range roles { + if listroles != "" { + listroles += ", " + } + listroles += role.Name + } + row := []components.ContentTableTD{ {Value: strconv.Itoa(int(u.ID))}, {Value: u.Name}, {Value: u.Fullname}, {Value: u.Email}, + {Value: listroles}, {Value: utils.FormatUnix(u.Created)}, {Value: utils.FormatUnix(u.Updated)}, {Value: components.ContentHref{ @@ -93,10 +133,44 @@ func AdminUsersList(c *core.Context) *core.Response { func AdminUsersAdd(c *core.Context) *core.Response { + // initiate authority + auth := new(utils.Authority) + var session = new(utils.SessionUser) + // true if session is active + hassession := session.Init(c) + + if !hassession { + type emptytemplate struct{} + emptyData := emptytemplate{} + return c.Response.Template("nopermission.html", emptyData) + } + + session_uid := session.GetUserID() + // check if user has role admin + is_admin, _ := auth.CheckUserRole(c, session_uid, "admin") + + if !is_admin { + type emptytemplate struct{} + emptyData := emptytemplate{} + return c.Response.Template("nopermission.html", emptyData) + } + // check if is submit submit := c.GetRequestParam("submit").(string) - errormessages := make([]string, 0) + var listroles []components.FormCheckboxItem + systemroles, _ := auth.GetAllRoles(c) + + for _, systemrole := range systemroles { + var userrole components.FormCheckboxItem + userrole.Label = systemrole.Name + userrole.Name = "roles" + userrole.Value = systemrole.Slug + if systemrole.Slug == "authenticated" { + userrole.IsChecked = true + } + listroles = append(listroles, userrole) + } name := "" fullname := "" @@ -109,6 +183,7 @@ func AdminUsersAdd(c *core.Context) *core.Response { fullname = c.GetRequestParam("fullname").(string) email = c.GetRequestParam("email").(string) password = c.GetRequestParam("password").(string) + roles := c.GetRequesForm("roles").([]string) // check if email exists var user models.User @@ -161,6 +236,11 @@ func AdminUsersAdd(c *core.Context) *core.Response { errormessages = append(errormessages, res.Error.Error()) } else { + // assign roles + for _, role := range roles { + auth.AssignRoleToUser(c, user.ID, role) + } + // fire user registered event err := c.GetEventsManager().Fire(&core.Event{Name: events.USER_REGISTERED, Payload: map[string]interface{}{ "user": user, @@ -180,6 +260,7 @@ func AdminUsersAdd(c *core.Context) *core.Response { FieldName components.FormInput FieldFullname components.FormInput FieldEmail components.FormInput + FieldRoles components.FormCheckbox FieldPassword components.FormInput ErrorMessages []string SubmitButton components.FormButton @@ -208,6 +289,10 @@ func AdminUsersAdd(c *core.Context) *core.Response { //Autocomplete: true, IsRequired: true, }, + FieldRoles: components.FormCheckbox{ + Label: "Roles", + AllCheckbox: listroles, + }, FieldPassword: components.FormInput{ ID: "password", Label: "Password", @@ -236,6 +321,29 @@ func AdminUsersEdit(c *core.Context) *core.Response { user_id := c.GetPathParam("id") errormessages := make([]string, 0) + // initiate authority + auth := new(utils.Authority) + + var listroles []components.FormCheckboxItem + + systemroles, _ := auth.GetAllRoles(c) + user_id_uint, _ := strconv.ParseUint(user_id.(string), 10, 32) + + userroles, _ := auth.GetUserRoles(c, uint(user_id_uint)) + + for _, systemrole := range systemroles { + var userrole components.FormCheckboxItem + userrole.Label = systemrole.Name + userrole.Name = "roles" + userrole.Value = systemrole.Slug + for _, ur := range userroles { + if ur.Slug == systemrole.Slug { + userrole.IsChecked = true + break + } + } + listroles = append(listroles, userrole) + } var origin_user models.User @@ -259,6 +367,7 @@ func AdminUsersEdit(c *core.Context) *core.Response { fullname = c.GetRequestParam("fullname").(string) email = c.GetRequestParam("email").(string) password = c.GetRequestParam("password").(string) + roles := c.GetRequesForm("roles").([]string) key := c.GetRequestParam("key") // check if email exists @@ -316,6 +425,14 @@ func AdminUsersEdit(c *core.Context) *core.Response { c.GetLogger().Error("Admin user: error updating") errormessages = append(errormessages, fmt.Sprintf("Error updating user %s:", user_id_string)) } else { + + // delete roles + auth.RevokeAllUserRole(c, origin_user.ID) + // assign roles + for _, role := range roles { + auth.AssignRoleToUser(c, origin_user.ID, role) + } + return c.Response.Redirect("/admin/users") } } @@ -325,6 +442,7 @@ func AdminUsersEdit(c *core.Context) *core.Response { FieldName components.FormInput FieldFullname components.FormInput FieldEmail components.FormInput + FieldRoles components.FormCheckbox FieldPassword components.FormInput FieldKey components.FormInput ErrorMessages []string @@ -355,6 +473,10 @@ func AdminUsersEdit(c *core.Context) *core.Response { //Autocomplete: true, IsRequired: true, }, + FieldRoles: components.FormCheckbox{ + Label: "Roles", + AllCheckbox: listroles, + }, FieldPassword: components.FormInput{ ID: "password", Label: "Password", @@ -461,6 +583,9 @@ func AdminUsersDelConfirm(c *core.Context) *core.Response { // check if is the seed user seed := "1" if user_id != seed { + + // initiate authority + auth := new(utils.Authority) // Delete the user // fire user delete event err := c.GetEventsManager().Fire(&core.Event{Name: events.USER_DELETED, Payload: map[string]interface{}{ @@ -469,6 +594,7 @@ func AdminUsersDelConfirm(c *core.Context) *core.Response { if err != nil { c.GetLogger().Error(err.Error()) } + auth.RevokeAllUserRole(c, origin_user.ID) result_db.Unscoped().Delete(&origin_user) } } diff --git a/storage/templates/admin_useradd.html b/storage/templates/admin_useradd.html index ccdb363..bc5d780 100644 --- a/storage/templates/admin_useradd.html +++ b/storage/templates/admin_useradd.html @@ -15,7 +15,8 @@
{{template "form_input" .FieldName}} {{template "form_input" .FieldFullname}} - {{template "form_input" .FieldEmail}} + {{template "form_input" .FieldEmail}} + {{template "form_checkbox" .FieldRoles}} {{template "form_input" .FieldPassword}}
{{template "form_button" .SubmitButton}} diff --git a/storage/templates/admin_useredit.html b/storage/templates/admin_useredit.html index 36ebfdf..3c49e08 100644 --- a/storage/templates/admin_useredit.html +++ b/storage/templates/admin_useredit.html @@ -15,7 +15,8 @@ {{template "form_input" .FieldName}} {{template "form_input" .FieldFullname}} - {{template "form_input" .FieldEmail}} + {{template "form_input" .FieldEmail}} + {{template "form_checkbox" .FieldRoles}} {{template "form_input" .FieldPassword}} {{template "form_input" .FieldKey}} {{template "form_button" .SubmitButton}} diff --git a/storage/templates/nopermission.html b/storage/templates/nopermission.html new file mode 100644 index 0000000..bec8f39 --- /dev/null +++ b/storage/templates/nopermission.html @@ -0,0 +1,12 @@ + + + {{template "page_head" "No permission"}} + +
+
+ You do not have permission to visit this page. +
+
+ {{template "page_footer"}} + + \ No newline at end of file diff --git a/utils/authority.go b/utils/authority.go index 33124e8..823f845 100644 --- a/utils/authority.go +++ b/utils/authority.go @@ -265,6 +265,38 @@ func (a *Authority) RevokeRolePermission(c *core.Context, roleSlug string, permS return nil } +// Revokes a user's role +func (a *Authority) RevokeUserRole(c *core.Context, userID uint, roleSlug string) error { + // find the role + var role models.Role + res := c.GetGorm().Where("slug = ?", roleSlug).First(&role) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return ErrRoleNotFound + } + return res.Error + } + + // revoke the role + rRes := c.GetGorm().Where("user_id = ?", userID).Where("role_id = ?", role.ID).Delete(models.UserRole{}) + if rRes.Error != nil { + return rRes.Error + } + + return nil +} + +// Revokes all user's role +func (a *Authority) RevokeAllUserRole(c *core.Context, userID uint) error { + // revoke the role + rRes := c.GetGorm().Where("user_id = ?", userID).Delete(models.UserRole{}) + if rRes.Error != nil { + return rRes.Error + } + + return nil +} + // Returns all stored roles func (a *Authority) GetAllRoles(c *core.Context) ([]models.Role, error) { var roles []models.Role -- 2.39.2