-
Notifications
You must be signed in to change notification settings - Fork 91
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add HTTP authentication based on the "Accept" header
It may be desirable in certain cases to only perform OpenID Connect based authentication in case users visit a page through the web browser. For non-interactive API access it may be desirable to return HTTP 401 instead. This change solves this by adding a decorator for Authenticator that can conditionalize its invocation based on whether a supported media type is provided.
- Loading branch information
1 parent
ee3fc0b
commit 02a681e
Showing
7 changed files
with
311 additions
and
67 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
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,38 @@ | ||
package http | ||
|
||
import ( | ||
"net/http" | ||
|
||
"github.com/aohorodnyk/mimeheader" | ||
"github.com/buildbarn/bb-storage/pkg/auth" | ||
|
||
"google.golang.org/grpc/codes" | ||
"google.golang.org/grpc/status" | ||
) | ||
|
||
type acceptHeaderAuthenticator struct { | ||
base Authenticator | ||
mediaTypes []string | ||
mismatchErr error | ||
} | ||
|
||
// NewAcceptHeaderAuthenticator creates a decorator for Authenticator | ||
// that only performs authentication if the HTTP request's "Accept" header | ||
// contains a matching media type. This can, for example, be used to | ||
// limit OpenID Connect authentication to requests originating from a | ||
// web browser. | ||
func NewAcceptHeaderAuthenticator(base Authenticator, mediaTypes []string) Authenticator { | ||
return &acceptHeaderAuthenticator{ | ||
base: base, | ||
mediaTypes: mediaTypes, | ||
mismatchErr: status.Errorf(codes.Unauthenticated, "Client does not accept media types %v", mediaTypes), | ||
} | ||
} | ||
|
||
func (a *acceptHeaderAuthenticator) Authenticate(w http.ResponseWriter, r *http.Request) (*auth.AuthenticationMetadata, error) { | ||
acceptHeader := mimeheader.ParseAcceptHeader(r.Header.Get("Accept")) | ||
if _, _, ok := acceptHeader.Negotiate(a.mediaTypes, ""); ok { | ||
return a.base.Authenticate(w, r) | ||
} | ||
return nil, a.mismatchErr | ||
} |
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,70 @@ | ||
package http_test | ||
|
||
import ( | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/buildbarn/bb-storage/internal/mock" | ||
"github.com/buildbarn/bb-storage/pkg/auth" | ||
bb_http "github.com/buildbarn/bb-storage/pkg/http" | ||
auth_pb "github.com/buildbarn/bb-storage/pkg/proto/auth" | ||
"github.com/buildbarn/bb-storage/pkg/testutil" | ||
"github.com/golang/mock/gomock" | ||
"github.com/stretchr/testify/require" | ||
|
||
"google.golang.org/grpc/codes" | ||
"google.golang.org/grpc/status" | ||
"google.golang.org/protobuf/types/known/structpb" | ||
) | ||
|
||
func TestAcceptHeaderAuthenticator(t *testing.T) { | ||
ctrl := gomock.NewController(t) | ||
|
||
baseAuthenticator := mock.NewMockHTTPAuthenticator(ctrl) | ||
authenticator := bb_http.NewAcceptHeaderAuthenticator(baseAuthenticator, []string{"text/html", "font/*"}) | ||
|
||
t.Run("MissingHeader", func(t *testing.T) { | ||
w := mock.NewMockResponseWriter(ctrl) | ||
r, err := http.NewRequest(http.MethodGet, "/path", nil) | ||
require.NoError(t, err) | ||
|
||
_, err = authenticator.Authenticate(w, r) | ||
testutil.RequireEqualStatus(t, status.Error(codes.Unauthenticated, "Client does not accept media types [text/html font/*]"), err) | ||
}) | ||
|
||
t.Run("MismatchingHeader", func(t *testing.T) { | ||
w := mock.NewMockResponseWriter(ctrl) | ||
r, err := http.NewRequest(http.MethodGet, "/path", nil) | ||
require.NoError(t, err) | ||
r.Header.Set("Accept", "application/xml") | ||
|
||
_, err = authenticator.Authenticate(w, r) | ||
testutil.RequireEqualStatus(t, status.Error(codes.Unauthenticated, "Client does not accept media types [text/html font/*]"), err) | ||
}) | ||
|
||
t.Run("MatchingHeader", func(t *testing.T) { | ||
for _, header := range []string{ | ||
"font/otf", | ||
"text/*", | ||
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", | ||
} { | ||
w := mock.NewMockResponseWriter(ctrl) | ||
r, err := http.NewRequest(http.MethodGet, "/path", nil) | ||
require.NoError(t, err) | ||
r.Header.Set("Accept", header) | ||
|
||
expectedMetadata := auth.MustNewAuthenticationMetadataFromProto(&auth_pb.AuthenticationMetadata{ | ||
Public: structpb.NewStructValue(&structpb.Struct{ | ||
Fields: map[string]*structpb.Value{ | ||
"username": structpb.NewStringValue("John Doe"), | ||
}, | ||
}), | ||
}) | ||
baseAuthenticator.EXPECT().Authenticate(w, r).Return(expectedMetadata, nil) | ||
|
||
actualMetadata, err := authenticator.Authenticate(w, r) | ||
require.NoError(t, err) | ||
require.Equal(t, expectedMetadata, actualMetadata) | ||
} | ||
}) | ||
} |
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
Oops, something went wrong.