From 1098a834fb41102b0ba5c8b9c6020955f2af0c25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Wed, 29 Jan 2025 16:22:41 +0100 Subject: [PATCH] bearer: add some tests --- toolkit/client/proof_test.go | 9 ++- toolkit/server/bearer/bearer_test.go | 32 ++++++++++ toolkit/server/exectx/middleware.go | 43 -------------- toolkit/server/exectx/middlewares.go | 87 ++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 45 deletions(-) create mode 100644 toolkit/server/bearer/bearer_test.go delete mode 100644 toolkit/server/exectx/middleware.go create mode 100644 toolkit/server/exectx/middlewares.go diff --git a/toolkit/client/proof_test.go b/toolkit/client/proof_test.go index 5f0738c..cfca2c2 100644 --- a/toolkit/client/proof_test.go +++ b/toolkit/client/proof_test.go @@ -2,7 +2,6 @@ package client import ( "iter" - "slices" "testing" "github.com/stretchr/testify/require" @@ -14,7 +13,13 @@ import ( func TestFindProof(t *testing.T) { dlgs := func() iter.Seq[*delegation.Bundle] { - return slices.Values(delegationtest.AllBundles) + return func(yield func(*delegation.Bundle) bool) { + for _, bundle := range delegationtest.AllBundles { + if !yield(&bundle) { + return + } + } + } } require.Equal(t, delegationtest.ProofAliceBob, diff --git a/toolkit/server/bearer/bearer_test.go b/toolkit/server/bearer/bearer_test.go new file mode 100644 index 0000000..0070159 --- /dev/null +++ b/toolkit/server/bearer/bearer_test.go @@ -0,0 +1,32 @@ +package bearer + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/require" + "github.com/ucan-wg/go-ucan/pkg/container" + "github.com/ucan-wg/go-ucan/pkg/container/containertest" +) + +func TestHTTPBearer(t *testing.T) { + for _, fn := range []func(h http.Header, container container.Writer) error{ + AddBearerContainer, + AddBearerContainerCompressed, + } { + r, err := http.NewRequest(http.MethodPost, "/foo/bar", nil) + require.NoError(t, err) + + cont, err := container.FromBytes(containertest.Bytes) + require.NoError(t, err) + + err = fn(r.Header, cont.ToWriter()) + require.NoError(t, err) + + contRead, err := ExtractBearerContainer(r.Header) + require.NoError(t, err) + + require.NotEmpty(t, contRead) + require.Equal(t, cont, contRead) + } +} diff --git a/toolkit/server/exectx/middleware.go b/toolkit/server/exectx/middleware.go deleted file mode 100644 index 8b5dd2f..0000000 --- a/toolkit/server/exectx/middleware.go +++ /dev/null @@ -1,43 +0,0 @@ -package exectx - -import ( - "errors" - "net/http" - - "github.com/INFURA/go-ucan-toolkit/server/bearer" -) - -// Middleware returns an HTTP middleware tasked with: -// - extracting UCAN credentials from the `Authorization: Bearer ` HTTP header -// - performing basic checks, and returning HTTP errors if necessary -// - exposing those credentials in the go context as a UcanCtx for further usage -func Middleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctn, err := bearer.ExtractBearerContainer(r.Header) - if errors.Is(err, bearer.ErrNoUcan) { - http.Error(w, "no UCAN auth", http.StatusBadRequest) - return - } - if errors.Is(err, bearer.ErrContainerMalformed) { - http.Error(w, "malformed token", http.StatusBadRequest) - return - } - if err != nil { - // should not happen, defensive programming - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // prepare a UcanCtx from the container, for further evaluation in the server pipeline - ucanCtx, err := FromContainer(ctn) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // insert into the go context - r = r.WithContext(AddUcanCtxToContext(r.Context(), ucanCtx)) - - next.ServeHTTP(w, r) - }) -} diff --git a/toolkit/server/exectx/middlewares.go b/toolkit/server/exectx/middlewares.go new file mode 100644 index 0000000..c2d96c4 --- /dev/null +++ b/toolkit/server/exectx/middlewares.go @@ -0,0 +1,87 @@ +package exectx + +import ( + "errors" + "net/http" + + "github.com/ucan-wg/go-ucan/did" + + "github.com/INFURA/go-ucan-toolkit/server/bearer" +) + +// ExtractMW returns an HTTP middleware tasked with: +// - extracting UCAN credentials from the `Authorization: Bearer ` HTTP header +// - performing basic checks, and returning HTTP errors if necessary +// - verify that the invocation targets our service +// - exposing those credentials in the go context as a UcanCtx for further usage +func ExtractMW(next http.Handler, serviceDID did.DID) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctn, err := bearer.ExtractBearerContainer(r.Header) + if errors.Is(err, bearer.ErrNoUcan) { + http.Error(w, "no UCAN auth", http.StatusBadRequest) + return + } + if errors.Is(err, bearer.ErrContainerMalformed) { + http.Error(w, "malformed token", http.StatusBadRequest) + return + } + if err != nil { + // should not happen, defensive programming + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // prepare a UcanCtx from the container, for further evaluation in the server pipeline + ucanCtx, err := FromContainer(ctn) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if ucanCtx.Invocation().Subject() != serviceDID { + http.Error(w, "UCAN delegation doesn't match the service DID", http.StatusUnauthorized) + return + } + + // insert into the go context + r = r.WithContext(AddUcanCtxToContext(r.Context(), ucanCtx)) + + next.ServeHTTP(w, r) + }) +} + +// HttpExtArgsVerify returns an HTTP middleware tasked with verifying the UCAN policies applying on the HTTP request. +func HttpExtArgsVerify(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ucanCtx, ok := FromContext(r.Context()) + if !ok { + http.Error(w, "no ucan-ctx found", http.StatusInternalServerError) + return + } + + if err := ucanCtx.VerifyHttp(r); err != nil { + http.Error(w, err.Error(), http.StatusForbidden) + return + } + + next.ServeHTTP(w, r) + }) +} + +// EnforceMW returns an HTTP middleware tasked with the final verification of the UCAN policies. +func EnforceMW(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ucanCtx, ok := FromContext(r.Context()) + if !ok { + http.Error(w, "no ucan-ctx found", http.StatusInternalServerError) + return + } + + if err := ucanCtx.ExecutionAllowed(); err != nil { + http.Error(w, err.Error(), http.StatusForbidden) + return + } + + next.ServeHTTP(w, r) + }) +}