Refactor metadata parsing in middlewares and workflows (#2715)

Signed-off-by: Bernd Verst <github@bernd.dev>
This commit is contained in:
Bernd Verst 2023-03-28 09:34:20 -07:00 committed by GitHub
parent 2681d97673
commit a069e682be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 149 additions and 87 deletions

View File

@ -140,11 +140,14 @@ func GetMetadataProperty(props map[string]string, keys ...string) (val string, o
// DecodeMetadata decodes metadata into a struct
// This is an extension of mitchellh/mapstructure which also supports decoding durations
func DecodeMetadata(input interface{}, result interface{}) error {
// avoids a common mistake of passing the metadata object, instead of the properties map
// if input is not of type map[string]string, then cast to metadata.Base and access the Properties
if _, ok := input.(map[string]string); !ok {
if base, ok := input.(Base); ok {
input = base.Properties
// avoids a common mistake of passing the metadata struct, instead of the properties map
// if input is of type struct, case it to metadata.Base and access the Properties instead
v := reflect.ValueOf(input)
if v.Kind() == reflect.Struct {
f := v.FieldByName("Properties")
if f.IsValid() && f.Kind() == reflect.Map {
properties := f.Interface().(map[string]string)
input = properties
}
}

View File

@ -17,6 +17,7 @@ import (
"context"
"fmt"
"net/http"
"reflect"
"strings"
"time"
@ -25,6 +26,7 @@ import (
"github.com/lestrrat-go/jwx/v2/jwt"
"github.com/dapr/components-contrib/internal/httputils"
contribMetadata "github.com/dapr/components-contrib/metadata"
"github.com/dapr/components-contrib/middleware"
"github.com/dapr/kit/logger"
)
@ -123,3 +125,10 @@ func (m *Middleware) GetHandler(ctx context.Context, metadata middleware.Metadat
})
}, nil
}
func (m *Middleware) GetComponentMetadata() map[string]string {
metadataStruct := bearerMiddlewareMetadata{}
metadataInfo := map[string]string{}
contribMetadata.GetMetadataInfoFromStructType(reflect.TypeOf(metadataStruct), &metadataInfo)
return metadataInfo
}

View File

@ -29,19 +29,19 @@ import (
type bearerMiddlewareMetadata struct {
// Issuer authority.
Issuer string `json:"issuer"`
Issuer string `json:"issuer" mapstructure:"issuer"`
// Audience to expect in the token (usually, a client ID).
Audience string `json:"audience"`
Audience string `json:"audience" mapstructure:"audience"`
// Optional address of the JKWS file.
// If missing, will try to fetch the URL set in the OpenID Configuration document `<issuer>/.well-known/openid-configuration`.
JWKSURL string `json:"jwksURL"`
JWKSURL string `json:"jwksURL" mapstructure:"jwksURL"`
// Deprecated - use "issuer" instead.
IssuerURL string `json:"issuerURL"`
IssuerURL string `json:"issuerURL" mapstructure:"issuerURL"`
// Deprecated - use "audience" instead.
ClientID string `json:"clientID"`
ClientID string `json:"clientID" mapstructure:"clientID"`
// Internal properties
logger logger.Logger
logger logger.Logger `json:"-" mapstructure:"-"`
}
// Parse the component's metadata into the object.

View File

@ -17,6 +17,7 @@ import (
"context"
"net/http"
"net/url"
"reflect"
"strings"
"github.com/fasthttp-contrib/sessions"
@ -32,14 +33,14 @@ import (
// Metadata is the oAuth middleware config.
type oAuth2MiddlewareMetadata struct {
ClientID string `json:"clientID"`
ClientSecret string `json:"clientSecret"`
Scopes string `json:"scopes"`
AuthURL string `json:"authURL"`
TokenURL string `json:"tokenURL"`
AuthHeaderName string `json:"authHeaderName"`
RedirectURL string `json:"redirectURL"`
ForceHTTPS string `json:"forceHTTPS"`
ClientID string `json:"clientID" mapstructure:"clientID"`
ClientSecret string `json:"clientSecret" mapstructure:"clientSecret"`
Scopes string `json:"scopes" mapstructure:"scopes"`
AuthURL string `json:"authURL" mapstructure:"authURL"`
TokenURL string `json:"tokenURL" mapstructure:"tokenURL"`
AuthHeaderName string `json:"authHeaderName" mapstructure:"authHeaderName"`
RedirectURL string `json:"redirectURL" mapstructure:"redirectURL"`
ForceHTTPS string `json:"forceHTTPS" mapstructure:"forceHTTPS"`
}
// NewOAuth2Middleware returns a new oAuth2 middleware.
@ -153,3 +154,10 @@ func (m *Middleware) getNativeMetadata(metadata middleware.Metadata) (*oAuth2Mid
}
return &middlewareMetadata, nil
}
func (m *Middleware) GetComponentMetadata() map[string]string {
metadataStruct := oAuth2MiddlewareMetadata{}
metadataInfo := map[string]string{}
mdutils.GetMetadataInfoFromStructType(reflect.TypeOf(metadataStruct), &metadataInfo)
return metadataInfo
}

View File

@ -20,6 +20,7 @@ import (
"fmt"
"net/http"
"net/url"
"reflect"
"strings"
"time"
@ -34,13 +35,13 @@ import (
// Metadata is the oAuth clientcredentials middleware config.
type oAuth2ClientCredentialsMiddlewareMetadata struct {
ClientID string `json:"clientID"`
ClientSecret string `json:"clientSecret"`
Scopes string `json:"scopes"`
TokenURL string `json:"tokenURL"`
HeaderName string `json:"headerName"`
EndpointParamsQuery string `json:"endpointParamsQuery,omitempty"`
AuthStyle int `json:"authStyle"`
ClientID string `json:"clientID" mapstructure:"clientID"`
ClientSecret string `json:"clientSecret" mapstructure:"clientSecret"`
Scopes string `json:"scopes" mapstructure:"scopes"`
TokenURL string `json:"tokenURL" mapstructure:"tokenURL"`
HeaderName string `json:"headerName" mapstructure:"headerName"`
EndpointParamsQuery string `json:"endpointParamsQuery,omitempty" mapstructure:"endpointParamsQuery"`
AuthStyle int `json:"authStyle" mapstructure:"authStyle"`
}
// TokenProviderInterface provides a common interface to Mock the Token retrieval in unit tests.
@ -176,3 +177,10 @@ func (m *Middleware) GetToken(ctx context.Context, conf *clientcredentials.Confi
return tokenSource.Token()
}
func (m *Middleware) GetComponentMetadata() map[string]string {
metadataStruct := oAuth2ClientCredentialsMiddlewareMetadata{}
metadataInfo := map[string]string{}
mdutils.GetMetadataInfoFromStructType(reflect.TypeOf(metadataStruct), &metadataInfo)
return metadataInfo
}

View File

@ -59,7 +59,7 @@ func TestOAuth2ClientCredentialsMetadata(t *testing.T) {
"authStyle": "asdf", // This is the value to test
}
_, err2 := NewOAuth2ClientCredentialsMiddleware(log).GetHandler(context.Background(), metadata)
assert.EqualError(t, err2, "metadata errors: 1 error(s) decoding:\n\n* cannot parse 'AuthStyle' as int: strconv.ParseInt: parsing \"asdf\": invalid syntax")
assert.EqualError(t, err2, "metadata errors: 1 error(s) decoding:\n\n* cannot parse 'authStyle' as int: strconv.ParseInt: parsing \"asdf\": invalid syntax")
// Invalid authStyle (int > 2)
metadata.Properties["authStyle"] = "3"

View File

@ -23,6 +23,7 @@ import (
"math"
"net/http"
"net/textproto"
"reflect"
"strconv"
"strings"
"time"
@ -32,6 +33,7 @@ import (
"github.com/dapr/components-contrib/internal/httputils"
"github.com/dapr/components-contrib/internal/utils"
contribMetadata "github.com/dapr/components-contrib/metadata"
"github.com/dapr/components-contrib/middleware"
"github.com/dapr/kit/logger"
)
@ -39,11 +41,11 @@ import (
type Status int
type middlewareMetadata struct {
Rego string `json:"rego"`
DefaultStatus Status `json:"defaultStatus,omitempty"`
IncludedHeaders string `json:"includedHeaders,omitempty"`
ReadBody string `json:"readBody,omitempty"`
includedHeadersParsed []string `json:"-"`
Rego string `json:"rego" mapstructure:"rego"`
DefaultStatus Status `json:"defaultStatus,omitempty" mapstructure:"defaultStatus"`
IncludedHeaders string `json:"includedHeaders,omitempty" mapstructure:"includedHeaders"`
ReadBody string `json:"readBody,omitempty" mapstructure:"readBody"`
internalIncludedHeadersParsed []string `json:"-" mapstructure:"-"`
}
// NewMiddleware returns a new Open Policy Agent middleware.
@ -136,7 +138,7 @@ func (m *Middleware) evalRequest(w http.ResponseWriter, r *http.Request, meta *m
headers := map[string]string{}
for key, value := range r.Header {
if len(value) > 0 && slices.Contains(meta.includedHeadersParsed, key) {
if len(value) > 0 && slices.Contains(meta.internalIncludedHeadersParsed, key) {
headers[key] = strings.Join(value, ", ")
}
}
@ -232,29 +234,31 @@ func (m *Middleware) opaError(w http.ResponseWriter, meta *middlewareMetadata, e
}
func (m *Middleware) getNativeMetadata(metadata middleware.Metadata) (*middlewareMetadata, error) {
b, err := json.Marshal(metadata.Properties)
if err != nil {
return nil, err
}
meta := middlewareMetadata{
DefaultStatus: 403,
}
err = json.Unmarshal(b, &meta)
err := contribMetadata.DecodeMetadata(metadata.Properties, &meta)
if err != nil {
return nil, err
}
meta.includedHeadersParsed = strings.Split(meta.IncludedHeaders, ",")
meta.internalIncludedHeadersParsed = strings.Split(meta.IncludedHeaders, ",")
n := 0
for i := range meta.includedHeadersParsed {
scrubbed := strings.ReplaceAll(meta.includedHeadersParsed[i], " ", "")
for i := range meta.internalIncludedHeadersParsed {
scrubbed := strings.ReplaceAll(meta.internalIncludedHeadersParsed[i], " ", "")
if scrubbed != "" {
meta.includedHeadersParsed[n] = textproto.CanonicalMIMEHeaderKey(scrubbed)
meta.internalIncludedHeadersParsed[n] = textproto.CanonicalMIMEHeaderKey(scrubbed)
n++
}
}
meta.includedHeadersParsed = meta.includedHeadersParsed[:n]
meta.internalIncludedHeadersParsed = meta.internalIncludedHeadersParsed[:n]
return &meta, nil
}
func (m *Middleware) GetComponentMetadata() map[string]string {
metadataStruct := middlewareMetadata{}
metadataInfo := map[string]string{}
contribMetadata.GetMetadataInfoFromStructType(reflect.TypeOf(metadataStruct), &metadataInfo)
return metadataInfo
}

View File

@ -17,11 +17,12 @@ import (
"context"
"fmt"
"net/http"
"strconv"
"reflect"
tollbooth "github.com/didip/tollbooth/v7"
libstring "github.com/didip/tollbooth/v7/libstring"
contribMetadata "github.com/dapr/components-contrib/metadata"
"github.com/dapr/components-contrib/middleware"
"github.com/dapr/kit/logger"
)
@ -85,21 +86,24 @@ func (m *Middleware) GetHandler(_ context.Context, metadata middleware.Metadata)
}
func (m *Middleware) getNativeMetadata(metadata middleware.Metadata) (*rateLimitMiddlewareMetadata, error) {
var middlewareMetadata rateLimitMiddlewareMetadata
middlewareMetadata := rateLimitMiddlewareMetadata{
MaxRequestsPerSecond: defaultMaxRequestsPerSecond,
}
err := contribMetadata.DecodeMetadata(metadata.Properties, &middlewareMetadata)
if err != nil {
return nil, err
}
middlewareMetadata.MaxRequestsPerSecond = defaultMaxRequestsPerSecond
if val := metadata.Properties[maxRequestsPerSecondKey]; val != "" {
f, err := strconv.ParseFloat(val, 64)
if err != nil {
return nil, fmt.Errorf("error parsing metadata property %s: %w", maxRequestsPerSecondKey, err)
}
if f <= 0 {
return nil, fmt.Errorf("metadata property %s must be a positive value", maxRequestsPerSecondKey)
}
middlewareMetadata.MaxRequestsPerSecond = f
} else {
return nil, fmt.Errorf("metadata property %s must not be empty", maxRequestsPerSecondKey)
if middlewareMetadata.MaxRequestsPerSecond <= 0 {
return nil, fmt.Errorf("metadata property %s must be a positive value", maxRequestsPerSecondKey)
}
return &middlewareMetadata, nil
}
func (m *Middleware) GetComponentMetadata() map[string]string {
metadataStruct := rateLimitMiddlewareMetadata{}
metadataInfo := map[string]string{}
contribMetadata.GetMetadataInfoFromStructType(reflect.TypeOf(metadataStruct), &metadataInfo)
return metadataInfo
}

View File

@ -26,13 +26,6 @@ import (
func TestMiddlewareGetNativeMetadata(t *testing.T) {
m := &Middleware{}
t.Run(maxRequestsPerSecondKey+" is empty", func(t *testing.T) {
res, err := m.getNativeMetadata(middleware.Metadata{Base: metadata.Base{Properties: map[string]string{}}})
require.Error(t, err)
assert.ErrorContains(t, err, "metadata property "+maxRequestsPerSecondKey+" must not be empty")
assert.Nil(t, res)
})
t.Run(maxRequestsPerSecondKey+" is 0", func(t *testing.T) {
res, err := m.getNativeMetadata(middleware.Metadata{Base: metadata.Base{Properties: map[string]string{
maxRequestsPerSecondKey: "0",
@ -56,7 +49,7 @@ func TestMiddlewareGetNativeMetadata(t *testing.T) {
maxRequestsPerSecondKey: "foo-bar",
}}})
require.Error(t, err)
assert.ErrorContains(t, err, "error parsing metadata property "+maxRequestsPerSecondKey)
assert.ErrorContains(t, err, "cannot parse 'MaxRequestsPerSecond' as float")
assert.Nil(t, res)
})

View File

@ -88,3 +88,7 @@ func vars(r *http.Request) map[string]string {
}
return nil
}
func (m *Middleware) GetComponentMetadata() map[string]string {
return map[string]string{}
}

View File

@ -17,6 +17,7 @@ import (
"context"
"fmt"
"net/http"
"reflect"
"regexp"
"github.com/dapr/components-contrib/internal/httputils"
@ -27,7 +28,7 @@ import (
// Metadata is the routerchecker middleware config.
type Metadata struct {
Rule string `json:"rule"`
Rule string `json:"rule" mapstructure:"rule"`
}
// NewRouterCheckerMiddleware returns a new routerchecker middleware.
@ -72,3 +73,10 @@ func (m *Middleware) getNativeMetadata(metadata middleware.Metadata) (*Metadata,
}
return &middlewareMetadata, nil
}
func (m *Middleware) GetComponentMetadata() map[string]string {
metadataStruct := Metadata{}
metadataInfo := map[string]string{}
mdutils.GetMetadataInfoFromStructType(reflect.TypeOf(metadataStruct), &metadataInfo)
return metadataInfo
}

View File

@ -17,6 +17,7 @@ import (
"context"
"fmt"
"net/http"
"reflect"
sentinel "github.com/alibaba/sentinel-golang/api"
"github.com/alibaba/sentinel-golang/core/base"
@ -29,15 +30,15 @@ import (
)
type middlewareMetadata struct {
AppName string `json:"appName"`
AppName string `json:"appName" mapstructure:"appName"`
// LogConfig
LogDir string `json:"logDir"`
LogDir string `json:"logDir" mapstructure:"logDir"`
// Rules
FlowRules string `yaml:"flowRules"`
CircuitBreakerRules string `yaml:"circuitBreakerRules"`
HotSpotParamRules string `yaml:"hotSpotParamRules"`
IsolationRules string `yaml:"isolationRules"`
SystemRules string `yaml:"systemRules"`
FlowRules string `yaml:"flowRules" mapstructure:"flowRules"`
CircuitBreakerRules string `yaml:"circuitBreakerRules" mapstructure:"circuitBreakerRules"`
HotSpotParamRules string `yaml:"hotSpotParamRules" mapstructure:"hotSpotParamRules"`
IsolationRules string `yaml:"isolationRules" mapstructure:"isolationRules"`
SystemRules string `yaml:"systemRules" mapstructure:"systemRules"`
}
// NewMiddleware returns a new sentinel middleware.
@ -155,3 +156,10 @@ func getNativeMetadata(metadata middleware.Metadata) (*middlewareMetadata, error
}
return &md, nil
}
func (m *Middleware) GetComponentMetadata() map[string]string {
metadataStruct := middlewareMetadata{}
metadataInfo := map[string]string{}
mdutils.GetMetadataInfoFromStructType(reflect.TypeOf(metadataStruct), &metadataInfo)
return metadataInfo
}

View File

@ -4,11 +4,11 @@ import (
"bytes"
"context"
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"reflect"
"time"
"github.com/http-wasm/http-wasm-host-go/handler"
@ -18,6 +18,7 @@ import (
"github.com/http-wasm/http-wasm-host-go/api"
"github.com/tetratelabs/wazero"
mdutils "github.com/dapr/components-contrib/metadata"
dapr "github.com/dapr/components-contrib/middleware"
"github.com/dapr/kit/logger"
)
@ -31,10 +32,10 @@ import (
type middlewareMetadata struct {
// Path is where to load a `%.wasm` file that implements the guest side of
// the handler protocol. No default.
Path string `json:"path"`
Path string `json:"path" mapstructure:"path"`
// guest is WebAssembly binary implementing the waPC guest, loaded from Path.
guest []byte
guest []byte `mapstructure:"-"`
}
type middleware struct {
@ -113,13 +114,8 @@ func (m *middleware) Log(_ context.Context, level api.LogLevel, message string)
}
func (m *middleware) getMetadata(metadata dapr.Metadata) (*middlewareMetadata, error) {
b, err := json.Marshal(metadata.Properties)
if err != nil {
return nil, err
}
var data middlewareMetadata
err = json.Unmarshal(b, &data)
data := middlewareMetadata{}
err := mdutils.DecodeMetadata(metadata.Properties, &data)
if err != nil {
return nil, err
}
@ -167,3 +163,10 @@ func (rh *requestHandler) Close() error {
defer cancel()
return rh.mw.Close(ctx)
}
func (m *middleware) GetComponentMetadata() map[string]string {
metadataStruct := middlewareMetadata{}
metadataInfo := map[string]string{}
mdutils.GetMetadataInfoFromStructType(reflect.TypeOf(metadataStruct), &metadataInfo)
return metadataInfo
}

View File

@ -21,4 +21,5 @@ import (
// Middleware is the interface for a middleware.
type Middleware interface {
GetHandler(ctx context.Context, metadata Metadata) (func(next http.Handler) http.Handler, error)
GetComponentMetadata() map[string]string
}

View File

@ -17,6 +17,7 @@ import (
"context"
"errors"
"fmt"
"reflect"
"time"
"go.temporal.io/api/enums/v1"
@ -33,9 +34,9 @@ type TemporalWF struct {
}
type temporalMetadata struct {
Identity string `json:"identity"`
HostPort string `json:"hostport"`
Namespace string `json:"namespace"`
Identity string `json:"identity" mapstructure:"identity"`
HostPort string `json:"hostport" mapstructure:"hostport"`
Namespace string `json:"namespace" mapstructure:"namespace"`
}
// NewTemporalWorkflow returns a new workflow.
@ -139,6 +140,13 @@ func (c *TemporalWF) parseMetadata(meta workflows.Metadata) (*temporalMetadata,
return &m, err
}
func (c *TemporalWF) GetComponentMetadata() map[string]string {
metadataStruct := temporalMetadata{}
metadataInfo := map[string]string{}
metadata.GetMetadataInfoFromStructType(reflect.TypeOf(metadataStruct), &metadataInfo)
return metadataInfo
}
func lookupStatus(status enums.WorkflowExecutionStatus) string {
switch status {
case 0:

View File

@ -22,4 +22,5 @@ type Workflow interface {
Terminate(ctx context.Context, req *WorkflowReference) error
Get(ctx context.Context, req *WorkflowReference) (*StateResponse, error)
RaiseEvent(ctx context.Context, req *RaiseEventRequest) error
GetComponentMetadata() map[string]string
}