From 9cf1e650fe2743c09d6196036c07ad4a091c1616 Mon Sep 17 00:00:00 2001 From: zoop Date: Fri, 29 Mar 2024 14:11:14 +0100 Subject: [PATCH] feat: add ResourceAccessScopeStrategy --- scope_strategy.go | 66 ++++++++++++++++++++++++++++++++++++++++++ scope_strategy_test.go | 58 +++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) diff --git a/scope_strategy.go b/scope_strategy.go index 0c4bbad6..646e7c29 100644 --- a/scope_strategy.go +++ b/scope_strategy.go @@ -83,3 +83,69 @@ func WildcardScopeStrategy(matchers []string, needle string) bool { return false } + +func ResourceAccessScopeStrategy(matchers []string, needle string) bool { + needleResources := strings.Split(needle, ":") + +matcherloop: + for _, matcher := range matchers { + matcherResources := strings.Split(matcher, ":") + + if len(matcherResources) != len(needleResources) { + continue matcherloop + } + + var match bool + + for index, matcherResource := range matcherResources { + isLastLoop := index+1 == len(matcherResources) + needleResource := needleResources[index] + + // on the last resource we split off the verb + matcherVerb := "" + if strings.Contains(matcherResource, ".") && isLastLoop { + matcherVerb = matcherResource[strings.LastIndex(matcherResource, "."):] + matcherResource, _ = strings.CutSuffix(matcherResource, matcherVerb) + } + + needleVerb := "" + if strings.Contains(needleResource, ".") && isLastLoop && matcherVerb != "" { + needleVerb = needleResource[strings.LastIndex(needleResource, "."):] + needleResource, _ = strings.CutSuffix(needleResource, needleVerb) + } + + var exactmatch bool + var prefixmatch bool + + if matcherResource == needleResource { + exactmatch = true + } + // if prefix we check only on the left side of `-` + if strings.HasSuffix(matcherResource, "-*") { + matcherPrefix, _, _ := strings.Cut(matcherResource, "-") + needlePrefix, _, hasPrefix := strings.Cut(needleResource, "-") + if hasPrefix && needlePrefix == matcherPrefix { + prefixmatch = true + } + } + match = prefixmatch || exactmatch + + if !match { + continue matcherloop + } + + // if matcher defines .* as verb then everything is permitted + // resource provider has responsibility to assume least privilege + if isLastLoop && matcherVerb != "" { + if matcherVerb == ".*" { + continue + } + match = matcherVerb == needleVerb + } + } + if match { + return true + } + } + return false +} diff --git a/scope_strategy_test.go b/scope_strategy_test.go index cfd9cb2b..a0badfb5 100644 --- a/scope_strategy_test.go +++ b/scope_strategy_test.go @@ -132,3 +132,61 @@ func TestExactScopeStrategy2ScopeStrategy(t *testing.T) { assert.False(t, strategy([]string{}, "foo")) } + +func TestResourceAccessScopeStrategy(t *testing.T) { + var strategy ScopeStrategy = ResourceAccessScopeStrategy + var scopes = []string{} + + assert.False(t, strategy(scopes, "foo:bar:baz")) + assert.False(t, strategy(scopes, "foo:bar")) + + scopes = []string{"*"} + assert.False(t, strategy(scopes, "")) + assert.True(t, strategy(scopes, "*")) + assert.False(t, strategy(scopes, "asdf.asdf")) + + scopes = []string{"foo"} + assert.False(t, strategy(scopes, "*")) + assert.False(t, strategy(scopes, "foo:*")) + assert.False(t, strategy(scopes, "fo*")) + assert.True(t, strategy(scopes, "foo")) + + scopes = []string{"foo-*"} + assert.False(t, strategy(scopes, "foo")) + assert.True(t, strategy(scopes, "foo-a")) + assert.False(t, strategy(scopes, "fo-bar")) + assert.True(t, strategy(scopes, "foo-*")) + + scopes = []string{"foo:bar"} + assert.True(t, strategy(scopes, "foo:bar")) + assert.False(t, strategy(scopes, "foo:baz")) + assert.False(t, strategy(scopes, "foo:bar:baz")) + assert.False(t, strategy(scopes, "foo")) + + scopes = []string{"foo-*:bar.read"} + assert.True(t, strategy(scopes, "foo-bar:bar.read")) + assert.False(t, strategy(scopes, "foo-bar:bar")) + assert.False(t, strategy(scopes, "foo:bar")) + assert.True(t, strategy(scopes, "foo-*:bar.read")) + assert.False(t, strategy(scopes, "bar-foo:foo")) + assert.False(t, strategy(scopes, "foo-bar:bar:baz")) + + scopes = []string{"foo-*:bar.*"} + assert.True(t, strategy(scopes, "foo-bar:bar.read")) + assert.True(t, strategy(scopes, "foo-bar:bar")) + assert.False(t, strategy(scopes, "foo:bar")) + assert.True(t, strategy(scopes, "foo-*:bar.write")) + assert.False(t, strategy(scopes, "bar-foo:foo")) + assert.False(t, strategy(scopes, "foo-bar:bar:baz")) + + scopes = []string{"foo-*.read:bar"} + assert.True(t, strategy(scopes, "foo-*.read:bar")) + assert.False(t, strategy(scopes, "foo-bar:bar")) + + scopes = strings.Fields("hydra:clients.* openid offline hydra") + assert.True(t, strategy(scopes, "hydra:clients")) + assert.True(t, strategy(scopes, "hydra:clients.get")) + assert.True(t, strategy(scopes, "hydra")) + assert.True(t, strategy(scopes, "offline")) + assert.True(t, strategy(scopes, "openid")) +}