Skip to content

Commit

Permalink
introspection request returns error if missing a token (#3465)
Browse files Browse the repository at this point in the history
* return 415 if using the wrong Content-Type
  • Loading branch information
gerardsn authored Oct 8, 2024
1 parent d2df24f commit b887163
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 12 deletions.
15 changes: 12 additions & 3 deletions auth/api/iam/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"html/template"
"net/http"
"net/url"
"slices"
"strings"
"time"

Expand Down Expand Up @@ -335,7 +336,11 @@ func (r Wrapper) RetrieveAccessToken(_ context.Context, request RetrieveAccessTo
}

// IntrospectAccessToken allows the resource server (XIS/EHR) to introspect details of an access token issued by this node
func (r Wrapper) IntrospectAccessToken(_ context.Context, request IntrospectAccessTokenRequestObject) (IntrospectAccessTokenResponseObject, error) {
func (r Wrapper) IntrospectAccessToken(ctx context.Context, request IntrospectAccessTokenRequestObject) (IntrospectAccessTokenResponseObject, error) {
headers := ctx.Value(httpRequestContextKey{}).(*http.Request).Header
if !slices.Contains(headers["Content-Type"], "application/x-www-form-urlencoded") {
return nil, core.Error(http.StatusUnsupportedMediaType, "Content-Type MUST be set to application/x-www-form-urlencoded")
}
input := request.Body.Token
response, err := r.introspectAccessToken(input)
if err != nil {
Expand All @@ -351,7 +356,11 @@ func (r Wrapper) IntrospectAccessToken(_ context.Context, request IntrospectAcce

// IntrospectAccessTokenExtended allows the resource server (XIS/EHR) to introspect details of an access token issued by this node.
// It returns the same information as IntrospectAccessToken, but with additional information.
func (r Wrapper) IntrospectAccessTokenExtended(_ context.Context, request IntrospectAccessTokenExtendedRequestObject) (IntrospectAccessTokenExtendedResponseObject, error) {
func (r Wrapper) IntrospectAccessTokenExtended(ctx context.Context, request IntrospectAccessTokenExtendedRequestObject) (IntrospectAccessTokenExtendedResponseObject, error) {
headers := ctx.Value(httpRequestContextKey{}).(*http.Request).Header
if !slices.Contains(headers["Content-Type"], "application/x-www-form-urlencoded") {
return nil, core.Error(http.StatusUnsupportedMediaType, "Content-Type MUST be set to application/x-www-form-urlencoded")
}
input := request.Body.Token
response, err := r.introspectAccessToken(input)
if err != nil {
Expand All @@ -366,7 +375,7 @@ func (r Wrapper) introspectAccessToken(input string) (*ExtendedTokenIntrospectio
// Validate token
if input == "" {
// Return 200 + 'Active = false' when token is invalid or malformed
log.Logger().Debug("IntrospectAccessToken: missing token")
log.Logger().Warn("IntrospectAccessToken: missing token")
return nil, nil
}

Expand Down
34 changes: 25 additions & 9 deletions auth/api/iam/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -626,29 +626,32 @@ func TestWrapper_IntrospectAccessToken(t *testing.T) {
ctx := newTestClient(t)
dpopToken, _, thumbprint := newSignedTestDPoP()

req := http.Request{Header: map[string][]string{"Content-Type": {"application/x-www-form-urlencoded"}}}
reqCtx := context.WithValue(context.Background(), httpRequestContextKey{}, &req)

// validate all fields are there after introspection
t.Run("error - no token provided", func(t *testing.T) {
res, err := ctx.client.IntrospectAccessToken(context.Background(), IntrospectAccessTokenRequestObject{Body: &TokenIntrospectionRequest{Token: ""}})
res, err := ctx.client.IntrospectAccessToken(reqCtx, IntrospectAccessTokenRequestObject{Body: &TokenIntrospectionRequest{Token: ""}})
require.NoError(t, err)
assert.Equal(t, res, IntrospectAccessToken200JSONResponse{})
})
t.Run("error - other store error", func(t *testing.T) {
// token is invalid JSON
require.NoError(t, ctx.client.accessTokenServerStore().Put("err", "{"))
res, err := ctx.client.IntrospectAccessToken(context.Background(), IntrospectAccessTokenRequestObject{Body: &TokenIntrospectionRequest{Token: "err"}})
res, err := ctx.client.IntrospectAccessToken(reqCtx, IntrospectAccessTokenRequestObject{Body: &TokenIntrospectionRequest{Token: "err"}})
assert.ErrorContains(t, err, "json: cannot unmarshal")
assert.Nil(t, res)
})
t.Run("error - does not exist", func(t *testing.T) {
res, err := ctx.client.IntrospectAccessToken(context.Background(), IntrospectAccessTokenRequestObject{Body: &TokenIntrospectionRequest{Token: "does not exist"}})
res, err := ctx.client.IntrospectAccessToken(reqCtx, IntrospectAccessTokenRequestObject{Body: &TokenIntrospectionRequest{Token: "does not exist"}})
require.NoError(t, err)
assert.Equal(t, res, IntrospectAccessToken200JSONResponse{})
})
t.Run("error - expired token", func(t *testing.T) {
token := AccessToken{Expiration: time.Now().Add(-time.Second)}
require.NoError(t, ctx.client.accessTokenServerStore().Put("token", token))

res, err := ctx.client.IntrospectAccessToken(context.Background(), IntrospectAccessTokenRequestObject{Body: &TokenIntrospectionRequest{Token: "token"}})
res, err := ctx.client.IntrospectAccessToken(reqCtx, IntrospectAccessTokenRequestObject{Body: &TokenIntrospectionRequest{Token: "token"}})

require.NoError(t, err)
assert.Equal(t, res, IntrospectAccessToken200JSONResponse{})
Expand All @@ -670,7 +673,7 @@ func TestWrapper_IntrospectAccessToken(t *testing.T) {
token := okToken
require.NoError(t, ctx.client.accessTokenServerStore().Put("token", token))

res, err := ctx.client.IntrospectAccessToken(context.Background(), IntrospectAccessTokenRequestObject{Body: &TokenIntrospectionRequest{Token: "token"}})
res, err := ctx.client.IntrospectAccessToken(reqCtx, IntrospectAccessTokenRequestObject{Body: &TokenIntrospectionRequest{Token: "token"}})

require.NoError(t, err)
tokenResponse, ok := res.(IntrospectAccessToken200JSONResponse)
Expand All @@ -684,7 +687,7 @@ func TestWrapper_IntrospectAccessToken(t *testing.T) {
token := okToken
require.NoError(t, ctx.client.accessTokenServerStore().Put("token", token))

res, err := ctx.client.IntrospectAccessTokenExtended(context.Background(), IntrospectAccessTokenExtendedRequestObject{Body: &TokenIntrospectionRequest{Token: "token"}})
res, err := ctx.client.IntrospectAccessTokenExtended(reqCtx, IntrospectAccessTokenExtendedRequestObject{Body: &TokenIntrospectionRequest{Token: "token"}})

require.NoError(t, err)
tokenResponse, ok := res.(IntrospectAccessTokenExtended200JSONResponse)
Expand All @@ -703,7 +706,7 @@ func TestWrapper_IntrospectAccessToken(t *testing.T) {
}
require.NoError(t, ctx.client.accessTokenServerStore().Put("token", token))

res, err := ctx.client.IntrospectAccessToken(context.Background(), IntrospectAccessTokenRequestObject{Body: &TokenIntrospectionRequest{Token: "token"}})
res, err := ctx.client.IntrospectAccessToken(reqCtx, IntrospectAccessTokenRequestObject{Body: &TokenIntrospectionRequest{Token: "token"}})

require.NoError(t, err)
tokenResponse, ok := res.(IntrospectAccessToken200JSONResponse)
Expand All @@ -719,7 +722,7 @@ func TestWrapper_IntrospectAccessToken(t *testing.T) {
}
require.NoError(t, ctx.client.accessTokenServerStore().Put("token", token))

res, err := ctx.client.IntrospectAccessToken(context.Background(), IntrospectAccessTokenRequestObject{Body: &TokenIntrospectionRequest{Token: "token"}})
res, err := ctx.client.IntrospectAccessToken(reqCtx, IntrospectAccessTokenRequestObject{Body: &TokenIntrospectionRequest{Token: "token"}})

require.EqualError(t, err, "IntrospectAccessToken: InputDescriptorConstraintIdMap contains reserved claim name: iss")
require.Nil(t, res)
Expand Down Expand Up @@ -767,13 +770,26 @@ func TestWrapper_IntrospectAccessToken(t *testing.T) {
})
require.NoError(t, err)

res, err := ctx.client.IntrospectAccessTokenExtended(context.Background(), IntrospectAccessTokenExtendedRequestObject{Body: &TokenIntrospectionRequest{Token: token.Token}})
res, err := ctx.client.IntrospectAccessTokenExtended(reqCtx, IntrospectAccessTokenExtendedRequestObject{Body: &TokenIntrospectionRequest{Token: token.Token}})

require.NoError(t, err)
tokenResponse, err := json.Marshal(res)
assert.NoError(t, err)
assert.JSONEq(t, string(expectedResponse), string(tokenResponse))
})
t.Run("error - wrong Content-Type header", func(t *testing.T) {
req := http.Request{Header: map[string][]string{"Content-Type": {"something-else"}}}
reqCtx := context.WithValue(context.Background(), httpRequestContextKey{}, &req)
expectedErr := core.Error(http.StatusUnsupportedMediaType, "Content-Type MUST be set to application/x-www-form-urlencoded")

res, err := ctx.client.IntrospectAccessToken(reqCtx, IntrospectAccessTokenRequestObject{Body: &TokenIntrospectionRequest{Token: "not-empty"}})
assert.ErrorIs(t, err, expectedErr)
assert.Nil(t, res)

resExt, err := ctx.client.IntrospectAccessTokenExtended(reqCtx, IntrospectAccessTokenExtendedRequestObject{Body: &TokenIntrospectionRequest{Token: "not-empty"}})
assert.ErrorIs(t, err, expectedErr)
assert.Nil(t, resExt)
})
}

func TestWrapper_Routes(t *testing.T) {
Expand Down

0 comments on commit b887163

Please sign in to comment.