Skip to content

Commit

Permalink
Use ladon regex compiler for matches (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
arekkas authored Nov 7, 2017
1 parent 5618c30 commit 972a328
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 20 deletions.
8 changes: 7 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

105 changes: 94 additions & 11 deletions evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import (
"github.com/golang/mock/gomock"
"github.com/ory/hydra/sdk/go/hydra"
"github.com/ory/hydra/sdk/go/hydra/swagger"
"github.com/ory/ladon/compiler"
"github.com/ory/oathkeeper/rule"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func mustCompileRegex(t *testing.T, pattern string) *regexp.Regexp {
exp, err := regexp.Compile(pattern)
exp, err := compiler.CompileRegex(pattern, '<', '>')
require.NoError(t, err)
return exp
}
Expand All @@ -30,15 +31,29 @@ func mustGenerateURL(t *testing.T, u string) *url.URL {

func TestEvaluator(t *testing.T) {
we := NewWardenEvaluator(nil, nil, nil)
publicRule := rule.Rule{MatchesMethods: []string{"GET"}, MatchesPath: mustCompileRegex(t, "/users/[0-9]+"), AllowAnonymous: true}
bypassACPRule := rule.Rule{MatchesMethods: []string{"GET"}, MatchesPath: mustCompileRegex(t, "/users/[0-9]+"), BypassAccessControlPolicies: true}
privateRule := rule.Rule{
publicRule := rule.Rule{MatchesMethods: []string{"GET"}, MatchesPath: mustCompileRegex(t, "/users/<[0-9]+>"), AllowAnonymous: true}
bypassACPRule := rule.Rule{MatchesMethods: []string{"GET"}, MatchesPath: mustCompileRegex(t, "/users/<[0-9]+>"), BypassAccessControlPolicies: true}
privateRuleWithSubstitution := rule.Rule{
MatchesMethods: []string{"POST"},
MatchesPath: mustCompileRegex(t, "/users/([0-9]+)"),
MatchesPath: mustCompileRegex(t, "/users/<[0-9]+>"),
RequiredResource: "users:$1",
RequiredAction: "get:$1",
RequiredScopes: []string{"users.create"},
}
privateRuleWithoutSubstitution := rule.Rule{
MatchesMethods: []string{"POST"},
MatchesPath: mustCompileRegex(t, "/users<$|/([0-9]+)>"),
RequiredResource: "users",
RequiredAction: "get",
RequiredScopes: []string{"users.create"},
}
privateRuleWithPartialSubstitution := rule.Rule{
MatchesMethods: []string{"POST"},
MatchesPath: mustCompileRegex(t, "/users<$|/([0-9]+)>"),
RequiredResource: "users:$2",
RequiredAction: "get",
RequiredScopes: []string{"users.create"},
}

for k, tc := range []struct {
d string
Expand Down Expand Up @@ -215,7 +230,7 @@ func TestEvaluator(t *testing.T) {
},
{
d: "request is denied because token is missing and endpoint is not public",
rules: []rule.Rule{privateRule},
rules: []rule.Rule{privateRuleWithSubstitution},
r: &http.Request{Method: "POST", Header: http.Header{"Authorization": []string{"bEaReR"}}, URL: mustGenerateURL(t, "https://localhost/users/1234")},
e: func(t *testing.T, s *Session, err error) {
require.Error(t, err)
Expand All @@ -226,7 +241,7 @@ func TestEvaluator(t *testing.T) {
},
{
d: "request is denied because warden request fails with a network error and endpoint is not public",
rules: []rule.Rule{privateRule},
rules: []rule.Rule{privateRuleWithSubstitution},
r: &http.Request{Method: "POST", Header: http.Header{"Authorization": []string{"bEaReR token"}}, URL: mustGenerateURL(t, "https://localhost/users/1234")},
e: func(t *testing.T, s *Session, err error) {
require.Error(t, err)
Expand All @@ -239,7 +254,7 @@ func TestEvaluator(t *testing.T) {
},
{
d: "request is denied because warden request fails with a 400 status code and endpoint is not public",
rules: []rule.Rule{privateRule},
rules: []rule.Rule{privateRuleWithSubstitution},
r: &http.Request{Method: "POST", Header: http.Header{"Authorization": []string{"bEaReR token"}}, URL: mustGenerateURL(t, "https://localhost/users/1234")},
e: func(t *testing.T, s *Session, err error) {
require.Error(t, err)
Expand All @@ -252,7 +267,7 @@ func TestEvaluator(t *testing.T) {
},
{
d: "request is denied because warden request fails with allowed=false",
rules: []rule.Rule{privateRule},
rules: []rule.Rule{privateRuleWithSubstitution},
r: &http.Request{Method: "POST", Header: http.Header{"Authorization": []string{"bEaReR token"}}, URL: mustGenerateURL(t, "https://localhost/users/1234")},
e: func(t *testing.T, s *Session, err error) {
require.Error(t, err)
Expand All @@ -264,8 +279,8 @@ func TestEvaluator(t *testing.T) {
},
},
{
d: "request is allowed because token is valid and allowed",
rules: []rule.Rule{privateRule},
d: "request is allowed because token is valid and allowed (rule with substitution)",
rules: []rule.Rule{privateRuleWithSubstitution},
r: &http.Request{RemoteAddr: "127.0.0.1:1234", Method: "POST", Header: http.Header{"Authorization": []string{"bEaReR token"}}, URL: mustGenerateURL(t, "https://localhost/users/1234")},
e: func(t *testing.T, s *Session, err error) {
require.NoError(t, err)
Expand All @@ -282,6 +297,63 @@ func TestEvaluator(t *testing.T) {
return s
},
},
{
d: "request is allowed because token is valid and allowed (rule with partial substitution)",
rules: []rule.Rule{privateRuleWithPartialSubstitution},
r: &http.Request{RemoteAddr: "127.0.0.1:1234", Method: "POST", Header: http.Header{"Authorization": []string{"bEaReR token"}}, URL: mustGenerateURL(t, "https://localhost/users/1234")},
e: func(t *testing.T, s *Session, err error) {
require.NoError(t, err)
},
mock: func(c *gomock.Controller) hydra.SDK {
s := NewMockSDK(c)
s.EXPECT().DoesWardenAllowTokenAccessRequest(gomock.Eq(swagger.WardenTokenAccessRequest{
Token: "token",
Resource: "users:1234",
Action: "get",
Scopes: []string{"users.create"},
Context: map[string]interface{}{"remoteIpAddress": "127.0.0.1"},
})).Return(&swagger.WardenTokenAccessRequestResponse{Allowed: true}, &swagger.APIResponse{Response: &http.Response{StatusCode: http.StatusOK}}, nil)
return s
},
},
{
d: "request is allowed because token is valid and allowed (rule with partial substitution and path parameter)",
rules: []rule.Rule{privateRuleWithoutSubstitution},
r: &http.Request{RemoteAddr: "127.0.0.1:1234", Method: "POST", Header: http.Header{"Authorization": []string{"bEaReR token"}}, URL: mustGenerateURL(t, "https://localhost/users/1234")},
e: func(t *testing.T, s *Session, err error) {
require.NoError(t, err)
},
mock: func(c *gomock.Controller) hydra.SDK {
s := NewMockSDK(c)
s.EXPECT().DoesWardenAllowTokenAccessRequest(gomock.Eq(swagger.WardenTokenAccessRequest{
Token: "token",
Resource: "users",
Action: "get",
Scopes: []string{"users.create"},
Context: map[string]interface{}{"remoteIpAddress": "127.0.0.1"},
})).Return(&swagger.WardenTokenAccessRequestResponse{Allowed: true}, &swagger.APIResponse{Response: &http.Response{StatusCode: http.StatusOK}}, nil)
return s
},
},
{
d: "request is allowed because token is valid and allowed (rule without substitution and path parameter)",
rules: []rule.Rule{privateRuleWithoutSubstitution},
r: &http.Request{RemoteAddr: "127.0.0.1:1234", Method: "POST", Header: http.Header{"Authorization": []string{"bEaReR token"}}, URL: mustGenerateURL(t, "https://localhost/users")},
e: func(t *testing.T, s *Session, err error) {
require.NoError(t, err)
},
mock: func(c *gomock.Controller) hydra.SDK {
s := NewMockSDK(c)
s.EXPECT().DoesWardenAllowTokenAccessRequest(gomock.Eq(swagger.WardenTokenAccessRequest{
Token: "token",
Resource: "users",
Action: "get",
Scopes: []string{"users.create"},
Context: map[string]interface{}{"remoteIpAddress": "127.0.0.1"},
})).Return(&swagger.WardenTokenAccessRequestResponse{Allowed: true}, &swagger.APIResponse{Response: &http.Response{StatusCode: http.StatusOK}}, nil)
return s
},
},
} {
t.Run(fmt.Sprintf("case=%d/description=%s", k, tc.d), func(t *testing.T) {
ctrl := gomock.NewController(t)
Expand All @@ -295,3 +367,14 @@ func TestEvaluator(t *testing.T) {
})
}
}

func TestSubstitution(t *testing.T) {
reg, err := compiler.CompileRegex("/rules<$|/([^/]+)>", '<', '>')
fmt.Println(reg.String())
fmt.Printf("Found: %s\n", reg.FindAllString("/rules", -1))
fmt.Printf("Found: %s\n", reg.FindAllString("/rules/", -1))
fmt.Printf("Found: %s\n", reg.FindAllString("/rules/2423", -1))
fmt.Printf("Found: %s\n", reg.ReplaceAllString("/rules/2423", "read:$2"))
require.NoError(t, err)

}
4 changes: 2 additions & 2 deletions rule/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package rule
import (
"encoding/json"
"net/http"
"regexp"

"github.com/julienschmidt/httprouter"
"github.com/ory/herodot"
"github.com/ory/ladon/compiler"
"github.com/ory/oathkeeper/helper"
"github.com/pborman/uuid"
"github.com/pkg/errors"
Expand Down Expand Up @@ -198,7 +198,7 @@ func decodeRule(w http.ResponseWriter, r *http.Request) (*Rule, error) {
}

func toRule(rule *jsonRule) (*Rule, error) {
exp, err := regexp.Compile(rule.MatchesPath)
exp, err := compiler.CompileRegex(rule.MatchesPath, '<', '>')
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions rule/manager_sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package rule
import (
"database/sql"
"fmt"
"regexp"
"strings"

"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
"github.com/ory/ladon/compiler"
"github.com/ory/oathkeeper/helper"
"github.com/pkg/errors"
"github.com/rubenv/sql-migrate"
Expand All @@ -27,7 +27,7 @@ type sqlRule struct {
}

func (r *sqlRule) toRule() (*Rule, error) {
exp, err := regexp.Compile(r.MatchesPath)
exp, err := compiler.CompileRegex(r.MatchesPath, '<', '>')
if err != nil {
return nil, errors.WithStack(err)
}
Expand Down
3 changes: 2 additions & 1 deletion rule/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/jmoiron/sqlx"
"github.com/ory/dockertest"
"github.com/ory/ladon/compiler"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand All @@ -26,7 +27,7 @@ func kjillAll() {
}

func mustCompileRegex(t *testing.T, pattern string) *regexp.Regexp {
exp, err := regexp.Compile(pattern)
exp, err := compiler.CompileRegex(pattern, '<', '>')
require.NoError(t, err)
return exp
}
Expand Down
7 changes: 4 additions & 3 deletions rule/matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,23 @@ package rule
import (
"fmt"
"net/url"
"regexp"
"strconv"
"testing"

"github.com/ory/ladon/compiler"
)

var methods = []string{"POST", "PUT", "GET", "DELETE", "PATCH", "OPTIONS", "HEAD"}

func generateDummyRules(amount int) []Rule {
rules := make([]Rule, amount)
scopes := []string{"foo", "bar", "baz", "faz"}
expressions := []string{"/users/", "/users", "/blogs/", "/use(r)s/"}
expressions := []string{"/users/", "/users", "/blogs/", "/use<(r)>s/"}
resources := []string{"users", "users:$1"}
actions := []string{"get", "get:$1"}

for i := 0; i < amount; i++ {
exp, _ := regexp.Compile(expressions[(i%(len(expressions)))] + "([0-" + strconv.Itoa(i) + "]+)")
exp, _ := compiler.CompileRegex(expressions[(i%(len(expressions)))]+"([0-"+strconv.Itoa(i)+"]+)", '<', '>')
rules[i] = Rule{
ID: strconv.Itoa(i),
MatchesMethods: methods[:i%(len(methods))],
Expand Down

0 comments on commit 972a328

Please sign in to comment.