Files
StarFleetCPTN 49a586db53 feat: Update dependencies and enhance admin role management
- Upgraded `golang.org/x/crypto` to v0.36.0 and other indirect dependencies to their latest versions.
- Added functionality to assign admin roles to users upon creation in the main application logic.
- Introduced new templates for admin role management, including forms for creating and editing roles.
- Removed deprecated admin tools template to streamline the admin interface.
- Added audit logging for role changes to improve tracking and accountability.
2025-03-22 16:55:36 -07:00

300 lines
7.1 KiB
Go

package db
import (
"database/sql/driver"
"encoding/json"
"errors"
"strings"
"time"
"gorm.io/gorm"
)
// Role represents a role in the system with associated permissions
type Role struct {
gorm.Model
Name string `gorm:"size:255;not null;unique"`
Description string `gorm:"type:text"`
Permissions Permissions `gorm:"type:text"`
Users []User `gorm:"many2many:user_roles;"`
}
// AssignToUser assigns this role to a user
func (r *Role) AssignToUser(tx *gorm.DB, userID uint, assignedByID uint) error {
// Check if the role is already assigned
var count int64
if err := tx.Table("user_roles").Where("user_id = ? AND role_id = ?", userID, r.ID).Count(&count).Error; err != nil {
return err
}
if count > 0 {
return errors.New("role is already assigned to user")
}
// Create the assignment
if err := tx.Exec("INSERT INTO user_roles (user_id, role_id, created_at, updated_at) VALUES (?, ?, ?, ?)",
userID, r.ID, time.Now(), time.Now()).Error; err != nil {
return err
}
// Create audit log
return tx.Create(&AuditLog{
Action: "assign_role",
EntityType: "role",
EntityID: r.ID,
UserID: assignedByID,
Details: map[string]interface{}{
"role_name": r.Name,
"user_id": userID,
},
Timestamp: time.Now(),
}).Error
}
// UnassignFromUser removes this role from a user
func (r *Role) UnassignFromUser(tx *gorm.DB, userID uint, unassignedByID uint) error {
// Check if the role is assigned
var count int64
if err := tx.Table("user_roles").Where("user_id = ? AND role_id = ?", userID, r.ID).Count(&count).Error; err != nil {
return err
}
if count == 0 {
return errors.New("role is not assigned to user")
}
// Remove the assignment
if err := tx.Exec("DELETE FROM user_roles WHERE user_id = ? AND role_id = ?", userID, r.ID).Error; err != nil {
return err
}
// Create audit log
return tx.Create(&AuditLog{
Action: "unassign_role",
EntityType: "role",
EntityID: r.ID,
UserID: unassignedByID,
Details: map[string]interface{}{
"role_name": r.Name,
"user_id": userID,
},
Timestamp: time.Now(),
}).Error
}
// Validate performs validation on the role
func (r *Role) Validate() error {
// Name validation
if strings.TrimSpace(r.Name) == "" {
return errors.New("role name cannot be empty")
}
if len(r.Name) > 255 {
return errors.New("role name cannot exceed 255 characters")
}
if len(r.Description) > 1000 {
return errors.New("description cannot exceed 1000 characters")
}
// Permission validation
if r.Permissions == nil {
return errors.New("permissions cannot be nil")
}
// Validate each permission
validPerms := map[string]bool{
// User management
"users.view": true,
"users.create": true,
"users.edit": true,
"users.delete": true,
// Role management
"roles.view": true,
"roles.create": true,
"roles.edit": true,
"roles.delete": true,
// Config management
"configs.view": true,
"configs.create": true,
"configs.edit": true,
"configs.delete": true,
// Job management
"jobs.view": true,
"jobs.create": true,
"jobs.edit": true,
"jobs.delete": true,
"jobs.run": true,
// Audit logs
"audit.view": true,
"audit.export": true,
// System settings
"system.settings": true,
"system.backup": true,
"system.restore": true,
}
for _, perm := range r.Permissions {
if !validPerms[perm] {
return errors.New("invalid permission: " + perm)
}
}
return nil
}
// BeforeSave is a GORM hook that runs before saving the role
func (r *Role) BeforeSave(tx *gorm.DB) error {
return r.Validate()
}
// BeforeDelete is a GORM hook that runs before deleting the role
func (r *Role) BeforeDelete(tx *gorm.DB) error {
if r.IsSystemRole() {
return errors.New("cannot delete system role")
}
// Create audit log for deletion
return tx.Create(&AuditLog{
Action: "delete_role",
EntityType: "role",
EntityID: r.ID,
Details: map[string]interface{}{
"role_name": r.Name,
"permissions": r.Permissions,
},
Timestamp: time.Now(),
}).Error
}
// AfterCreate is a GORM hook that runs after creating the role
func (r *Role) AfterCreate(tx *gorm.DB) error {
// Create audit log for creation
return tx.Create(&AuditLog{
Action: "create_role",
EntityType: "role",
EntityID: r.ID,
Details: map[string]interface{}{
"role_name": r.Name,
"permissions": r.Permissions,
},
Timestamp: time.Now(),
}).Error
}
// AfterUpdate is a GORM hook that runs after updating the role
func (r *Role) AfterUpdate(tx *gorm.DB) error {
// Create audit log for update
return tx.Create(&AuditLog{
Action: "update_role",
EntityType: "role",
EntityID: r.ID,
Details: map[string]interface{}{
"role_name": r.Name,
"permissions": r.Permissions,
},
Timestamp: time.Now(),
}).Error
}
// Permissions is a custom type for storing permissions as a JSON array
type Permissions []string
// Scan implements the sql.Scanner interface
func (p *Permissions) Scan(value interface{}) error {
if value == nil {
*p = make(Permissions, 0)
return nil
}
var str string
switch v := value.(type) {
case []byte:
str = string(v)
case string:
str = v
default:
return errors.New("failed to scan Permissions: invalid type")
}
return json.Unmarshal([]byte(str), p)
}
// Value implements the driver.Valuer interface
func (p Permissions) Value() (driver.Value, error) {
if p == nil {
p = make(Permissions, 0)
}
bytes, err := json.Marshal(p)
if err != nil {
return nil, err
}
return string(bytes), nil
}
// HasPermission checks if the role has a specific permission
func (r *Role) HasPermission(permission string) bool {
for _, p := range r.Permissions {
if p == permission {
return true
}
}
return false
}
// AddPermission adds a permission to the role if it doesn't already exist
func (r *Role) AddPermission(permission string) {
if !r.HasPermission(permission) {
r.Permissions = append(r.Permissions, permission)
}
}
// RemovePermission removes a permission from the role
func (r *Role) RemovePermission(permission string) {
if r.Permissions == nil {
return
}
for i, p := range r.Permissions {
if p == permission {
r.Permissions = append(r.Permissions[:i], r.Permissions[i+1:]...)
return
}
}
}
// SetPermissions sets the role's permissions
func (r *Role) SetPermissions(permissions []string) {
r.Permissions = permissions
}
// GetPermissions returns the role's permissions
func (r *Role) GetPermissions() []string {
if r.Permissions == nil {
return make([]string, 0)
}
return r.Permissions
}
// IsSystemRole checks if this is a system-defined role that shouldn't be modified
func (r *Role) IsSystemRole() bool {
systemRoles := []string{"admin", "system", "superuser"}
for _, role := range systemRoles {
if strings.ToLower(r.Name) == role {
return true
}
}
return false
}
// AuditLog creates an audit log entry for role changes
func (r *Role) AuditLog(tx *gorm.DB, action string, userID uint) error {
return tx.Create(&AuditLog{
Action: action,
EntityType: "role",
EntityID: r.ID,
UserID: userID,
Details: map[string]interface{}{"name": r.Name, "permissions": r.Permissions},
}).Error
}