feat: enforce tenant content moderation
This commit is contained in:
@@ -61,9 +61,32 @@ var (
|
||||
registrationSMSCache = newConfigCache[bool]()
|
||||
thirdPartyConfigCache = newConfigCache[map[string]string]()
|
||||
appReleaseConfigCache = newConfigCache[*types.AppRelease]()
|
||||
moderationProviderCache = newConfigCache[*types.ModerationProviderConfig]()
|
||||
moderationPolicyCache = newConfigCache[*types.ModerationPolicy]()
|
||||
moderationWordsCache = newConfigCache[*types.SensitiveWordSet]()
|
||||
thirdPartyConfigAllKeys = "*"
|
||||
)
|
||||
|
||||
func InvalidateModerationConfigCache(tenantCode string, provider, policy, words bool) {
|
||||
if provider {
|
||||
moderationProviderCache.clear()
|
||||
}
|
||||
if policy {
|
||||
if strings.TrimSpace(tenantCode) == "" {
|
||||
moderationPolicyCache.clear()
|
||||
} else {
|
||||
moderationPolicyCache.delete(strings.TrimSpace(tenantCode))
|
||||
}
|
||||
}
|
||||
if words {
|
||||
if strings.TrimSpace(tenantCode) == "" {
|
||||
moderationWordsCache.clear()
|
||||
} else {
|
||||
moderationWordsCache.delete(strings.TrimSpace(tenantCode))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cloneTenant(t *types.Tenant) *types.Tenant {
|
||||
if t == nil {
|
||||
return nil
|
||||
|
||||
@@ -12,6 +12,15 @@ type SecurityAuditPersistenceInterface interface {
|
||||
RecordLoginFailure(types.TenantID, *types.LoginFailureEvent) error
|
||||
CloseLogin(types.TenantID, string, time.Time) error
|
||||
ApplyMessageAction(types.TenantID, *types.MessageAdminAction) error
|
||||
RecordContentModeration(types.TenantID, *types.ContentModerationLog) error
|
||||
}
|
||||
|
||||
func (securityAuditMapper) RecordContentModeration(tenantID types.TenantID, event *types.ContentModerationLog) error {
|
||||
audit, err := tenantSecurityAuditAdapter(tenantID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return audit.TenantContentModerationLogCreate(tenantID, event)
|
||||
}
|
||||
|
||||
type securityAuditMapper struct{}
|
||||
|
||||
@@ -170,6 +170,9 @@ type AdminInternalConfigProvider interface {
|
||||
RegistrationSMSEnabled() (bool, error)
|
||||
ThirdPartyConfig(group string, keys ...string) (map[string]string, error)
|
||||
LatestAppRelease(platform, channel string) (*types.AppRelease, error)
|
||||
ModerationProvider() (*types.ModerationProviderConfig, error)
|
||||
ModerationPolicy(tenantCode string) (*types.ModerationPolicy, error)
|
||||
SensitiveWords(tenantCode string) (*types.SensitiveWordSet, error)
|
||||
}
|
||||
|
||||
var adminInternalConfigProvider AdminInternalConfigProvider
|
||||
@@ -178,6 +181,60 @@ func SetAdminInternalConfigProvider(provider AdminInternalConfigProvider) {
|
||||
adminInternalConfigProvider = provider
|
||||
}
|
||||
|
||||
type ModerationConfigPersistenceInterface interface {
|
||||
Provider() (*types.ModerationProviderConfig, error)
|
||||
Policy(tenantCode string) (*types.ModerationPolicy, error)
|
||||
Words(tenantCode string) (*types.SensitiveWordSet, error)
|
||||
}
|
||||
|
||||
type moderationConfigMapper struct{}
|
||||
|
||||
var ModerationConfig ModerationConfigPersistenceInterface = moderationConfigMapper{}
|
||||
|
||||
func (moderationConfigMapper) Provider() (*types.ModerationProviderConfig, error) {
|
||||
if value, ok := moderationProviderCache.get("provider"); ok {
|
||||
return value, nil
|
||||
}
|
||||
if adminInternalConfigProvider == nil {
|
||||
return nil, types.ErrUnsupported
|
||||
}
|
||||
value, err := adminInternalConfigProvider.ModerationProvider()
|
||||
if err == nil && value != nil {
|
||||
moderationProviderCache.set("provider", value)
|
||||
}
|
||||
return value, err
|
||||
}
|
||||
|
||||
func (moderationConfigMapper) Policy(tenantCode string) (*types.ModerationPolicy, error) {
|
||||
tenantCode = strings.TrimSpace(tenantCode)
|
||||
if value, ok := moderationPolicyCache.get(tenantCode); ok {
|
||||
return value, nil
|
||||
}
|
||||
if adminInternalConfigProvider == nil {
|
||||
return nil, types.ErrUnsupported
|
||||
}
|
||||
value, err := adminInternalConfigProvider.ModerationPolicy(tenantCode)
|
||||
if err == nil && value != nil {
|
||||
moderationPolicyCache.set(tenantCode, value)
|
||||
}
|
||||
return value, err
|
||||
}
|
||||
|
||||
func (moderationConfigMapper) Words(tenantCode string) (*types.SensitiveWordSet, error) {
|
||||
tenantCode = strings.TrimSpace(tenantCode)
|
||||
if value, ok := moderationWordsCache.get(tenantCode); ok {
|
||||
return value, nil
|
||||
}
|
||||
if adminInternalConfigProvider == nil {
|
||||
return nil, types.ErrUnsupported
|
||||
}
|
||||
value, err := adminInternalConfigProvider.SensitiveWords(tenantCode)
|
||||
if err == nil && value != nil {
|
||||
moderationWordsCache.set(tenantCode, value)
|
||||
}
|
||||
return value, err
|
||||
}
|
||||
|
||||
// RegistrationConfigPersistenceInterface exposes global registration policy.
|
||||
type RegistrationConfigPersistenceInterface interface {
|
||||
SMSEnabled() (bool, error)
|
||||
|
||||
@@ -57,3 +57,20 @@ type MessageAdminAction struct {
|
||||
RequestID string
|
||||
ProcessedAt time.Time
|
||||
}
|
||||
|
||||
type ContentModerationLog struct {
|
||||
RequestID string
|
||||
Scene string
|
||||
Topic string
|
||||
SeqID *int64
|
||||
ActorID Uid
|
||||
Provider string
|
||||
ProviderRequestID string
|
||||
RiskLevel string
|
||||
RiskLabels []string
|
||||
Action string
|
||||
ContentHash string
|
||||
LatencyMS int64
|
||||
ErrorCode string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package types
|
||||
|
||||
type ModerationProviderConfig struct {
|
||||
Provider string `json:"provider"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Enabled bool `json:"enabled"`
|
||||
RegionID string `json:"region_id"`
|
||||
NicknameService string `json:"nickname_service"`
|
||||
ChatService string `json:"chat_service"`
|
||||
TimeoutMS int `json:"timeout_ms"`
|
||||
FailureMode string `json:"failure_mode"`
|
||||
AccessKeyID string `json:"access_key_id"`
|
||||
AccessKeySecret string `json:"access_key_secret"`
|
||||
}
|
||||
|
||||
type ModerationPolicy struct {
|
||||
TenantID TenantID `json:"tenant_id"`
|
||||
TenantCode string `json:"tenant_code"`
|
||||
PolicyVersion uint `json:"policy_version"`
|
||||
Enabled bool `json:"enabled"`
|
||||
AliyunEnabled bool `json:"aliyun_enabled"`
|
||||
LocalWordsEnabled bool `json:"local_words_enabled"`
|
||||
Scenes map[string]bool `json:"scenes"`
|
||||
RiskActions map[string]string `json:"risk_actions"`
|
||||
FailureMode string `json:"failure_mode"`
|
||||
BlockMessage string `json:"block_message"`
|
||||
}
|
||||
|
||||
type SensitiveWordRule struct {
|
||||
ID int64 `json:"id"`
|
||||
Word string `json:"word"`
|
||||
MatchType string `json:"match_type"`
|
||||
Scenes []string `json:"scenes"`
|
||||
Action string `json:"action"`
|
||||
Replacement string `json:"replacement"`
|
||||
RiskLevel string `json:"risk_level"`
|
||||
Priority int `json:"priority"`
|
||||
}
|
||||
|
||||
type SensitiveWordSet struct {
|
||||
TenantID TenantID `json:"tenant_id"`
|
||||
TenantCode string `json:"tenant_code"`
|
||||
Version int64 `json:"version"`
|
||||
Items []*SensitiveWordRule `json:"items"`
|
||||
}
|
||||
Reference in New Issue
Block a user