From 46a717e0428fba1fcabb0bdb669acaba39aa5444 Mon Sep 17 00:00:00 2001 From: arekkas Date: Mon, 20 Nov 2017 18:16:27 +0100 Subject: [PATCH] Renames bypass values for better clarity Closes #13 Closes #29 --- director/director_test.go | 5 +- docs/api.swagger.json | 18 +-- evaluator/evaluator_test.go | 7 +- evaluator/evaluator_warden.go | 156 +++++++++++++--------- rule/doc.go | 25 ++-- rule/handler.go | 44 +++--- rule/handler_test.go | 13 +- rule/manager_sql.go | 66 ++++----- rule/manager_test.go | 16 +-- rule/rule.go | 22 +-- sdk/go/oathkeepersdk/swagger/docs/Rule.md | 4 +- sdk/go/oathkeepersdk/swagger/rule.go | 10 +- stub/rules.json | 4 +- 13 files changed, 203 insertions(+), 187 deletions(-) diff --git a/director/director_test.go b/director/director_test.go index 7af4c31456..1763eb8f9b 100644 --- a/director/director_test.go +++ b/director/director_test.go @@ -41,14 +41,15 @@ func TestProxy(t *testing.T) { proxy := httptest.NewServer(&httputil.ReverseProxy{Director: d.Director, Transport: d}) defer proxy.Close() - publicRule := rule.Rule{MatchesMethods: []string{"GET"}, MatchesURLCompiled: mustCompileRegex(t, proxy.URL+"/users/[0-9]+"), AllowAnonymousModeEnabled: true} - disabledRule := rule.Rule{MatchesMethods: []string{"GET"}, MatchesURLCompiled: mustCompileRegex(t, proxy.URL+"/users/[0-9]+"), PassThroughModeEnabled: true} + publicRule := rule.Rule{MatchesMethods: []string{"GET"}, MatchesURLCompiled: mustCompileRegex(t, proxy.URL+"/users/[0-9]+"), Mode: rule.AnonymousMode} + disabledRule := rule.Rule{MatchesMethods: []string{"GET"}, MatchesURLCompiled: mustCompileRegex(t, proxy.URL+"/users/[0-9]+"), Mode: rule.BypassMode} privateRule := rule.Rule{ MatchesMethods: []string{"GET"}, MatchesURLCompiled: mustCompileRegex(t, proxy.URL+"/users/([0-9]+)"), RequiredResource: "users:$1", RequiredAction: "get:$1", RequiredScopes: []string{"users.create"}, + Mode: rule.PolicyMode, } for k, tc := range []struct { diff --git a/docs/api.swagger.json b/docs/api.swagger.json index 14870b15d4..b8e7b4545a 100644 --- a/docs/api.swagger.json +++ b/docs/api.swagger.json @@ -243,16 +243,6 @@ "description": "A rule", "type": "object", "properties": { - "allowAnonymousModeEnabled": { - "description": "If set to true, the protected endpoint is available to anonymous users. That means that the endpoint is accessible\nwithout having a valid access token. This setting overrides `basicAuthorizationModeEnabled`.", - "type": "boolean", - "x-go-name": "AllowAnonymousModeEnabled" - }, - "basicAuthorizationModeEnabled": { - "description": "If set to true, disables checks against ORY Hydra's Warden API and uses basic authorization. This means that\nthe access token is validated (e.g. checking if it is expired, check if it claimed the necessary scopes)\nbut does not use the `requiredAction` and `requiredResource` fields for advanced access control.", - "type": "boolean", - "x-go-name": "BasicAuthorizationModeEnabled" - }, "description": { "description": "A human readable description of this rule.", "type": "string", @@ -276,10 +266,10 @@ "type": "string", "x-go-name": "MatchesURL" }, - "passThroughModeEnabled": { - "description": "If set to true, any authorization logic is completely disabled and the Authorization header is not changed at all.\nThis is useful if you have an endpoint that has it's own authorization logic, for example using basic authorization.\nIf set to true, this setting overrides `basicAuthorizationModeEnabled` and `allowAnonymousModeEnabled`.", - "type": "boolean", - "x-go-name": "PassThroughModeEnabled" + "mode": { + "description": "Defines which mode this rule should use. There are four valid modes:\n\nbypass: If set, any authorization logic is completely disabled and the Authorization header is not changed at all.\nThis is useful if you have an endpoint that has it's own authorization logic, for example using basic authorization.\nIf set to true, this setting overrides `basicAuthorizationModeEnabled` and `allowAnonymousModeEnabled`.\nanonymous: If set, the protected endpoint is available to anonymous users. That means that the endpoint is accessible\nwithout having a valid access token. This setting overrides `basicAuthorizationModeEnabled`.\ntoken: If set, disables checks against ORY Hydra's Warden API and uses basic authorization. This means that\nthe access token is validated (e.g. checking if it is expired, check if it claimed the necessary scopes)\nbut does not use the `requiredAction` and `requiredResource` fields for advanced access control.\npolicy: If set, uses ORY Hydra's Warden API for access control using access control policies.", + "type": "string", + "x-go-name": "Mode" }, "requiredAction": { "description": "This field will be used to decide advanced authorization requests where access control policies are used. A\naction is typically something a user wants to do (e.g. write, read, delete).\nThis field supports expansion as described in the developer guide: https://ory.gitbooks.io/oathkeeper/content/concepts.html#rules", diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index 90460dd349..92ab0d4682 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -31,14 +31,15 @@ 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"}, MatchesURLCompiled: mustCompileRegex(t, "http://localhost/users/<[0-9]+>"), AllowAnonymousModeEnabled: true} - bypassACPRule := rule.Rule{MatchesMethods: []string{"GET"}, MatchesURLCompiled: mustCompileRegex(t, "http://localhost/users/<[0-9]+>"), BasicAuthorizationModeEnabled: true} + publicRule := rule.Rule{MatchesMethods: []string{"GET"}, MatchesURLCompiled: mustCompileRegex(t, "http://localhost/users/<[0-9]+>"), Mode: rule.AnonymousMode} + bypassACPRule := rule.Rule{MatchesMethods: []string{"GET"}, MatchesURLCompiled: mustCompileRegex(t, "http://localhost/users/<[0-9]+>"), Mode: rule.AuthenticatedMode} privateRuleWithSubstitution := rule.Rule{ MatchesMethods: []string{"POST"}, MatchesURLCompiled: mustCompileRegex(t, "http://localhost/users/<[0-9]+>"), RequiredResource: "users:$1", RequiredAction: "get:$1", RequiredScopes: []string{"users.create"}, + Mode: rule.PolicyMode, } privateRuleWithoutSubstitution := rule.Rule{ MatchesMethods: []string{"POST"}, @@ -46,6 +47,7 @@ func TestEvaluator(t *testing.T) { RequiredResource: "users", RequiredAction: "get", RequiredScopes: []string{"users.create"}, + Mode: rule.PolicyMode, } privateRuleWithPartialSubstitution := rule.Rule{ MatchesMethods: []string{"POST"}, @@ -53,6 +55,7 @@ func TestEvaluator(t *testing.T) { RequiredResource: "users:$2", RequiredAction: "get", RequiredScopes: []string{"users.create"}, + Mode: rule.PolicyMode, } for k, tc := range []struct { diff --git a/evaluator/evaluator_warden.go b/evaluator/evaluator_warden.go index 1b6a10f3c7..e9b9a62c38 100644 --- a/evaluator/evaluator_warden.go +++ b/evaluator/evaluator_warden.go @@ -49,6 +49,7 @@ var reasons = map[string]string{ "policy_decision_point_http_error": "Rule requires policy-based access control decision, which failed due to a http error", "policy_decision_point_access_forbidden": "Rule requires policy-based access control decision, which was denied", "policy_decision_point_access_granted": "Rule requires policy-based access control decision, which was granted", + "unknown_mode": "Rule defines a unknown mod ", } func (d *WardenEvaluator) EvaluateAccessRequest(r *http.Request) (*Session, error) { @@ -78,20 +79,20 @@ func (d *WardenEvaluator) EvaluateAccessRequest(r *http.Request) (*Session, erro return nil, err } - if rl.PassThroughModeEnabled { + switch rl.Mode { + case rule.BypassMode: d.Logger. WithField("granted", true). WithField("user", ""). WithField("access_url", u.String()). WithField("token", tokenID). WithField("rule", rl.ID). + WithField("mode", rl.Mode). WithField("reason", reasons["passthrough"]). WithField("reason_id", "passthrough"). Infoln("Access request granted") return &Session{Issuer: "", User: "", Anonymous: true, ClientID: "", Disabled: true}, nil - } - - if rl.AllowAnonymousModeEnabled { + case rule.AnonymousMode: if token == "" { d.Logger. WithField("granted", true). @@ -112,6 +113,7 @@ func (d *WardenEvaluator) EvaluateAccessRequest(r *http.Request) (*Session, erro WithField("user", ""). WithField("access_url", u.String()). WithField("token", tokenID). + WithField("mode", rl.Mode). WithField("reason", reasons["anonymous_without_credentials_failed_introspection"]). WithField("reason_id", "anonymous_without_credentials_failed_introspection"). Infoln("Access request granted") @@ -123,6 +125,7 @@ func (d *WardenEvaluator) EvaluateAccessRequest(r *http.Request) (*Session, erro WithField("status_code", response.StatusCode). WithField("token", tokenID). WithField("access_url", u.String()). + WithField("mode", rl.Mode). WithField("reason", reasons["anonymous_introspection_http_error"]). WithField("reason_id", "anonymous_introspection_http_error"). Infoln("Access request granted") @@ -134,6 +137,7 @@ func (d *WardenEvaluator) EvaluateAccessRequest(r *http.Request) (*Session, erro WithField("access_url", u.String()). WithField("token", tokenID). WithField("rule", rl.ID). + WithField("mode", rl.Mode). WithField("reason", reasons["anonymous_introspection_invalid_credentials"]). WithField("reason_id", "anonymous_introspection_invalid_credentials"). Infoln("Access request granted") @@ -146,6 +150,7 @@ func (d *WardenEvaluator) EvaluateAccessRequest(r *http.Request) (*Session, erro WithField("access_url", u.String()). WithField("token", tokenID). WithField("rule", rl.ID). + WithField("mode", rl.Mode). WithField("reason", reasons["anonymous_with_valid_credentials"]). WithField("reason_id", "anonymous_with_valid_credentials"). Infoln("Access request granted") @@ -155,21 +160,20 @@ func (d *WardenEvaluator) EvaluateAccessRequest(r *http.Request) (*Session, erro ClientID: introspection.ClientId, Anonymous: false, }, nil - } - - if token == "" { - d.Logger.WithError(err). - WithField("granted", false). - WithField("user", ""). - WithField("access_url", u.String()). - WithField("token", tokenID). - WithField("reason", reasons["missing_credentials"]). - WithField("reason_id", "missing_credentials"). - Warn("Access request denied") - return nil, errors.WithStack(helper.ErrMissingBearerToken) - } + case rule.AuthenticatedMode: + if token == "" { + d.Logger.WithError(err). + WithField("granted", false). + WithField("user", ""). + WithField("access_url", u.String()). + WithField("token", tokenID). + WithField("mode", rl.Mode). + WithField("reason", reasons["missing_credentials"]). + WithField("reason_id", "missing_credentials"). + Warn("Access request denied") + return nil, errors.WithStack(helper.ErrMissingBearerToken) + } - if rl.BasicAuthorizationModeEnabled { introspection, response, err := d.Hydra.IntrospectOAuth2Token(token, strings.Join(rl.RequiredScopes, " ")) if err != nil { d.Logger.WithError(err). @@ -177,6 +181,7 @@ func (d *WardenEvaluator) EvaluateAccessRequest(r *http.Request) (*Session, erro WithField("user", ""). WithField("access_url", u.String()). WithField("token", tokenID). + WithField("mode", rl.Mode). WithField("reason", reasons["introspection_network_error"]). WithField("reason_id", "introspection_network_error"). Warn("Access request denied") @@ -188,6 +193,7 @@ func (d *WardenEvaluator) EvaluateAccessRequest(r *http.Request) (*Session, erro WithField("access_url", u.String()). WithField("status_code", response.StatusCode). WithField("token", tokenID). + WithField("mode", rl.Mode). WithField("reason", reasons["introspection_http_error"]). WithField("reason_id", "introspection_http_error"). Warn("Access request denied") @@ -199,6 +205,7 @@ func (d *WardenEvaluator) EvaluateAccessRequest(r *http.Request) (*Session, erro WithField("access_url", u.String()). WithField("status_code", response.StatusCode). WithField("token", tokenID). + WithField("mode", rl.Mode). WithField("reason", reasons["introspection_invalid_credentials"]). WithField("reason_id", "introspection_invalid_credentials"). Warn("Access request denied") @@ -211,6 +218,7 @@ func (d *WardenEvaluator) EvaluateAccessRequest(r *http.Request) (*Session, erro WithField("access_url", u.String()). WithField("token", tokenID). WithField("rule", rl.ID). + WithField("mode", rl.Mode). WithField("reason", reasons["introspection_valid"]). WithField("reason_id", "introspection_valid"). Infoln("Access request granted") @@ -220,58 +228,86 @@ func (d *WardenEvaluator) EvaluateAccessRequest(r *http.Request) (*Session, erro ClientID: introspection.ClientId, Anonymous: false, }, nil - } + case rule.PolicyMode: + if token == "" { + d.Logger.WithError(err). + WithField("granted", false). + WithField("user", ""). + WithField("access_url", u.String()). + WithField("token", tokenID). + WithField("mode", rl.Mode). + WithField("reason", reasons["missing_credentials"]). + WithField("reason_id", "missing_credentials"). + Warn("Access request denied") + return nil, errors.WithStack(helper.ErrMissingBearerToken) + } - introspection, response, err := d.Hydra.DoesWardenAllowTokenAccessRequest(d.prepareAccessRequests(r, u.String(), token, rl)) - if err != nil { - d.Logger.WithError(err). - WithField("granted", false). - WithField("user", ""). - WithField("access_url", u.String()). - WithField("token", tokenID). - WithField("reason", reasons["policy_decision_point_network_error"]). - WithField("reason_id", "policy_decision_point_network_error"). - Warn("Access request denied") - return nil, errors.WithStack(err) - } else if response.StatusCode != http.StatusOK { - d.Logger.WithError(err). - WithField("granted", false). - WithField("user", ""). + introspection, response, err := d.Hydra.DoesWardenAllowTokenAccessRequest(d.prepareAccessRequests(r, u.String(), token, rl)) + if err != nil { + d.Logger.WithError(err). + WithField("granted", false). + WithField("user", ""). + WithField("access_url", u.String()). + WithField("token", tokenID). + WithField("mode", rl.Mode). + WithField("reason", reasons["policy_decision_point_network_error"]). + WithField("reason_id", "policy_decision_point_network_error"). + Warn("Access request denied") + return nil, errors.WithStack(err) + } else if response.StatusCode != http.StatusOK { + d.Logger.WithError(err). + WithField("granted", false). + WithField("user", ""). + WithField("access_url", u.String()). + WithField("status_code", response.StatusCode). + WithField("token", tokenID). + WithField("mode", rl.Mode). + WithField("reason", reasons["policy_decision_point_http_error"]). + WithField("reason_id", "policy_decision_point_http_error"). + Warn("Access request denied") + return nil, errors.Errorf("Token introspection expects status code %d but got %d", http.StatusOK, response.StatusCode) + } else if !introspection.Allowed { + d.Logger.WithError(err). + WithField("granted", false). + WithField("user", ""). + WithField("access_url", u.String()). + WithField("status_code", response.StatusCode). + WithField("token", tokenID). + WithField("mode", rl.Mode). + WithField("reason", reasons["policy_decision_point_access_forbidden"]). + WithField("reason_id", "policy_decision_point_access_forbidden"). + Warn("Access request denied") + return nil, errors.WithStack(helper.ErrForbidden) + } + + d.Logger. + WithField("granted", true). + WithField("user", introspection.Subject). WithField("access_url", u.String()). - WithField("status_code", response.StatusCode). WithField("token", tokenID). - WithField("reason", reasons["policy_decision_point_http_error"]). - WithField("reason_id", "policy_decision_point_http_error"). - Warn("Access request denied") - return nil, errors.Errorf("Token introspection expects status code %d but got %d", http.StatusOK, response.StatusCode) - } else if !introspection.Allowed { + WithField("rule", rl.ID). + WithField("mode", rl.Mode). + WithField("reason", reasons["policy_decision_point_access_granted"]). + WithField("reason_id", "policy_decision_point_access_granted"). + Infoln("Access request granted") + return &Session{ + Issuer: introspection.Issuer, + User: introspection.Subject, + ClientID: introspection.ClientId, + Anonymous: false, + }, nil + default: d.Logger.WithError(err). WithField("granted", false). WithField("user", ""). WithField("access_url", u.String()). - WithField("status_code", response.StatusCode). WithField("token", tokenID). - WithField("reason", reasons["policy_decision_point_access_forbidden"]). - WithField("reason_id", "policy_decision_point_access_forbidden"). + WithField("mode", rl.Mode). + WithField("reason", reasons["unknown_mode"]). + WithField("reason_id", "unknown_mode"). Warn("Access request denied") - return nil, errors.WithStack(helper.ErrForbidden) + return nil, errors.Errorf("Unknown rule mode \"%s\"", rl.Mode) } - - d.Logger. - WithField("granted", true). - WithField("user", introspection.Subject). - WithField("access_url", u.String()). - WithField("token", tokenID). - WithField("rule", rl.ID). - WithField("reason", reasons["policy_decision_point_access_granted"]). - WithField("reason_id", "policy_decision_point_access_granted"). - Infoln("Access request granted") - return &Session{ - Issuer: introspection.Issuer, - User: introspection.Subject, - ClientID: introspection.ClientId, - Anonymous: false, - }, nil } func (d *WardenEvaluator) prepareAccessRequests(r *http.Request, u string, token string, rl *rule.Rule) swagger.WardenTokenAccessRequest { diff --git a/rule/doc.go b/rule/doc.go index 3a233818e9..1df7f605ec 100644 --- a/rule/doc.go +++ b/rule/doc.go @@ -78,19 +78,18 @@ type jsonRule struct { // If the token used in the Authorization header did not request that specific scope, the request is denied. RequiredScopes []string `json:"requiredScopes"` - // If set to true, any authorization logic is completely disabled and the Authorization header is not changed at all. - // This is useful if you have an endpoint that has it's own authorization logic, for example using basic authorization. - // If set to true, this setting overrides `basicAuthorizationModeEnabled` and `allowAnonymousModeEnabled`. - PassThroughModeEnabled bool `json:"passThroughModeEnabled"` - - // If set to true, the protected endpoint is available to anonymous users. That means that the endpoint is accessible - // without having a valid access token. This setting overrides `basicAuthorizationModeEnabled`. - AllowAnonymousModeEnabled bool `json:"allowAnonymousModeEnabled"` - - // If set to true, disables checks against ORY Hydra's Warden API and uses basic authorization. This means that - // the access token is validated (e.g. checking if it is expired, check if it claimed the necessary scopes) - // but does not use the `requiredAction` and `requiredResource` fields for advanced access control. - BasicAuthorizationModeEnabled bool `json:"basicAuthorizationModeEnabled"` + // Defines which mode this rule should use. There are four valid modes: + // + // - bypass: If set, any authorization logic is completely disabled and the Authorization header is not changed at all. + // This is useful if you have an endpoint that has it's own authorization logic, for example using basic authorization. + // If set to true, this setting overrides `basicAuthorizationModeEnabled` and `allowAnonymousModeEnabled`. + // - anonymous: If set, the protected endpoint is available to anonymous users. That means that the endpoint is accessible + // without having a valid access token. This setting overrides `basicAuthorizationModeEnabled`. + // - token: If set, disables checks against ORY Hydra's Warden API and uses basic authorization. This means that + // the access token is validated (e.g. checking if it is expired, check if it claimed the necessary scopes) + // but does not use the `requiredAction` and `requiredResource` fields for advanced access control. + // - policy: If set, uses ORY Hydra's Warden API for access control using access control policies. + Mode string `json:"mode"` // This field will be used to decide advanced authorization requests where access control policies are used. A // action is typically something a user wants to do (e.g. write, read, delete). diff --git a/rule/handler.go b/rule/handler.go index 9225ff8ee7..fc06df7c62 100644 --- a/rule/handler.go +++ b/rule/handler.go @@ -4,6 +4,8 @@ import ( "encoding/json" "net/http" + "strings" + "github.com/julienschmidt/httprouter" "github.com/ory/herodot" "github.com/ory/ladon/compiler" @@ -213,32 +215,32 @@ func toRule(rule *jsonRule) (*Rule, error) { return nil, err } + if !stringInSlice(rule.Mode, ruleModes) { + return nil, errors.Errorf("Rule mode %s not supported, use one of: %s", rule.Mode, strings.Join(ruleModes, ",")) + } + return &Rule{ - ID: rule.ID, - MatchesURLCompiled: exp, - MatchesURL: rule.MatchesURL, - MatchesMethods: rule.MatchesMethods, - RequiredScopes: rule.RequiredScopes, - RequiredAction: rule.RequiredAction, - RequiredResource: rule.RequiredResource, - AllowAnonymousModeEnabled: rule.AllowAnonymousModeEnabled, - PassThroughModeEnabled: rule.PassThroughModeEnabled, - BasicAuthorizationModeEnabled: rule.BasicAuthorizationModeEnabled, - Description: rule.Description, + ID: rule.ID, + MatchesURLCompiled: exp, + MatchesURL: rule.MatchesURL, + MatchesMethods: rule.MatchesMethods, + RequiredScopes: rule.RequiredScopes, + RequiredAction: rule.RequiredAction, + RequiredResource: rule.RequiredResource, + Mode: rule.Mode, + Description: rule.Description, }, nil } func encodeRule(r *Rule) *jsonRule { return &jsonRule{ - ID: r.ID, - MatchesURL: r.MatchesURL, - MatchesMethods: r.MatchesMethods, - RequiredScopes: r.RequiredScopes, - RequiredAction: r.RequiredAction, - RequiredResource: r.RequiredResource, - PassThroughModeEnabled: r.PassThroughModeEnabled, - BasicAuthorizationModeEnabled: r.BasicAuthorizationModeEnabled, - AllowAnonymousModeEnabled: r.AllowAnonymousModeEnabled, - Description: r.Description, + ID: r.ID, + MatchesURL: r.MatchesURL, + MatchesMethods: r.MatchesMethods, + RequiredScopes: r.RequiredScopes, + RequiredAction: r.RequiredAction, + RequiredResource: r.RequiredResource, + Mode: r.Mode, + Description: r.Description, } } diff --git a/rule/handler_test.go b/rule/handler_test.go index 6029778e6a..5492428f48 100644 --- a/rule/handler_test.go +++ b/rule/handler_test.go @@ -31,15 +31,14 @@ func TestHandler(t *testing.T) { RequiredResource: "users:$1", RequiredAction: "create:$1", RequiredScopes: []string{"users.create"}, + Mode: PolicyMode, } r2 := swagger.Rule{ - Description: "Get users rule", - MatchesUrl: server.URL + "/users/([0-9]+)", - MatchesMethods: []string{"GET"}, - RequiredScopes: []string{}, - AllowAnonymousModeEnabled: true, - PassThroughModeEnabled: true, - BasicAuthorizationModeEnabled: true, + Description: "Get users rule", + MatchesUrl: server.URL + "/users/([0-9]+)", + MatchesMethods: []string{"GET"}, + RequiredScopes: []string{}, + Mode: AnonymousMode, } t.Run("case=create a new rule", func(t *testing.T) { diff --git a/rule/manager_sql.go b/rule/manager_sql.go index 6dcae817e8..259284ff87 100644 --- a/rule/manager_sql.go +++ b/rule/manager_sql.go @@ -14,17 +14,15 @@ import ( ) type sqlRule struct { - ID string `db:"surrogate_id"` - InternalID int64 `db:"id"` - MatchesMethods string `db:"matches_methods"` - MatchesURL string `db:"matches_url"` - RequiredScopes string `db:"required_scopes"` - RequiredAction string `db:"required_action"` - RequiredResource string `db:"required_resource"` - AllowAnonymousModeEnabled bool `db:"allow_anonymous"` - PassThroughModeEnabled bool `db:"disable_firewall"` - BasicAuthorizationModeEnabled bool `db:"disable_acp"` - Description string `db:"description"` + ID string `db:"surrogate_id"` + InternalID int64 `db:"id"` + MatchesMethods string `db:"matches_methods"` + MatchesURL string `db:"matches_url"` + RequiredScopes string `db:"required_scopes"` + RequiredAction string `db:"required_action"` + RequiredResource string `db:"required_resource"` + Description string `db:"description"` + Mode string `db:"mode"` } func (r *sqlRule) toRule() (*Rule, error) { @@ -43,32 +41,28 @@ func (r *sqlRule) toRule() (*Rule, error) { } return &Rule{ - ID: r.ID, - MatchesMethods: methods, - MatchesURLCompiled: exp, - MatchesURL: r.MatchesURL, - RequiredScopes: scopes, - RequiredAction: r.RequiredAction, - RequiredResource: r.RequiredResource, - AllowAnonymousModeEnabled: r.AllowAnonymousModeEnabled, - PassThroughModeEnabled: r.PassThroughModeEnabled, - BasicAuthorizationModeEnabled: r.BasicAuthorizationModeEnabled, - Description: r.Description, + ID: r.ID, + MatchesMethods: methods, + MatchesURLCompiled: exp, + MatchesURL: r.MatchesURL, + RequiredScopes: scopes, + RequiredAction: r.RequiredAction, + RequiredResource: r.RequiredResource, + Mode: r.Mode, + Description: r.Description, }, nil } func toSqlRule(r *Rule) *sqlRule { return &sqlRule{ - ID: r.ID, - MatchesMethods: strings.Join(r.MatchesMethods, " "), - MatchesURL: r.MatchesURL, - RequiredScopes: strings.Join(r.RequiredScopes, " "), - RequiredAction: r.RequiredAction, - RequiredResource: r.RequiredResource, - AllowAnonymousModeEnabled: r.AllowAnonymousModeEnabled, - PassThroughModeEnabled: r.PassThroughModeEnabled, - BasicAuthorizationModeEnabled: r.BasicAuthorizationModeEnabled, - Description: r.Description, + ID: r.ID, + MatchesMethods: strings.Join(r.MatchesMethods, " "), + MatchesURL: r.MatchesURL, + RequiredScopes: strings.Join(r.RequiredScopes, " "), + RequiredAction: r.RequiredAction, + RequiredResource: r.RequiredResource, + Description: r.Description, + Mode: r.Mode, } } @@ -84,10 +78,8 @@ var migrations = &migrate.MemoryMigrationSource{ required_scopes text NOT NULL, required_action text NOT NULL, required_resource text NOT NULL, - allow_anonymous bool NOT NULL, description text NOT NULL, - disable_firewall bool NOT NULL, - disable_acp bool NOT NULL + mode text NOT NULL )`}, Down: []string{ "DROP TABLE hydra_client", @@ -103,10 +95,8 @@ var sqlParams = []string{ "required_scopes", "required_action", "required_resource", - "allow_anonymous", "description", - "disable_firewall", - "disable_acp", + "mode", } func NewSQLManager(db *sqlx.DB) *SQLManager { diff --git a/rule/manager_test.go b/rule/manager_test.go index 1c7a2ece50..e2d89bb5b0 100644 --- a/rule/manager_test.go +++ b/rule/manager_test.go @@ -60,15 +60,13 @@ func TestManagers(t *testing.T) { RequiredScopes: []string{"users.create"}, } r2 := Rule{ - ID: "foo2", - Description: "Get users rule", - MatchesURLCompiled: mustCompileRegex(t, "/users/([0-9]+)"), - MatchesURL: "/users/([0-9]+)", - MatchesMethods: []string{"GET"}, - AllowAnonymousModeEnabled: true, - RequiredScopes: []string{}, - PassThroughModeEnabled: true, - BasicAuthorizationModeEnabled: true, + ID: "foo2", + Description: "Get users rule", + MatchesURLCompiled: mustCompileRegex(t, "/users/([0-9]+)"), + MatchesURL: "/users/([0-9]+)", + MatchesMethods: []string{"GET"}, + Mode: AnonymousMode, + RequiredScopes: []string{}, } t.Run("case="+k, func(t *testing.T) { diff --git a/rule/rule.go b/rule/rule.go index dc051d394b..62b94f0e3b 100644 --- a/rule/rule.go +++ b/rule/rule.go @@ -8,6 +8,18 @@ import ( "github.com/pkg/errors" ) +const BypassMode = "bypass" +const AnonymousMode = "anonymous" +const AuthenticatedMode = "authenticated" +const PolicyMode = "policy" + +var ruleModes = []string{ + BypassMode, + AnonymousMode, + AuthenticatedMode, + PolicyMode, +} + // Rule is a single rule that will get checked on every HTTP request. type Rule struct { // ID is the unique id of the rule. It can be at most 190 characters long, but the layout of the ID is up to you. @@ -34,14 +46,8 @@ type Rule struct { // RequiredScopes is the resource this rule requires. RequiredResource string - // AllowAnonymousModeEnabled if set true allows anonymous access to the specified paths and methods. - AllowAnonymousModeEnabled bool - - // PassThroughModeEnabled if set true disables firewall capabilities. - PassThroughModeEnabled bool - - // BasicAuthorizationModeEnabled if set true disables checking access control policies. - BasicAuthorizationModeEnabled bool + // Mode is the mode this rule enforces. + Mode string // Description describes the rule. Description string diff --git a/sdk/go/oathkeepersdk/swagger/docs/Rule.md b/sdk/go/oathkeepersdk/swagger/docs/Rule.md index 7f6a333a1a..a11bdf38bf 100644 --- a/sdk/go/oathkeepersdk/swagger/docs/Rule.md +++ b/sdk/go/oathkeepersdk/swagger/docs/Rule.md @@ -3,13 +3,11 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**AllowAnonymousModeEnabled** | **bool** | If set to true, the protected endpoint is available to anonymous users. That means that the endpoint is accessible without having a valid access token. This setting overrides `basicAuthorizationModeEnabled`. | [optional] [default to null] -**BasicAuthorizationModeEnabled** | **bool** | If set to true, disables checks against ORY Hydra's Warden API and uses basic authorization. This means that the access token is validated (e.g. checking if it is expired, check if it claimed the necessary scopes) but does not use the `requiredAction` and `requiredResource` fields for advanced access control. | [optional] [default to null] **Description** | **string** | A human readable description of this rule. | [optional] [default to null] **Id** | **string** | The ID is the unique id of the rule. It can be at most 190 characters long, but the layout of the ID is up to you. You will need this ID later on to update or delete the rule. | [optional] [default to null] **MatchesMethods** | **[]string** | An array of HTTP methods (e.g. GET, POST, PUT, DELETE, ...). When ORY Oathkeeper searches for rules to decide what to do with an incoming request to the proxy server, it compares the HTTP method of the incoming request with the HTTP methods of each rules. If a match is found, the rule is considered a partial match. If the matchesUrl field is satisfied as well, the rule is considered a full match. | [optional] [default to null] **MatchesUrl** | **string** | This field represents the URL pattern this rule matches. When ORY Oathkeeper searches for rules to decide what to do with an incoming request to the proxy server, it compares the full request URL (e.g. https://mydomain.com/api/resource) without query parameters of the incoming request with this field. If a match is found, the rule is considered a partial match. If the matchesMethods field is satisfied as well, the rule is considered a full match. You can use regular expressions in this field to match more than one url. Regular expressions are encapsulated in brackets < and >. The following example matches all paths of the domain `mydomain.com`: `https://mydomain.com/<.*>`. For more information refer to: https://ory.gitbooks.io/oathkeeper/content/concepts.html#rules | [optional] [default to null] -**PassThroughModeEnabled** | **bool** | If set to true, any authorization logic is completely disabled and the Authorization header is not changed at all. This is useful if you have an endpoint that has it's own authorization logic, for example using basic authorization. If set to true, this setting overrides `basicAuthorizationModeEnabled` and `allowAnonymousModeEnabled`. | [optional] [default to null] +**Mode** | **string** | Defines which mode this rule should use. There are four valid modes: bypass: If set, any authorization logic is completely disabled and the Authorization header is not changed at all. This is useful if you have an endpoint that has it's own authorization logic, for example using basic authorization. If set to true, this setting overrides `basicAuthorizationModeEnabled` and `allowAnonymousModeEnabled`. anonymous: If set, the protected endpoint is available to anonymous users. That means that the endpoint is accessible without having a valid access token. This setting overrides `basicAuthorizationModeEnabled`. token: If set, disables checks against ORY Hydra's Warden API and uses basic authorization. This means that the access token is validated (e.g. checking if it is expired, check if it claimed the necessary scopes) but does not use the `requiredAction` and `requiredResource` fields for advanced access control. policy: If set, uses ORY Hydra's Warden API for access control using access control policies. | [optional] [default to null] **RequiredAction** | **string** | This field will be used to decide advanced authorization requests where access control policies are used. A action is typically something a user wants to do (e.g. write, read, delete). This field supports expansion as described in the developer guide: https://ory.gitbooks.io/oathkeeper/content/concepts.html#rules | [optional] [default to null] **RequiredResource** | **string** | This field will be used to decide advanced authorization requests where access control policies are used. A resource is typically something a user wants to access (e.g. printer, article, virtual machine). This field supports expansion as described in the developer guide: https://ory.gitbooks.io/oathkeeper/content/concepts.html#rules | [optional] [default to null] **RequiredScopes** | **[]string** | An array of OAuth 2.0 scopes that are required when accessing an endpoint protected by this rule. If the token used in the Authorization header did not request that specific scope, the request is denied. | [optional] [default to null] diff --git a/sdk/go/oathkeepersdk/swagger/rule.go b/sdk/go/oathkeepersdk/swagger/rule.go index 7d0f321534..12cf3c9cb4 100644 --- a/sdk/go/oathkeepersdk/swagger/rule.go +++ b/sdk/go/oathkeepersdk/swagger/rule.go @@ -13,12 +13,6 @@ package swagger // A rule type Rule struct { - // If set to true, the protected endpoint is available to anonymous users. That means that the endpoint is accessible without having a valid access token. This setting overrides `basicAuthorizationModeEnabled`. - AllowAnonymousModeEnabled bool `json:"allowAnonymousModeEnabled,omitempty"` - - // If set to true, disables checks against ORY Hydra's Warden API and uses basic authorization. This means that the access token is validated (e.g. checking if it is expired, check if it claimed the necessary scopes) but does not use the `requiredAction` and `requiredResource` fields for advanced access control. - BasicAuthorizationModeEnabled bool `json:"basicAuthorizationModeEnabled,omitempty"` - // A human readable description of this rule. Description string `json:"description,omitempty"` @@ -31,8 +25,8 @@ type Rule struct { // This field represents the URL pattern this rule matches. When ORY Oathkeeper searches for rules to decide what to do with an incoming request to the proxy server, it compares the full request URL (e.g. https://mydomain.com/api/resource) without query parameters of the incoming request with this field. If a match is found, the rule is considered a partial match. If the matchesMethods field is satisfied as well, the rule is considered a full match. You can use regular expressions in this field to match more than one url. Regular expressions are encapsulated in brackets < and >. The following example matches all paths of the domain `mydomain.com`: `https://mydomain.com/<.*>`. For more information refer to: https://ory.gitbooks.io/oathkeeper/content/concepts.html#rules MatchesUrl string `json:"matchesUrl,omitempty"` - // If set to true, any authorization logic is completely disabled and the Authorization header is not changed at all. This is useful if you have an endpoint that has it's own authorization logic, for example using basic authorization. If set to true, this setting overrides `basicAuthorizationModeEnabled` and `allowAnonymousModeEnabled`. - PassThroughModeEnabled bool `json:"passThroughModeEnabled,omitempty"` + // Defines which mode this rule should use. There are four valid modes: bypass: If set, any authorization logic is completely disabled and the Authorization header is not changed at all. This is useful if you have an endpoint that has it's own authorization logic, for example using basic authorization. If set to true, this setting overrides `basicAuthorizationModeEnabled` and `allowAnonymousModeEnabled`. anonymous: If set, the protected endpoint is available to anonymous users. That means that the endpoint is accessible without having a valid access token. This setting overrides `basicAuthorizationModeEnabled`. token: If set, disables checks against ORY Hydra's Warden API and uses basic authorization. This means that the access token is validated (e.g. checking if it is expired, check if it claimed the necessary scopes) but does not use the `requiredAction` and `requiredResource` fields for advanced access control. policy: If set, uses ORY Hydra's Warden API for access control using access control policies. + Mode string `json:"mode,omitempty"` // This field will be used to decide advanced authorization requests where access control policies are used. A action is typically something a user wants to do (e.g. write, read, delete). This field supports expansion as described in the developer guide: https://ory.gitbooks.io/oathkeeper/content/concepts.html#rules RequiredAction string `json:"requiredAction,omitempty"` diff --git a/stub/rules.json b/stub/rules.json index 4fe29c807d..0986119f7a 100644 --- a/stub/rules.json +++ b/stub/rules.json @@ -2,10 +2,10 @@ "id": "test-rule-1", "matchesMethods": ["POST", "GET"], "matchesUrl": "http://localhost/", - "passThroughModeEnabled": true + "mode": "bypass" }, { "id": "test-rule-2", "matchesMethods": ["POST", "GET"], "matchesUrl": "http://localhost/", - "passThroughModeEnabled": true + "mode": "bypass" }] \ No newline at end of file