Files
gochat/internal/model/custom_role.go
T
2026-06-04 15:44:48 +08:00

118 lines
4.5 KiB
Go

package model
// CustomRole represents an enterprise custom role with fine-grained permissions.
// Reference: Chatwoot enterprise/app/models/custom_role.rb
// P2E §2.1 — 6 permission dimensions: conversation_manage, conversation_delete,
// contact_manage, report_manage, knowledge_base_manage, automation_manage
//
// CustomRoles allow enterprise accounts to define roles between agent and administrator
// with specific permission levels (full/read/none) on each dimension.
//
// NOTE: PermissionMatrix type is intentionally NOT imported from internal/auth to avoid
// a circular dependency (auth -> model -> auth). Instead, the model stores permissions
// as raw JSONB string, and the auth/service layers handle deserialization into PermissionMatrix.
// See RBACService.GetPermissionMatrix(customRole) for the conversion.
import (
"encoding/json"
"fmt"
"time"
"gorm.io/gorm"
)
// PermissionDimension constants — duplicated here to avoid circular import with auth package.
// These must be kept in sync with auth.PermissionDimension.
type PermissionDimension string
const (
DimensionConversationManage PermissionDimension = "conversation_manage"
DimensionConversationDelete PermissionDimension = "conversation_delete"
DimensionContactManage PermissionDimension = "contact_manage"
DimensionReportManage PermissionDimension = "report_manage"
DimensionKnowledgeBaseManage PermissionDimension = "knowledge_base_manage"
DimensionAutomationManage PermissionDimension = "automation_manage"
)
// PermissionLevel constants — duplicated here to avoid circular import with auth package.
type PermissionLevel string
const (
PermissionLevelFull PermissionLevel = "full"
PermissionLevelRead PermissionLevel = "read"
PermissionLevelNone PermissionLevel = "none"
)
// CustomRole represents a custom enterprise role definition.
type CustomRole struct {
ID uint `gorm:"primaryKey" json:"id"`
AccountID uint `gorm:"not null;index" json:"account_id"`
Name string `gorm:"size:255;not null" json:"name"`
Permissions string `gorm:"type:jsonb;not null;default:'{}'" json:"permissions"` // JSONB: {"conversation_manage":"read", ...}
Description string `gorm:"size:500" json:"description,omitempty"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
// Relations
Account Account `gorm:"foreignKey:AccountID" json:"account,omitempty"`
AccountUsers []AccountUser `gorm:"foreignKey:CustomRoleID" json:"account_users,omitempty"`
}
func (CustomRole) TableName() string { return "custom_roles" }
// GetPermissionMap deserializes the JSONB permissions field into a map of
// PermissionDimension → PermissionLevel. This is the raw deserialization;
// the auth.PolicyContext layer converts this into auth.PermissionMatrix for
// policy evaluation.
func (cr *CustomRole) GetPermissionMap() (map[PermissionDimension]PermissionLevel, error) {
var m map[PermissionDimension]PermissionLevel
if cr.Permissions == "" || cr.Permissions == "{}" {
return map[PermissionDimension]PermissionLevel{}, nil
}
if err := json.Unmarshal([]byte(cr.Permissions), &m); err != nil {
return nil, fmt.Errorf("failed to parse custom role permissions JSON: %w", err)
}
return m, nil
}
// SetPermissionMap serializes a permission map into the JSONB permissions field.
func (cr *CustomRole) SetPermissionMap(m map[PermissionDimension]PermissionLevel) error {
data, err := json.Marshal(m)
if err != nil {
return fmt.Errorf("failed to serialize custom role permissions: %w", err)
}
cr.Permissions = string(data)
return nil
}
// Validate checks that the custom role has a valid name and permission dimensions.
func (cr *CustomRole) Validate() error {
if cr.Name == "" {
return fmt.Errorf("custom role name is required")
}
pm, err := cr.GetPermissionMap()
if err != nil {
return err
}
for dim, level := range pm {
if level != PermissionLevelFull && level != PermissionLevelRead && level != PermissionLevelNone {
return fmt.Errorf("invalid permission level '%s' for dimension '%s'", level, dim)
}
}
return nil
}
// CustomRoleValidationError represents a validation error for a custom role.
type CustomRoleValidationError struct {
Dimension string
Level string
Message string
}
func (e *CustomRoleValidationError) Error() string {
return "custom_role validation error: dimension '" + e.Dimension + "' has invalid level '" + e.Level + "' — " + e.Message
}