Skip to content

Commit

Permalink
feat(rule): Add glob matching strategy (#334)
Browse files Browse the repository at this point in the history
This patch adds the ability to choose a matching strategy and adds a glob-based matching strategy to the available options (regex is still the default).

Closes #321
  • Loading branch information
ayzu authored Feb 5, 2020
1 parent 4f22e42 commit 5f983ab
Show file tree
Hide file tree
Showing 32 changed files with 1,247 additions and 188 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ vendor
_book
node_modules/
LICENSE.txt
*-packr.go
*-packr.go
dev
10 changes: 10 additions & 0 deletions .schemas/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,16 @@
"examples": [
"[\"file://path/to/rules.json\",\"inline://W3siaWQiOiJmb28tcnVsZSIsImF1dGhlbnRpY2F0b3JzIjpbXX1d\",\"https://path-to-my-rules/rules.json\"]"
]
},
"matching_strategy": {
"title": "Matching strategy",
"description": "This an optional field describing matching strategy. Currently supported values are 'glob' and 'regexp'.",
"type": "string",
"default": "regexp",
"enum": ["glob", "regexp"],
"examples": [
"glob", "regexp"
]
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
<a href="https://discord.gg/PAMQWkr">Chat</a> |
<a href="https://community.ory.sh/">Forums</a> |
<a href="http://eepurl.com/di390P">Newsletter</a><br/><br/>
<a href="https://www.ory.sh/docs/oathkeeper/">Guide</a> |
<a href="https://www.ory.sh/docs/oathkeeper/sdk/api">API Docs</a> |
<a href="https://www.ory.sh/docs/oathkeeper/">Guide</a> |
<a href="https://godoc.org/github.com/ory/oathkeeper">Code Docs</a><br/><br/>
<a href="https://opencollective.com/ory">Support this project!</a>
</h4>
Expand Down
168 changes: 125 additions & 43 deletions api/decision_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package api_test

import (
"context"
"fmt"
"net/http"
"net/http/httptest"
Expand Down Expand Up @@ -74,42 +75,62 @@ func TestDecisionAPI(t *testing.T) {
Upstream: rule.Upstream{URL: "", StripPath: "/strip-path/", PreserveHost: true},
}

ruleNoOpAuthenticatorGLOB := rule.Rule{
Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-noop/<[0-9]*>"},
Authenticators: []rule.Handler{{Handler: "noop"}},
Authorizer: rule.Handler{Handler: "allow"},
Mutators: []rule.Handler{{Handler: "noop"}},
Upstream: rule.Upstream{URL: ""},
}
ruleNoOpAuthenticatorModifyUpstreamGLOB := rule.Rule{
Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/strip-path/authn-noop/<[0-9]*>"},
Authenticators: []rule.Handler{{Handler: "noop"}},
Authorizer: rule.Handler{Handler: "allow"},
Mutators: []rule.Handler{{Handler: "noop"}},
Upstream: rule.Upstream{URL: "", StripPath: "/strip-path/", PreserveHost: true},
}

for k, tc := range []struct {
url string
code int
messages []string
rules []rule.Rule
transform func(r *http.Request)
authz string
d string
url string
code int
messages []string
rulesRegexp []rule.Rule
rulesGlob []rule.Rule
transform func(r *http.Request)
authz string
d string
}{
{
d: "should fail because url does not exist in rule set",
url: ts.URL + "/decisions" + "/invalid",
rules: []rule.Rule{},
code: http.StatusNotFound,
d: "should fail because url does not exist in rule set",
url: ts.URL + "/decisions" + "/invalid",
rulesRegexp: []rule.Rule{},
rulesGlob: []rule.Rule{},
code: http.StatusNotFound,
},
{
d: "should fail because url does exist but is matched by two rules",
url: ts.URL + "/decisions" + "/authn-noop/1234",
rules: []rule.Rule{ruleNoOpAuthenticator, ruleNoOpAuthenticator},
code: http.StatusInternalServerError,
d: "should fail because url does exist but is matched by two rulesRegexp",
url: ts.URL + "/decisions" + "/authn-noop/1234",
rulesRegexp: []rule.Rule{ruleNoOpAuthenticator, ruleNoOpAuthenticator},
rulesGlob: []rule.Rule{ruleNoOpAuthenticatorGLOB, ruleNoOpAuthenticatorGLOB},
code: http.StatusInternalServerError,
},
{
d: "should pass",
url: ts.URL + "/decisions" + "/authn-noop/1234",
rules: []rule.Rule{ruleNoOpAuthenticator},
code: http.StatusOK,
d: "should pass",
url: ts.URL + "/decisions" + "/authn-noop/1234",
rulesRegexp: []rule.Rule{ruleNoOpAuthenticator},
rulesGlob: []rule.Rule{ruleNoOpAuthenticatorGLOB},
code: http.StatusOK,
transform: func(r *http.Request) {
r.Header.Add("Authorization", "bearer token")
},
authz: "bearer token",
},
{
d: "should pass",
url: ts.URL + "/decisions" + "/strip-path/authn-noop/1234",
rules: []rule.Rule{ruleNoOpAuthenticatorModifyUpstream},
code: http.StatusOK,
d: "should pass",
url: ts.URL + "/decisions" + "/strip-path/authn-noop/1234",
rulesRegexp: []rule.Rule{ruleNoOpAuthenticatorModifyUpstream},
rulesGlob: []rule.Rule{ruleNoOpAuthenticatorModifyUpstreamGLOB},
code: http.StatusOK,
transform: func(r *http.Request) {
r.Header.Add("Authorization", "bearer token")
},
Expand All @@ -118,11 +139,16 @@ func TestDecisionAPI(t *testing.T) {
{
d: "should fail because no authorizer was configured",
url: ts.URL + "/decisions" + "/authn-anon/authz-none/cred-none/1234",
rules: []rule.Rule{{
rulesRegexp: []rule.Rule{{
Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-none/cred-none/<[0-9]+>"},
Authenticators: []rule.Handler{{Handler: "anonymous"}},
Upstream: rule.Upstream{URL: ""},
}},
rulesGlob: []rule.Rule{{
Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-none/cred-none/<[0-9]*>"},
Authenticators: []rule.Handler{{Handler: "anonymous"}},
Upstream: rule.Upstream{URL: ""},
}},
transform: func(r *http.Request) {
r.Header.Add("Authorization", "bearer token")
},
Expand All @@ -131,102 +157,158 @@ func TestDecisionAPI(t *testing.T) {
{
d: "should fail because no mutator was configured",
url: ts.URL + "/decisions" + "/authn-anon/authz-allow/cred-none/1234",
rules: []rule.Rule{{
rulesRegexp: []rule.Rule{{
Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-allow/cred-none/<[0-9]+>"},
Authenticators: []rule.Handler{{Handler: "anonymous"}},
Authorizer: rule.Handler{Handler: "allow"},
Upstream: rule.Upstream{URL: ""},
}},
rulesGlob: []rule.Rule{{
Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-allow/cred-none/<[0-9]*>"},
Authenticators: []rule.Handler{{Handler: "anonymous"}},
Authorizer: rule.Handler{Handler: "allow"},
Upstream: rule.Upstream{URL: ""},
}},
code: http.StatusInternalServerError,
},
{
d: "should pass with anonymous and everything else set to noop",
url: ts.URL + "/decisions" + "/authn-anon/authz-allow/cred-noop/1234",
rules: []rule.Rule{{
rulesRegexp: []rule.Rule{{
Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-allow/cred-noop/<[0-9]+>"},
Authenticators: []rule.Handler{{Handler: "anonymous"}},
Authorizer: rule.Handler{Handler: "allow"},
Mutators: []rule.Handler{{Handler: "noop"}},
Upstream: rule.Upstream{URL: ""},
}},
rulesGlob: []rule.Rule{{
Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-allow/cred-noop/<[0-9]*>"},
Authenticators: []rule.Handler{{Handler: "anonymous"}},
Authorizer: rule.Handler{Handler: "allow"},
Mutators: []rule.Handler{{Handler: "noop"}},
Upstream: rule.Upstream{URL: ""},
}},
code: http.StatusOK,
authz: "",
},
{
d: "should fail when authorizer fails",
url: ts.URL + "/decisions" + "/authn-anon/authz-deny/cred-noop/1234",
rules: []rule.Rule{{
rulesRegexp: []rule.Rule{{
Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-deny/cred-noop/<[0-9]+>"},
Authenticators: []rule.Handler{{Handler: "anonymous"}},
Authorizer: rule.Handler{Handler: "deny"},
Mutators: []rule.Handler{{Handler: "noop"}},
Upstream: rule.Upstream{URL: ""},
}},
rulesGlob: []rule.Rule{{
Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-deny/cred-noop/<[0-9]*>"},
Authenticators: []rule.Handler{{Handler: "anonymous"}},
Authorizer: rule.Handler{Handler: "deny"},
Mutators: []rule.Handler{{Handler: "noop"}},
Upstream: rule.Upstream{URL: ""},
}},
code: http.StatusForbidden,
},
{
d: "should fail when authenticator fails",
url: ts.URL + "/decisions" + "/authn-broken/authz-none/cred-none/1234",
rules: []rule.Rule{{
rulesRegexp: []rule.Rule{{
Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-broken/authz-none/cred-none/<[0-9]+>"},
Authenticators: []rule.Handler{{Handler: "unauthorized"}},
Upstream: rule.Upstream{URL: ""},
}},
rulesGlob: []rule.Rule{{
Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-broken/authz-none/cred-none/<[0-9]*>"},
Authenticators: []rule.Handler{{Handler: "unauthorized"}},
Upstream: rule.Upstream{URL: ""},
}},
code: http.StatusUnauthorized,
},
{
d: "should fail when mutator fails",
url: ts.URL + "/decisions" + "/authn-anonymous/authz-allow/cred-broken/1234",
rules: []rule.Rule{{
rulesRegexp: []rule.Rule{{
Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-anonymous/authz-allow/cred-broken/<[0-9]+>"},
Authenticators: []rule.Handler{{Handler: "anonymous"}},
Authorizer: rule.Handler{Handler: "allow"},
Mutators: []rule.Handler{{Handler: "broken"}},
Upstream: rule.Upstream{URL: ""},
}},
rulesGlob: []rule.Rule{{
Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-anonymous/authz-allow/cred-broken/<[0-9]*>"},
Authenticators: []rule.Handler{{Handler: "anonymous"}},
Authorizer: rule.Handler{Handler: "allow"},
Mutators: []rule.Handler{{Handler: "broken"}},
Upstream: rule.Upstream{URL: ""},
}},
code: http.StatusInternalServerError,
},
{
d: "should fail when one of the mutators fails",
url: ts.URL + "/decisions" + "/authn-anonymous/authz-allow/cred-broken/1234",
rules: []rule.Rule{{
rulesRegexp: []rule.Rule{{
Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-anonymous/authz-allow/cred-broken/<[0-9]+>"},
Authenticators: []rule.Handler{{Handler: "anonymous"}},
Authorizer: rule.Handler{Handler: "allow"},
Mutators: []rule.Handler{{Handler: "noop"}, {Handler: "broken"}},
Upstream: rule.Upstream{URL: ""},
}},
rulesGlob: []rule.Rule{{
Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-anonymous/authz-allow/cred-broken/<[0-9]*>"},
Authenticators: []rule.Handler{{Handler: "anonymous"}},
Authorizer: rule.Handler{Handler: "allow"},
Mutators: []rule.Handler{{Handler: "noop"}, {Handler: "broken"}},
Upstream: rule.Upstream{URL: ""},
}},
code: http.StatusInternalServerError,
},
{
d: "should fail when authorizer fails and send www_authenticate as defined in the rule",
url: ts.URL + "/decisions" + "/authn-anon/authz-deny/cred-noop/1234",
rules: []rule.Rule{{
rulesRegexp: []rule.Rule{{
Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-deny/cred-noop/<[0-9]+>"},
Authenticators: []rule.Handler{{Handler: "anonymous"}},
Authorizer: rule.Handler{Handler: "deny"},
Mutators: []rule.Handler{{Handler: "noop"}},
Upstream: rule.Upstream{URL: ""},
Errors: []rule.ErrorHandler{{Handler: "www_authenticate"}},
}},
rulesGlob: []rule.Rule{{
Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-deny/cred-noop/<[0-9]*>"},
Authenticators: []rule.Handler{{Handler: "anonymous"}},
Authorizer: rule.Handler{Handler: "deny"},
Mutators: []rule.Handler{{Handler: "noop"}},
Upstream: rule.Upstream{URL: ""},
Errors: []rule.ErrorHandler{{Handler: "www_authenticate"}},
}},
code: http.StatusUnauthorized,
},
} {
t.Run(fmt.Sprintf("case=%d/description=%s", k, tc.d), func(t *testing.T) {
reg.RuleRepository().(*rule.RepositoryMemory).WithRules(tc.rules)
testFunc := func(strategy configuration.MatchingStrategy) {
require.NoError(t, reg.RuleRepository().SetMatchingStrategy(context.Background(), strategy))
req, err := http.NewRequest("GET", tc.url, nil)
require.NoError(t, err)
if tc.transform != nil {
tc.transform(req)
}

req, err := http.NewRequest("GET", tc.url, nil)
require.NoError(t, err)
if tc.transform != nil {
tc.transform(req)
}

res, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer res.Body.Close()
res, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer res.Body.Close()

assert.Equal(t, tc.authz, res.Header.Get("Authorization"))
assert.Equal(t, tc.code, res.StatusCode)
assert.Equal(t, tc.authz, res.Header.Get("Authorization"))
assert.Equal(t, tc.code, res.StatusCode)
}
t.Run("regexp", func(t *testing.T) {
reg.RuleRepository().(*rule.RepositoryMemory).WithRules(tc.rulesRegexp)
testFunc(configuration.Regexp)
})
t.Run("glob", func(t *testing.T) {
reg.RuleRepository().(*rule.RepositoryMemory).WithRules(tc.rulesRegexp)
testFunc(configuration.Glob)
})
})
}
}
Loading

0 comments on commit 5f983ab

Please sign in to comment.