From 51b101d3d86d10c1ac15382265dc5d5e9cff1a97 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Sat, 7 Dec 2024 12:05:58 +0100 Subject: [PATCH] feat: rules behavior --- README.md | 14 +++++++++- lib/config.go | 17 +++++++++++- lib/config_test.go | 65 ++++++++++++++++++++++++++++++++++++++++++++++ lib/permissions.go | 21 ++++++++++++--- 4 files changed, 112 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7e273ad..45ddbba 100644 --- a/README.md +++ b/README.md @@ -86,9 +86,21 @@ directory: . # permissions. For example, to allow to read and create, set "RC". Default is "R". permissions: R -# The default permissions rules for users. Default is none. +# The default permissions rules for users. Default is none. Rules are applied +# from last to first, that is, the first rule that matches the request, starting +# from the end, will be applied to the request. rules: [] +# The behavior of redefining the rules for users. It can be: +# - overwrite: when a user has rules defined, these will overwrite any global +# rules already defined. That is, the global rules are not applicable to the +# user. +# - append: when a user has rules defined, these will be appended to the global +# rules already defined. That is, for this user, their own specific rules will +# be checked first, and then the global rules. +# Default is 'overwrite'. +rulesBehavior: overwrite + # Logging configuration log: # Logging format ('console', 'json'). Default is 'console'. diff --git a/lib/config.go b/lib/config.go index b605416..72f3f05 100644 --- a/lib/config.go +++ b/lib/config.go @@ -75,6 +75,7 @@ func ParseConfig(filename string, flags *pflag.FlagSet) (*Config, error) { v.SetDefault("Prefix", DefaultPrefix) // Other defaults + v.SetDefault("RulesBehavior", RulesOverwrite) v.SetDefault("Directory", ".") v.SetDefault("Permissions", "R") v.SetDefault("Debug", false) @@ -115,7 +116,21 @@ func ParseConfig(filename string, flags *pflag.FlagSet) (*Config, error) { cfg.Users[i].Permissions = cfg.Permissions } - if !v.IsSet(fmt.Sprintf("Users.%d.Rules", i)) { + if !v.IsSet(fmt.Sprintf("Users.%d.RulesBehavior", i)) { + cfg.Users[i].RulesBehavior = cfg.RulesBehavior + } + + if v.IsSet(fmt.Sprintf("Users.%d.Rules", i)) { + switch cfg.Users[i].RulesBehavior { + case RulesOverwrite: + // Do nothing + case RulesAppend: + rules := append([]*Rule{}, cfg.Rules...) + rules = append(rules, cfg.Users[i].Rules...) + + cfg.Users[i].Rules = rules + } + } else { cfg.Users[i].Rules = cfg.Rules } } diff --git a/lib/config_test.go b/lib/config_test.go index b8d48b2..541bed5 100644 --- a/lib/config_test.go +++ b/lib/config_test.go @@ -232,6 +232,71 @@ rules: require.Nil(t, cfg.Rules[1].Regex) }) + t.Run("Rules Behavior (Default: Overwrite)", func(t *testing.T) { + content := ` +directory: / +rules: + - regex: '^.+\.js$' + - path: /public/access/ + +users: + - username: foo + password: bar + rules: + - path: /private/access/` + + cfg := writeAndParseConfig(t, content, ".yaml") + require.NoError(t, cfg.Validate()) + + require.Len(t, cfg.Rules, 2) + + require.Empty(t, cfg.Rules[0].Path) + require.NotNil(t, cfg.Rules[0].Regex) + require.True(t, cfg.Rules[0].Regex.MatchString("/my/path/to/file.js")) + require.False(t, cfg.Rules[0].Regex.MatchString("/my/path/to/file.ts")) + + require.EqualValues(t, "/public/access/", cfg.Rules[1].Path) + require.Nil(t, cfg.Rules[1].Regex) + + require.Len(t, cfg.Users, 1) + require.Len(t, cfg.Users[0].Rules, 1) + require.EqualValues(t, "/private/access/", cfg.Users[0].Rules[0].Path) + }) + + t.Run("Rules Behavior (Append)", func(t *testing.T) { + content := ` +directory: / +rules: + - regex: '^.+\.js$' + - path: /public/access/ +rulesBehavior: append + +users: + - username: foo + password: bar + rules: + - path: /private/access/` + + cfg := writeAndParseConfig(t, content, ".yaml") + require.NoError(t, cfg.Validate()) + + require.Len(t, cfg.Rules, 2) + + require.Empty(t, cfg.Rules[0].Path) + require.NotNil(t, cfg.Rules[0].Regex) + require.True(t, cfg.Rules[0].Regex.MatchString("/my/path/to/file.js")) + require.False(t, cfg.Rules[0].Regex.MatchString("/my/path/to/file.ts")) + + require.EqualValues(t, "/public/access/", cfg.Rules[1].Path) + require.Nil(t, cfg.Rules[1].Regex) + + require.Len(t, cfg.Users, 1) + require.Len(t, cfg.Users[0].Rules, 3) + + require.EqualValues(t, cfg.Rules[0], cfg.Users[0].Rules[0]) + require.EqualValues(t, cfg.Rules[1], cfg.Users[0].Rules[1]) + require.EqualValues(t, "/private/access/", cfg.Users[0].Rules[2].Path) + }) } func TestConfigEnv(t *testing.T) { diff --git a/lib/permissions.go b/lib/permissions.go index 0fe9f26..aa70a7f 100644 --- a/lib/permissions.go +++ b/lib/permissions.go @@ -36,10 +36,18 @@ func (r *Rule) Matches(path string) bool { return strings.HasPrefix(path, r.Path) } +type RulesBehavior string + +const ( + RulesOverwrite RulesBehavior = "overwrite" + RulesAppend RulesBehavior = "append" +) + type UserPermissions struct { - Directory string - Permissions Permissions - Rules []*Rule + Directory string + Permissions Permissions + Rules []*Rule + RulesBehavior RulesBehavior } // Allowed checks if the user has permission to access a directory/file @@ -91,6 +99,13 @@ func (p *UserPermissions) Validate() error { } } + switch p.RulesBehavior { + case RulesAppend, RulesOverwrite: + // Good to go + default: + return fmt.Errorf("invalid rule behavior: %s", p.RulesBehavior) + } + return nil }