-
-
Notifications
You must be signed in to change notification settings - Fork 362
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(authz): Add remote_json authorizer (#389)
This patch adds the `remote_json` authorizer as documented here: ory/docs@07a2297#diff-c400219db6c7e4b6abab71839d9d294eR272 Closes #201
- Loading branch information
Showing
10 changed files
with
480 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
{ | ||
"$id": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/authorizers.remote_json.schema.json", | ||
"$schema": "http://json-schema.org/draft-07/schema#", | ||
"type": "object", | ||
"title": "Remote JSON Configuration", | ||
"description": "This section is optional when the authorizer is disabled.", | ||
"properties": { | ||
"remote": { | ||
"title": "Remote Authorizer URL", | ||
"type": "string", | ||
"format": "uri", | ||
"description": "The URL of the remote authorizer. The remote authorizer is expected to return either 200 OK or 403 Forbidden to allow/deny access.\n\n>If this authorizer is enabled, this value is required.", | ||
"examples": [ | ||
"https://host/path" | ||
] | ||
}, | ||
"payload": { | ||
"title": "JSON Payload", | ||
"type": "string", | ||
"description": "The JSON payload of the request sent to the remote authorizer. The string will be parsed by the Go text/template package and applied to an AuthenticationSession object.\n\n>If this authorizer is enabled, this value is required.", | ||
"examples": [ | ||
"{\"subject\":\"{{ .Subject }}\"}" | ||
] | ||
} | ||
}, | ||
"required": [ | ||
"remote", | ||
"payload" | ||
], | ||
"additionalProperties": false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"$id": "https://raw.githubusercontent.com/ory/oathkeeper/v0.34.0-beta.1/.schemas/authorizers.remote_json.schema.json", | ||
"$schema": "http://json-schema.org/draft-07/schema#", | ||
"$ref": "https://raw.githubusercontent.com/ory/oathkeeper/v0.34.0-beta.1/.schemas/config.schema.json#/definitions/configAuthorizersRemoteJSON" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package driver | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestRegistryMemoryAvailablePipelineAuthorizers(t *testing.T) { | ||
r := NewRegistryMemory() | ||
got := r.AvailablePipelineAuthorizers() | ||
assert.ElementsMatch(t, got, []string{"allow", "deny", "keto_engine_acp_ory", "remote_json"}) | ||
} | ||
|
||
func TestRegistryMemoryPipelineAuthorizer(t *testing.T) { | ||
tests := []struct { | ||
id string | ||
wantErr bool | ||
}{ | ||
{id: "allow"}, | ||
{id: "deny"}, | ||
{id: "keto_engine_acp_ory"}, | ||
{id: "remote_json"}, | ||
{id: "unregistered", wantErr: true}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.id, func(t *testing.T) { | ||
r := NewRegistryMemory() | ||
a, err := r.PipelineAuthorizer(tt.id) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("PipelineAuthorizer() error = %v, wantErr %v", err, tt.wantErr) | ||
return | ||
} | ||
if a != nil && a.GetID() != tt.id { | ||
t.Errorf("PipelineAuthorizer() got = %v, want %v", a.GetID(), tt.id) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
package authz | ||
|
||
import ( | ||
"bytes" | ||
"crypto/sha256" | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"text/template" | ||
|
||
"github.com/ory/x/httpx" | ||
"github.com/pkg/errors" | ||
|
||
"github.com/ory/oathkeeper/driver/configuration" | ||
"github.com/ory/oathkeeper/helper" | ||
"github.com/ory/oathkeeper/pipeline" | ||
"github.com/ory/oathkeeper/pipeline/authn" | ||
"github.com/ory/oathkeeper/x" | ||
) | ||
|
||
// AuthorizerRemoteJSONConfiguration represents a configuration for the remote_json authorizer. | ||
type AuthorizerRemoteJSONConfiguration struct { | ||
Remote string `json:"remote"` | ||
Payload string `json:"payload"` | ||
} | ||
|
||
// PayloadTemplateID returns a string with which to associate the payload template. | ||
func (c *AuthorizerRemoteJSONConfiguration) PayloadTemplateID() string { | ||
return fmt.Sprintf("%x", sha256.Sum256([]byte(c.Payload))) | ||
} | ||
|
||
// AuthorizerRemoteJSON implements the Authorizer interface. | ||
type AuthorizerRemoteJSON struct { | ||
c configuration.Provider | ||
|
||
client *http.Client | ||
t *template.Template | ||
} | ||
|
||
// NewAuthorizerRemoteJSON creates a new AuthorizerRemoteJSON. | ||
func NewAuthorizerRemoteJSON(c configuration.Provider) *AuthorizerRemoteJSON { | ||
return &AuthorizerRemoteJSON{ | ||
c: c, | ||
client: httpx.NewResilientClientLatencyToleranceSmall(nil), | ||
t: x.NewTemplate("remote_json"), | ||
} | ||
} | ||
|
||
// GetID implements the Authorizer interface. | ||
func (a *AuthorizerRemoteJSON) GetID() string { | ||
return "remote_json" | ||
} | ||
|
||
// Authorize implements the Authorizer interface. | ||
func (a *AuthorizerRemoteJSON) Authorize(_ *http.Request, session *authn.AuthenticationSession, config json.RawMessage, _ pipeline.Rule) error { | ||
c, err := a.Config(config) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
templateID := c.PayloadTemplateID() | ||
t := a.t.Lookup(templateID) | ||
if t == nil { | ||
var err error | ||
t, err = a.t.New(templateID).Parse(c.Payload) | ||
if err != nil { | ||
return errors.WithStack(err) | ||
} | ||
} | ||
|
||
var body bytes.Buffer | ||
if err := t.Execute(&body, session); err != nil { | ||
return errors.WithStack(err) | ||
} | ||
|
||
var j json.RawMessage | ||
if err := json.Unmarshal(body.Bytes(), &j); err != nil { | ||
return errors.Wrap(err, "payload is not a JSON text") | ||
} | ||
|
||
req, err := http.NewRequest("POST", c.Remote, &body) | ||
if err != nil { | ||
return errors.WithStack(err) | ||
} | ||
req.Header.Add("Content-Type", "application/json") | ||
|
||
res, err := a.client.Do(req) | ||
if err != nil { | ||
return errors.WithStack(err) | ||
} | ||
defer res.Body.Close() | ||
|
||
if res.StatusCode == http.StatusForbidden { | ||
return errors.WithStack(helper.ErrForbidden) | ||
} else if res.StatusCode != http.StatusOK { | ||
return errors.Errorf("expected status code %d but got %d", http.StatusOK, res.StatusCode) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Validate implements the Authorizer interface. | ||
func (a *AuthorizerRemoteJSON) Validate(config json.RawMessage) error { | ||
if !a.c.AuthorizerIsEnabled(a.GetID()) { | ||
return NewErrAuthorizerNotEnabled(a) | ||
} | ||
|
||
_, err := a.Config(config) | ||
return err | ||
} | ||
|
||
// Config merges config and the authorizer's configuration and validates the | ||
// resulting configuration. It reports an error if the configuration is invalid. | ||
func (a *AuthorizerRemoteJSON) Config(config json.RawMessage) (*AuthorizerRemoteJSONConfiguration, error) { | ||
var c AuthorizerRemoteJSONConfiguration | ||
if err := a.c.AuthorizerConfig(a.GetID(), config, &c); err != nil { | ||
return nil, NewErrAuthorizerMisconfigured(a, err) | ||
} | ||
|
||
return &c, nil | ||
} |
Oops, something went wrong.