package service import ( "fmt" "net" "github.com/tinyauthapp/tinyauth/internal/model" "github.com/tinyauthapp/tinyauth/internal/utils/logger" ) type Policy string const ( PolicyAllow Policy = "allow" PolicyDeny Policy = "deny" ) type Effect int const ( EffectAbstain Effect = iota EffectAllow EffectDeny ) type Rule interface { Evaluate(ctx *ACLContext) Effect } type ACLContext struct { ACLs *model.App UserContext *model.UserContext IP net.IP Path string } type PolicyEngine struct { log *logger.Logger rules map[RuleName]Rule policy Policy } func NewPolicyEngine(config model.Config, log *logger.Logger) (*PolicyEngine, error) { engine := PolicyEngine{ log: log, rules: make(map[RuleName]Rule), } switch config.Auth.ACLs.Policy { case string(PolicyAllow): log.App.Debug().Msg("Using 'allow' ACL policy: access to apps will be allowed by default unless explicitly blocked") engine.policy = PolicyAllow case string(PolicyDeny): log.App.Debug().Msg("Using 'deny' ACL policy: access to apps will be blocked by default unless explicitly allowed") engine.policy = PolicyDeny default: return nil, fmt.Errorf("invalid acl policy: %s", config.Auth.ACLs.Policy) } return &engine, nil } func (engine *PolicyEngine) RegisterRule(name RuleName, rule Rule) { engine.rules[name] = rule } func (engine *PolicyEngine) evaluateRuleByName(name RuleName, ctx *ACLContext) Effect { rule, exists := engine.rules[name] if !exists { engine.log.App.Warn().Str("rule", string(name)).Msg("Rule not found in policy engine, defaulting to abstain") return EffectAbstain } return rule.Evaluate(ctx) } func (engine *PolicyEngine) effectToAccess(effect Effect) bool { switch effect { case EffectAllow: return true case EffectDeny: return false default: // If the effect is abstain, we fall back to the default policy return engine.policy == PolicyAllow } } func (engine *PolicyEngine) Evaluate(name RuleName, ctx *ACLContext) bool { effect := engine.evaluateRuleByName(name, ctx) access := engine.effectToAccess(effect) engine.log.App.Debug(). Str("rule", string(name)). Int("effect", int(effect)). Bool("access", access). Msg("Evaluated ACL rule") return access }