Skip to content

Commit

Permalink
Merge branch 'master' into mitzXnuts
Browse files Browse the repository at this point in the history
* master:
  Policy: add 'type' query parameter to /presentation_definition endpoint (#3019)
  Storage: suppress dbmate logging during unit tests (#3038)
  IAM: Expand InputDescriptorConstraintIdMap onto introspection response (#3032)
  IAM: Create session-bound user wallet when starting user OpenID4VP flow (#2991)
  Bump google.golang.org/grpc from 1.63.0 to 1.63.2 (#3037)
  Audit log StatusList2021Credential renewal against system (#3027)
  • Loading branch information
rolandgroen committed Apr 9, 2024
2 parents bdbe5ca + b856651 commit fbe44fb
Show file tree
Hide file tree
Showing 19 changed files with 844 additions and 131 deletions.
60 changes: 37 additions & 23 deletions auth/api/iam/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ const accessTokenValidity = 15 * time.Minute

const oid4vciSessionValidity = 15 * time.Minute

// userSessionCookieName is the name of the cookie used to store the user session.
// It uses the __Host prefix, that instructs the user agent to treat it as a secure cookie:
// - Must be set with the Secure attribute
// - Must be set from an HTTPS uri
// - Must not contain a Domain attribute
// - Must contain a Path attribute
// Also see:
// - https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/06-Session_Management_Testing/02-Testing_for_Cookies_Attributes
// - https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies
const userSessionCookieName = "__Host-SID"

// Wrapper handles OAuth2 flows.
type Wrapper struct {
vcr vcr.VCR
Expand Down Expand Up @@ -223,26 +234,14 @@ func (r Wrapper) IntrospectAccessToken(_ context.Context, request IntrospectAcce
iat := int(token.IssuedAt.Unix())
exp := int(token.Expiration.Unix())
response := IntrospectAccessToken200JSONResponse{
Active: true,
Iat: &iat,
Exp: &exp,
Iss: &token.Issuer,
Sub: &token.Issuer,
ClientId: &token.ClientId,
Scope: &token.Scope,
InputDescriptorConstraintIdMap: &token.InputDescriptorConstraintIdMap,
PresentationDefinition: nil,
PresentationSubmission: nil,
Vps: &token.VPToken,

// TODO: user authentication, used in OpenID4VP flow
FamilyName: nil,
Prefix: nil,
Initials: nil,
AssuranceLevel: nil,
Email: nil,
UserRole: nil,
Username: nil,
Active: true,
Iat: &iat,
Exp: &exp,
Iss: &token.Issuer,
Sub: &token.Issuer,
ClientId: &token.ClientId,
Scope: &token.Scope,
Vps: &token.VPToken,
}

// set presentation definition if in token
Expand All @@ -259,6 +258,16 @@ func (r Wrapper) IntrospectAccessToken(_ context.Context, request IntrospectAcce
log.Logger().WithError(err).Error("IntrospectAccessToken: failed to marshal presentation submission")
return IntrospectAccessToken200JSONResponse{}, err
}

if token.InputDescriptorConstraintIdMap != nil {
for _, reserved := range []string{"iss", "sub", "exp", "iat", "active", "client_id", "scope"} {
if _, exists := token.InputDescriptorConstraintIdMap[reserved]; exists {
return nil, errors.New(fmt.Sprintf("IntrospectAccessToken: InputDescriptorConstraintIdMap contains reserved claim name '%s'", reserved))
}
}
response.AdditionalProperties = token.InputDescriptorConstraintIdMap
}

return response, nil
}

Expand Down Expand Up @@ -469,11 +478,16 @@ func (r Wrapper) PresentationDefinition(ctx context.Context, request Presentatio
}
}

if _, ok := mapping[pe.WalletOwnerOrganization]; !ok {
return nil, oauthError(oauth.ServerError, "no presentation definition found for organization wallet")
walletOwnerType := pe.WalletOwnerOrganization
if request.Params.WalletOwnerType != nil {
walletOwnerType = *request.Params.WalletOwnerType
}
result, exists := mapping[walletOwnerType]
if !exists {
return nil, oauthError(oauth.InvalidRequest, fmt.Sprintf("no presentation definition found for '%s' wallet", walletOwnerType))
}

return PresentationDefinition200JSONResponse(mapping[pe.WalletOwnerOrganization]), nil
return PresentationDefinition200JSONResponse(result), nil
}

// toOwnedDIDForOAuth2 is like toOwnedDID but wraps the errors in oauth.OAuth2Error to make sure they're returned as specified by the OAuth2 RFC.
Expand Down
80 changes: 69 additions & 11 deletions auth/api/iam/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ func TestWrapper_PresentationDefinition(t *testing.T) {
webDID := did.MustParseDID("did:web:example.com:iam:123")
ctx := audit.TestContext()
walletOwnerMapping := pe.WalletOwnerMapping{pe.WalletOwnerOrganization: pe.PresentationDefinition{Id: "test"}}
userWalletType := pe.WalletOwnerUser

t.Run("ok", func(t *testing.T) {
test := newTestClient(t)
Expand All @@ -251,6 +252,33 @@ func TestWrapper_PresentationDefinition(t *testing.T) {
assert.True(t, ok)
})

t.Run("ok - user wallet", func(t *testing.T) {
walletOwnerMapping := pe.WalletOwnerMapping{pe.WalletOwnerUser: pe.PresentationDefinition{Id: "test"}}

test := newTestClient(t)
test.policy.EXPECT().PresentationDefinitions(gomock.Any(), webDID, "example-scope").Return(walletOwnerMapping, nil)
test.vdr.EXPECT().IsOwner(gomock.Any(), webDID).Return(true, nil)

response, err := test.client.PresentationDefinition(ctx, PresentationDefinitionRequestObject{Did: webDID.String(), Params: PresentationDefinitionParams{Scope: "example-scope", WalletOwnerType: &userWalletType}})

require.NoError(t, err)
require.NotNil(t, response)
_, ok := response.(PresentationDefinition200JSONResponse)
assert.True(t, ok)
})

t.Run("err - unknown wallet type", func(t *testing.T) {
test := newTestClient(t)
test.vdr.EXPECT().IsOwner(gomock.Any(), webDID).Return(true, nil)
test.policy.EXPECT().PresentationDefinitions(gomock.Any(), webDID, "example-scope").Return(walletOwnerMapping, nil)

response, err := test.client.PresentationDefinition(ctx, PresentationDefinitionRequestObject{Did: webDID.String(), Params: PresentationDefinitionParams{Scope: "example-scope", WalletOwnerType: &userWalletType}})

require.Error(t, err)
assert.Nil(t, response)
assert.Equal(t, "invalid_request - no presentation definition found for 'user' wallet", err.Error())
})

t.Run("error - unknown scope", func(t *testing.T) {
test := newTestClient(t)
test.vdr.EXPECT().IsOwner(gomock.Any(), webDID).Return(true, nil)
Expand Down Expand Up @@ -639,6 +667,36 @@ func TestWrapper_IntrospectAccessToken(t *testing.T) {
require.True(t, ok)
assert.True(t, tokenResponse.Active)
})
t.Run("with claims from InputDescriptorConstraintIdMap", func(t *testing.T) {
token := AccessToken{
Expiration: time.Now().Add(time.Second),
InputDescriptorConstraintIdMap: map[string]any{
"family_name": "Doe",
},
}
require.NoError(t, ctx.client.accessTokenServerStore().Put("token", token))

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

require.NoError(t, err)
tokenResponse, ok := res.(IntrospectAccessToken200JSONResponse)
require.True(t, ok)
assert.Equal(t, "Doe", tokenResponse.AdditionalProperties["family_name"])
})
t.Run("InputDescriptorConstraintIdMap contains reserved claim", func(t *testing.T) {
token := AccessToken{
Expiration: time.Now().Add(time.Second),
InputDescriptorConstraintIdMap: map[string]any{
"iss": "value",
},
}
require.NoError(t, ctx.client.accessTokenServerStore().Put("token", token))

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

require.EqualError(t, err, "IntrospectAccessToken: InputDescriptorConstraintIdMap contains reserved claim name 'iss'")
require.Nil(t, res)
})

t.Run(" ok - s2s flow", func(t *testing.T) {
// TODO: this should be an integration test to make sure all fields are set
Expand All @@ -663,17 +721,17 @@ func TestWrapper_IntrospectAccessToken(t *testing.T) {

require.NoError(t, ctx.client.accessTokenServerStore().Put(token.Token, token))
expectedResponse, err := json.Marshal(IntrospectAccessToken200JSONResponse{
Active: true,
ClientId: ptrTo("client"),
Exp: ptrTo(int(tNow.Add(time.Minute).Unix())),
Iat: ptrTo(int(tNow.Unix())),
Iss: ptrTo("resource-owner"),
Scope: ptrTo("test"),
Sub: ptrTo("resource-owner"),
Vps: &[]VerifiablePresentation{presentation},
InputDescriptorConstraintIdMap: ptrTo(map[string]any{"key": "value"}),
PresentationSubmission: ptrTo(map[string]interface{}{"definition_id": "", "descriptor_map": nil, "id": ""}),
PresentationDefinition: ptrTo(map[string]interface{}{"id": "", "input_descriptors": nil}),
Active: true,
ClientId: ptrTo("client"),
Exp: ptrTo(int(tNow.Add(time.Minute).Unix())),
Iat: ptrTo(int(tNow.Unix())),
Iss: ptrTo("resource-owner"),
Scope: ptrTo("test"),
Sub: ptrTo("resource-owner"),
Vps: &[]VerifiablePresentation{presentation},
PresentationSubmission: ptrTo(map[string]interface{}{"definition_id": "", "descriptor_map": nil, "id": ""}),
PresentationDefinition: ptrTo(map[string]interface{}{"id": "", "input_descriptors": nil}),
AdditionalProperties: map[string]interface{}{"key": "value"},
})
require.NoError(t, err)

Expand Down
Loading

0 comments on commit fbe44fb

Please sign in to comment.