Skip to content

Commit

Permalink
Merge pull request #156 from Kuadrant/post-http-metadata
Browse files Browse the repository at this point in the history
Custom parameters in the body of HTTP external metadata `POST` requests
  • Loading branch information
guicassolato authored Oct 7, 2021
2 parents f0d7321 + d054b80 commit 423fb41
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 13 deletions.
11 changes: 11 additions & 0 deletions api/v1beta1/auth_config_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ type Metadata_UMA struct {
// +kubebuilder:validation:Enum:=GET;POST
type GenericHTTP_Method string

// +kubebuilder:validation:Enum:=application/x-www-form-urlencoded;application/json
type Metadata_GenericHTTP_ContentType string

// Generic HTTP interface to obtain authorization metadata from a HTTP service.
type Metadata_GenericHTTP struct {
// Endpoint of the HTTP service.
Expand All @@ -207,6 +210,14 @@ type Metadata_GenericHTTP struct {
// When the request method is POST, the authorization JSON is passed in the body of the request.
Method GenericHTTP_Method `json:"method,omitempty"`

// Custom parameters to encode in the body of the HTTP request.
// Use it with method=POST; for GET requests, specify parameters using placeholders in the endpoint.
Parameters []JsonProperty `json:"bodyParameters,omitempty"`

// Content-Type of the request body.
// +kubebuilder:default:=application/x-www-form-urlencoded
ContentType Metadata_GenericHTTP_ContentType `json:"contentType,omitempty"`

// Reference to a Secret key whose value will be passed by Authorino in the request.
// The HTTP service can use the shared secret to authenticate the origin of the request.
SharedSecret *SecretKeyReference `json:"sharedSecretRef,omitempty"`
Expand Down
7 changes: 7 additions & 0 deletions api/v1beta1/zz_generated.deepcopy.go

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

13 changes: 13 additions & 0 deletions controllers/auth_config_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,22 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf
sharedSecret = string(secret.Data[sharedSecretRef.Key])
}

params := make([]common.JSONProperty, 0, len(genericHttp.Parameters))
for _, param := range genericHttp.Parameters {
params = append(params, common.JSONProperty{
Name: param.Name,
Value: common.JSONValue{
Static: param.Value,
Pattern: param.ValueFrom.AuthJSON,
},
})
}

translatedMetadata.GenericHTTP = &authorinoMetadata.GenericHttp{
Endpoint: genericHttp.Endpoint,
Method: string(genericHttp.Method),
Parameters: params,
ContentType: string(genericHttp.ContentType),
SharedSecret: sharedSecret,
AuthCredentials: auth_credentials.NewAuthCredential(creds.KeySelector, string(creds.In)),
}
Expand Down
63 changes: 54 additions & 9 deletions examples/ext-http-metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,68 @@ spec:
in: authorization_header
keySelector: APIKEY
metadata:
- name: echo-api
- name: echo-api-get
http:
endpoint: http://talker-api.authorino.svc.cluster.local:3000/authz?requested_path={context.request.http.path}
method: POST
endpoint: http://talker-api.authorino.svc.cluster.local:3000/metadata?encoding=text/plain&original_path={context.request.http.path}
method: GET
sharedSecretRef:
name: talker-api-protection-secret
key: echo-metadata-shared-auth
credentials:
in: authorization_header
keySelector: Bearer
authorization:
- name: validate-echo-response
- name: echo-api-post-form
http:
endpoint: http://talker-api.authorino.svc.cluster.local:3000/metadata?encoding=form-data
method: POST
bodyParameters:
- name: original_path
valueFrom:
authJSON: context.request.http.path
- name: my_str
value: foo
- name: my_num
value: 123
- name: my_bool
value: true
- name: my_arr
value: ["a", "b", "c"]
- name: my_obj
value:
a_prop: "a value"
- name: echo-api-post-json
http:
endpoint: http://talker-api.authorino.svc.cluster.local:3000/metadata?encoding=json
method: POST
contentType: application/json
bodyParameters:
- name: original_path
valueFrom:
authJSON: context.request.http.path
- name: my_str
value: foo
- name: my_num
value: 123
- name: my_bool
value: true
- name: my_arr
value: ["a", "b", "c"]
- name: my_obj
value:
a_prop: "a value"
response:
- name: echo-api-metadata
json:
rules:
- selector: auth.metadata.echo-api.path
operator: eq
value: /authz
properties:
- name: get-request
valueFrom:
authJSON: auth.metadata.echo-api-get
- name: post-request-form
valueFrom:
authJSON: auth.metadata.echo-api-post-form
- name: post-request-json
valueFrom:
authJSON: auth.metadata.echo-api-post-json
---
apiVersion: v1
kind: Secret
Expand Down
31 changes: 31 additions & 0 deletions install/crd/authorino.3scale.net_authconfigs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,37 @@ spec:
description: Generic HTTP interface to obtain authorization
metadata from a HTTP service.
properties:
bodyParameters:
description: Custom parameters to encode in the body of
the HTTP request. Use it with method=POST; for GET requests,
specify parameters using placeholders in the endpoint.
items:
properties:
name:
description: The name of the claim
type: string
value:
description: Static value of the claim
x-kubernetes-preserve-unknown-fields: true
valueFrom:
description: Dynamic value of the claim
properties:
authJSON:
description: Selector to fill the value of claim
from the authorization JSON
type: string
type: object
required:
- name
type: object
type: array
contentType:
default: application/x-www-form-urlencoded
description: Content-Type of the request body.
enum:
- application/x-www-form-urlencoded
- application/json
type: string
credentials:
description: Defines where client credentials will be passed
in the request to the service. If omitted, it defaults
Expand Down
44 changes: 43 additions & 1 deletion pkg/config/metadata/generic_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"

"github.com/kuadrant/authorino/pkg/common"
"github.com/kuadrant/authorino/pkg/common/auth_credentials"
Expand All @@ -15,6 +16,8 @@ import (
type GenericHttp struct {
Endpoint string
Method string
Parameters []common.JSONProperty
ContentType string
SharedSecret string
auth_credentials.AuthCredentials
}
Expand All @@ -28,13 +31,20 @@ func (h *GenericHttp) Call(pipeline common.AuthPipeline, ctx context.Context) (i
endpoint := common.ReplaceJSONPlaceholders(h.Endpoint, string(authData))

var requestBody io.Reader
var contentType string

method := h.Method
switch method {
case "GET":
contentType = "text/plain"
requestBody = nil
case "POST":
requestBody = bytes.NewBuffer(authData)
var err error
contentType = h.ContentType
requestBody, err = h.buildRequestBody(string(authData))
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unsupported method")
}
Expand All @@ -44,6 +54,8 @@ func (h *GenericHttp) Call(pipeline common.AuthPipeline, ctx context.Context) (i
return nil, err
}

req.Header.Set("Content-Type", contentType)

resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
Expand All @@ -59,3 +71,33 @@ func (h *GenericHttp) Call(pipeline common.AuthPipeline, ctx context.Context) (i

return claims, nil
}

func (h *GenericHttp) buildRequestBody(authData string) (io.Reader, error) {
data := make(map[string]interface{})
for _, param := range h.Parameters {
data[param.Name] = param.Value.ResolveFor(authData)
}

switch h.ContentType {
case "application/x-www-form-urlencoded":
formData := url.Values{}
for key, value := range data {
if valueAsStr, err := common.StringifyJSON(value); err != nil {
return nil, fmt.Errorf("failed to encode http request")
} else {
formData.Set(key, valueAsStr)
}
}
return bytes.NewBufferString(formData.Encode()), nil

case "application/json":
if dataJSON, err := json.Marshal(data); err != nil {
return nil, err
} else {
return bytes.NewBuffer(dataJSON), nil
}

default:
return nil, fmt.Errorf("unsupported content-type")
}
}
7 changes: 4 additions & 3 deletions pkg/config/metadata/generic_http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package metadata
import (
"bytes"
"context"
"encoding/json"
"net/http"
"testing"

"github.com/kuadrant/authorino/pkg/common"
. "github.com/kuadrant/authorino/pkg/common/auth_credentials/mocks"
. "github.com/kuadrant/authorino/pkg/common/mocks"

Expand Down Expand Up @@ -72,14 +72,15 @@ func TestGenericHttpCallWithPOST(t *testing.T) {
pipelineMock.EXPECT().GetDataForAuthorization().Return(dataForAuthorization)

sharedCredsMock := NewMockAuthCredentials(ctrl)
identityObjectMockJSON, _ := json.Marshal(dataForAuthorization)
requestBody := bytes.NewBuffer(identityObjectMockJSON)
requestBody := bytes.NewBuffer([]byte("user=mock"))
httpRequestMock, _ := http.NewRequest("POST", endpoint, requestBody)
sharedCredsMock.EXPECT().BuildRequestWithCredentials(ctx, endpoint, "POST", "secret", requestBody).Return(httpRequestMock, nil)

metadata := &GenericHttp{
Endpoint: endpoint,
Method: "POST",
Parameters: []common.JSONProperty{{Name: "user", Value: common.JSONValue{Pattern: "auth.identity.user"}}},
ContentType: "application/x-www-form-urlencoded",
SharedSecret: "secret",
AuthCredentials: sharedCredsMock,
}
Expand Down

0 comments on commit 423fb41

Please sign in to comment.