5920b690d1
Turns rules into Adaxes/Active-Roles style dynamic groups. The core new
capability is a set-level SyncGroupMembership action that reconciles a
target group's membership against the matched object set in one pass
instead of the old add-only, per-object behaviour.
Engine / reconciliation:
- New ActionSyncGroupMembership runs once per target group after the
match: resolve (and optionally create) the group, read its current
members, diff against the matched set, and apply the adds/removes.
- Three per-rule sync modes (types.SyncMode): FullSync (membership ==
matched set; removes stale members incl. manual adds), ManagedAdd (adds
matches, removes only members this rule added), AddOnly (never removes).
- Managed ownership tracked in a new managed_group_members table
(migration 005) + repository, wired into the runner's engine so
ManagedAdd removes only what it added.
- Adds/removes are recorded as AddToGroup / RemoveFromGroupIfNoLongerMatched
run-actions so the activity feed categorises them as syncs/removals.
- Preview now computes an accurate, non-mutating diff for sync actions
(+add / -remove / already-in-sync counts and per-member entries).
- memberOf and memberOf-recursive (LDAP_MATCHING_RULE_IN_CHAIN) operators;
Regex no longer silently degrades to equals.
- Canonical group targets: CanonicalToLeafDN / NormalizeGroupTarget so a
target group can be given as domain.com/OU/Group as well as a DN.
Editor-facing APIs (backend-first; UI comes next):
- Rule create/update now accept conditionGroups + actions and persist them
via RuleRepository.ReplaceLogic (soft-delete + insert, preserving the
rule_run_actions FK). Omitting them leaves existing logic untouched.
- POST /api/v1/rules/preview evaluates an unsaved draft (live match panel).
- GET /api/v1/rules/metadata serves the operator vocabulary (object types,
operators, action types, sync modes, common attributes) so UI dropdowns
stay in lock-step with the backend.
- GET /api/v1/ad-connections/{id}/directory searches groups/OUs for the
target pickers.
Tests: reconciliation across all three modes + create-if-missing and the
missing-group error path (fake directory client); canonical leaf-DN
conversion; ReplaceLogic round-trip. Full suite green.
Note: RuleRepository.GetByID nests a query (getConditionGroups holds a
cursor while calling getConditions); safe under the production pool (25)
but a follow-up should flatten it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
147 lines
4.0 KiB
Go
147 lines
4.0 KiB
Go
// Package models - Rule domain models
|
|
package models
|
|
|
|
import "time"
|
|
|
|
// Rule represents an automation rule
|
|
type Rule struct {
|
|
ID string
|
|
Name string
|
|
Description *string
|
|
IsEnabled bool
|
|
ADConnectionID string
|
|
ObjectType string
|
|
BaseDNOverride *string
|
|
SearchScopeOverride *string
|
|
ScheduleID *string
|
|
ExecutionMode string
|
|
GroupJoinOperator string
|
|
MaxParallelism *int
|
|
StopOnError bool
|
|
LastRunUTC *time.Time
|
|
LastRunResult *string
|
|
CreatedUTC time.Time
|
|
UpdatedUTC time.Time
|
|
DeletedUTC *time.Time
|
|
ConditionGroups []RuleConditionGroup
|
|
Actions []RuleAction
|
|
}
|
|
|
|
// RuleConditionGroup represents a group of conditions
|
|
type RuleConditionGroup struct {
|
|
ID string
|
|
RuleID string
|
|
Name *string
|
|
IsEnabled bool
|
|
JoinOperator string
|
|
SortOrder int
|
|
Negate bool
|
|
CreatedUTC time.Time
|
|
UpdatedUTC time.Time
|
|
DeletedUTC *time.Time
|
|
Conditions []RuleCondition
|
|
}
|
|
|
|
// RuleCondition represents a single condition in a group
|
|
type RuleCondition struct {
|
|
ID string
|
|
ConditionGroupID string
|
|
IsEnabled bool
|
|
AttributeName string
|
|
Operator string
|
|
ValueType string
|
|
ComparisonValue *string
|
|
CustomLdapExpression *string
|
|
Negate bool
|
|
SortOrder int
|
|
CaseSensitive bool
|
|
CreatedUTC time.Time
|
|
UpdatedUTC time.Time
|
|
DeletedUTC *time.Time
|
|
}
|
|
|
|
// RuleAction represents an action to execute when conditions match
|
|
type RuleAction struct {
|
|
ID string
|
|
RuleID string
|
|
ActionType string
|
|
IsEnabled bool
|
|
SortOrder int
|
|
ConfigurationJSON string
|
|
RollbackMode *string
|
|
CreatedUTC time.Time
|
|
UpdatedUTC time.Time
|
|
DeletedUTC *time.Time
|
|
}
|
|
|
|
// RuleRun represents an execution history record
|
|
type RuleRun struct {
|
|
ID string
|
|
RuleID string
|
|
Status string
|
|
StartedUTC time.Time
|
|
CompletedUTC *time.Time
|
|
DurationMS *int
|
|
ObjectsMatched int
|
|
ObjectsProcessed int
|
|
ActionsExecuted int
|
|
ActionsFailed int
|
|
ErrorMessage *string
|
|
ExecutionMode string
|
|
TriggeredBy *string
|
|
CreatedUTC time.Time
|
|
}
|
|
|
|
// RuleRunAction represents a single action execution detail
|
|
type RuleRunAction struct {
|
|
ID string
|
|
RuleRunID string
|
|
RuleActionID string
|
|
ObjectDN string
|
|
ActionType string
|
|
Status string
|
|
DetailsJSON *string
|
|
ErrorMessage *string
|
|
DurationMS *int
|
|
CreatedUTC time.Time
|
|
}
|
|
|
|
// ActionConfig represents the configuration for different action types
|
|
type ActionConfig struct {
|
|
// AddToGroup / AddGroupToGroup
|
|
TargetGroupDN string `json:"targetGroupDn,omitempty"`
|
|
CreateIfMissing bool `json:"createIfMissing,omitempty"`
|
|
GroupScope string `json:"groupScope,omitempty"` // Global, DomainLocal, Universal
|
|
GroupType string `json:"groupType,omitempty"` // Security, Distribution
|
|
GroupOU string `json:"groupOu,omitempty"`
|
|
|
|
// MoveToOu
|
|
TargetOU string `json:"targetOu,omitempty"`
|
|
CreateOUIfMissing bool `json:"createOuIfMissing,omitempty"`
|
|
|
|
// RemoveFromGroupIfNoMatch
|
|
RemoveIfNoMatch bool `json:"removeIfNoMatch,omitempty"`
|
|
|
|
// SyncGroupMembership: how to reconcile the target group's membership
|
|
// against the matched set. One of FullSync, ManagedAdd, AddOnly. Empty
|
|
// defaults to FullSync.
|
|
SyncMode string `json:"syncMode,omitempty"`
|
|
|
|
// Dynamic path variables
|
|
UseDynamicPath bool `json:"useDynamicPath,omitempty"`
|
|
PathTemplate string `json:"pathTemplate,omitempty"`
|
|
}
|
|
|
|
// SummaryRuleStats represents aggregated statistics for a rule
|
|
type SummaryRuleStats struct {
|
|
RuleID string
|
|
TotalRuns int
|
|
SuccessfulRuns int
|
|
FailedRuns int
|
|
LastRunUTC *time.Time
|
|
LastRunStatus *string
|
|
AvgDurationMS *int
|
|
TotalObjectsProcessed int
|
|
UpdatedUTC time.Time
|
|
}
|