Skip to content

Commit

Permalink
Merge pull request #271 from xmidt-org/feature/allow-source-in-valida…
Browse files Browse the repository at this point in the history
…tors

Feature/allow source in validators
  • Loading branch information
johnabass authored Jul 30, 2024
2 parents af3693f + 3336041 commit 5a5aa08
Show file tree
Hide file tree
Showing 9 changed files with 623 additions and 129 deletions.
28 changes: 13 additions & 15 deletions authorizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,34 @@ type Authorizer[R any] interface {
// cancelation semantics.
//
// If this method doesn't support the given token, it should return nil.
Authorize(ctx context.Context, token Token, resource R) error
Authorize(ctx context.Context, resource R, token Token) error
}

// AuthorizerFunc is a closure type that implements Authorizer.
type AuthorizerFunc[R any] func(context.Context, Token, R) error
type AuthorizerFunc[R any] func(context.Context, R, Token) error

func (af AuthorizerFunc[R]) Authorize(ctx context.Context, token Token, resource R) error {
return af(ctx, token, resource)
func (af AuthorizerFunc[R]) Authorize(ctx context.Context, resource R, token Token) error {
return af(ctx, resource, token)
}

// Authorizers is a collection of Authorizers.
type Authorizers[R any] []Authorizer[R]

// Add appends authorizers to this aggregate Authorizers.
func (as *Authorizers[R]) Add(a ...Authorizer[R]) {
if *as == nil {
*as = make(Authorizers[R], 0, len(a))
}

*as = append(*as, a...)
// Append tacks on one or more authorizers to this collection. The possibly
// new Authorizers instance is returned. The semantics of this method are
// the same as the built-in append.
func (as Authorizers[R]) Append(a ...Authorizer[R]) Authorizers[R] {
return append(as, a...)
}

// Authorize requires all authorizers in this sequence to allow access. This
// method supplies a logical AND.
//
// Because authorization can be arbitrarily expensive, execution halts at the first failed
// authorization attempt.
func (as Authorizers[R]) Authorize(ctx context.Context, token Token, resource R) error {
func (as Authorizers[R]) Authorize(ctx context.Context, resource R, token Token) error {
for _, a := range as {
if err := a.Authorize(ctx, token, resource); err != nil {
if err := a.Authorize(ctx, resource, token); err != nil {
return err
}
}
Expand All @@ -61,10 +59,10 @@ type requireAny[R any] struct {

// Authorize returns nil at the first authorizer that returns nil, i.e. accepts the access.
// Otherwise, this method returns an aggregate error of all the authorization errors.
func (ra requireAny[R]) Authorize(ctx context.Context, token Token, resource R) error {
func (ra requireAny[R]) Authorize(ctx context.Context, resource R, token Token) error {
var err error
for _, a := range ra.a {
authErr := a.Authorize(ctx, token, resource)
authErr := a.Authorize(ctx, resource, token)
if authErr == nil {
return nil
}
Expand Down
20 changes: 10 additions & 10 deletions authorizer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ func (suite *AuthorizersTestSuite) TestAuthorize() {

for _, err := range testCase.results {
err := err
as.Add(
AuthorizerFunc[string](func(ctx context.Context, token Token, resource string) error {
as = as.Append(
AuthorizerFunc[string](func(ctx context.Context, resource string, token Token) error {
suite.Same(testCtx, ctx)
suite.Same(testToken, token)
suite.Equal(testToken, token)
suite.Equal(placeholderResource, resource)
return err
}),
Expand All @@ -75,7 +75,7 @@ func (suite *AuthorizersTestSuite) TestAuthorize() {

suite.Equal(
testCase.expectedErr,
as.Authorize(testCtx, testToken, placeholderResource),
as.Authorize(testCtx, placeholderResource, testToken),
)
})
}
Expand Down Expand Up @@ -123,10 +123,10 @@ func (suite *AuthorizersTestSuite) TestAny() {

for _, err := range testCase.results {
err := err
as.Add(
AuthorizerFunc[string](func(ctx context.Context, token Token, resource string) error {
as = as.Append(
AuthorizerFunc[string](func(ctx context.Context, resource string, token Token) error {
suite.Same(testCtx, ctx)
suite.Same(testToken, token)
suite.Equal(testToken, token)
suite.Equal(placeholderResource, resource)
return err
}),
Expand All @@ -136,21 +136,21 @@ func (suite *AuthorizersTestSuite) TestAny() {
anyAs := as.Any()
suite.Equal(
testCase.expectedErr,
anyAs.Authorize(testCtx, testToken, placeholderResource),
anyAs.Authorize(testCtx, placeholderResource, testToken),
)

if len(as) > 0 {
// the any instance should be distinct
as[0] = AuthorizerFunc[string](
func(context.Context, Token, string) error {
func(context.Context, string, Token) error {
suite.Fail("should not have been called")
return nil
},
)

suite.Equal(
testCase.expectedErr,
anyAs.Authorize(testCtx, testToken, placeholderResource),
anyAs.Authorize(testCtx, placeholderResource, testToken),
)
}
})
Expand Down
16 changes: 8 additions & 8 deletions basculehttp/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ func WithTokenParser(scheme bascule.Scheme, tp bascule.TokenParser[*http.Request
// WithAuthentication adds validators used for authentication to this Middleware. Each
// invocation of this option is cumulative. Authentication validators are run in the order
// supplied by this option.
func WithAuthentication(v ...bascule.Validator) MiddlewareOption {
func WithAuthentication(v ...bascule.Validator[*http.Request]) MiddlewareOption {
return middlewareOptionFunc(func(m *Middleware) error {
m.authentication.Add(v...)
m.authentication = m.authentication.Append(v...)
return nil
})
}
Expand All @@ -77,7 +77,7 @@ func WithChallenges(ch ...Challenge) MiddlewareOption {
// This is useful for use cases like admin access or alternate capabilities.
func WithAuthorization(a ...bascule.Authorizer[*http.Request]) MiddlewareOption {
return middlewareOptionFunc(func(m *Middleware) error {
m.authorization.Add(a...)
m.authorization = m.authorization.Append(a...)
return nil
})
}
Expand Down Expand Up @@ -114,7 +114,7 @@ func WithErrorMarshaler(em ErrorMarshaler) MiddlewareOption {
type Middleware struct {
credentialsParser bascule.CredentialsParser[*http.Request]
tokenParsers bascule.TokenParsers[*http.Request]
authentication bascule.Validators
authentication bascule.Validators[*http.Request]
authorization bascule.Authorizers[*http.Request]
challenges Challenges

Expand Down Expand Up @@ -194,12 +194,12 @@ func (m *Middleware) getCredentialsAndToken(ctx context.Context, request *http.R
return
}

func (m *Middleware) authenticate(ctx context.Context, token bascule.Token) error {
return m.authentication.Validate(ctx, token)
func (m *Middleware) authenticate(ctx context.Context, request *http.Request, token bascule.Token) (bascule.Token, error) {
return m.authentication.Validate(ctx, request, token)
}

func (m *Middleware) authorize(ctx context.Context, token bascule.Token, request *http.Request) error {
return m.authorization.Authorize(ctx, token, request)
return m.authorization.Authorize(ctx, request, token)
}

// frontDoor is the internal handler implementation that protects a handler
Expand All @@ -220,7 +220,7 @@ func (fd *frontDoor) ServeHTTP(response http.ResponseWriter, request *http.Reque
}

ctx = bascule.WithCredentials(ctx, creds)
err = fd.middleware.authenticate(ctx, token)
token, err = fd.middleware.authenticate(ctx, request, token)
if err != nil {
// at this point in the workflow, the request has valid credentials. we use
// StatusForbidden as the default because any failure to authenticate isn't a
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/sys v0.22.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncj
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
Expand Down
36 changes: 36 additions & 0 deletions mocks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2020 Comcast Cable Communications Management, LLC
// SPDX-License-Identifier: Apache-2.0

package bascule

import (
"context"

"github.com/stretchr/testify/mock"
)

type testToken string

func (tt testToken) Principal() string { return string(tt) }

type mockValidator[S any] struct {
mock.Mock
}

func (m *mockValidator[S]) Validate(ctx context.Context, source S, token Token) (Token, error) {
args := m.Called(ctx, source, token)
t, _ := args.Get(0).(Token)
return t, args.Error(1)
}

func (m *mockValidator[S]) ExpectValidate(ctx context.Context, source S, token Token) *mock.Call {
return m.On("Validate", ctx, source, token)
}

func assertValidators[S any](t mock.TestingT, vs ...Validator[S]) (passed bool) {
for _, v := range vs {
passed = v.(*mockValidator[S]).AssertExpectations(t) && passed
}

return
}
12 changes: 1 addition & 11 deletions testSuite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,6 @@ const (
testScheme Scheme = "Test"
)

type testToken struct {
principal string
}

func (tt *testToken) Principal() string {
return tt.principal
}

// TestSuite holds generally useful functionality for testing bascule.
type TestSuite struct {
suite.Suite
Expand All @@ -43,9 +35,7 @@ func (suite *TestSuite) testCredentials() Credentials {
}

func (suite *TestSuite) testToken() Token {
return &testToken{
principal: "test",
}
return testToken("test")
}

func (suite *TestSuite) contexter(ctx context.Context) Contexter {
Expand Down
Loading

0 comments on commit 5a5aa08

Please sign in to comment.