Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Renames bypass values for better clarity #31

Merged
merged 1 commit into from
Nov 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions director/director_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
18 changes: 4 additions & 14 deletions docs/api.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
7 changes: 5 additions & 2 deletions evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,31 @@ 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"},
MatchesURLCompiled: mustCompileRegex(t, "http://localhost/users<$|/([0-9]+)>"),
RequiredResource: "users",
RequiredAction: "get",
RequiredScopes: []string{"users.create"},
Mode: rule.PolicyMode,
}
privateRuleWithPartialSubstitution := rule.Rule{
MatchesMethods: []string{"POST"},
MatchesURLCompiled: mustCompileRegex(t, "http://localhost/users<$|/([0-9]+)>"),
RequiredResource: "users:$2",
RequiredAction: "get",
RequiredScopes: []string{"users.create"},
Mode: rule.PolicyMode,
}

for k, tc := range []struct {
Expand Down
156 changes: 96 additions & 60 deletions evaluator/evaluator_warden.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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).
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -155,28 +160,28 @@ 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).
WithField("granted", false).
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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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 {
Expand Down
25 changes: 12 additions & 13 deletions rule/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
Loading