118 lines
4.5 KiB
Go
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
|
|
} |