Skip to content

Commit

Permalink
VCR: remove credentials based on subject (#3474)
Browse files Browse the repository at this point in the history
  • Loading branch information
gerardsn authored Oct 11, 2024
1 parent 3590c1e commit 869308b
Show file tree
Hide file tree
Showing 4 changed files with 240 additions and 197 deletions.
14 changes: 7 additions & 7 deletions docs/_static/vcr/vcr_v2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -530,17 +530,17 @@ paths:
description: The credential will not be altered in any way, so no need to return it.
default:
$ref: '../common/error_response.yaml'
/internal/vcr/v2/holder/{did}/vc/{id}:
/internal/vcr/v2/holder/{subjectID}/vc/{id}:
parameters:
- name: did
- name: subjectID
in: path
description: URL encoded DID.
description: Subject ID of the wallet owner at this node.
required: true
content:
plain/text:
schema:
type: string
example: did:web:example.com
example: 90BC1AE9-752B-432F-ADC3-DD9F9C61843C
- name: id
in: path
description: URL encoded VC ID.
Expand All @@ -552,15 +552,15 @@ paths:
description: |
Remove a VerifiableCredential from the holders wallet. After removal the holder can't present the credential any more.
It does not revoke the credential or inform the credential issuer that the wallet removed the wallet.
error returns:
* 400 - Invalid credential
* 404 - Credential not found
* 404 - Credential or subject not found
* 500 - An error occurred while processing the request
operationId: removeCredentialFromWallet
tags:
- credential
- credential
responses:
"204":
description: Credential has been removed from the wallet.
Expand Down
41 changes: 26 additions & 15 deletions vcr/api/vcr/v2/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,6 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/nuts-foundation/nuts-node/audit"
"github.com/nuts-foundation/nuts-node/jsonld"
"github.com/nuts-foundation/nuts-node/vcr/credential"
"github.com/nuts-foundation/nuts-node/vcr/holder"
"github.com/nuts-foundation/nuts-node/vcr/issuer"
vcrTypes "github.com/nuts-foundation/nuts-node/vcr/types"
"github.com/nuts-foundation/nuts-node/vcr/verifier"
"github.com/nuts-foundation/nuts-node/vdr/didsubject"
"github.com/nuts-foundation/nuts-node/vdr/resolver"
"net/http"
"strings"
"time"
Expand All @@ -40,9 +31,18 @@ import (
ssi "github.com/nuts-foundation/go-did"
"github.com/nuts-foundation/go-did/did"
"github.com/nuts-foundation/go-did/vc"
"github.com/nuts-foundation/nuts-node/audit"
"github.com/nuts-foundation/nuts-node/core"
"github.com/nuts-foundation/nuts-node/jsonld"
"github.com/nuts-foundation/nuts-node/vcr"
"github.com/nuts-foundation/nuts-node/vcr/credential"
"github.com/nuts-foundation/nuts-node/vcr/holder"
"github.com/nuts-foundation/nuts-node/vcr/issuer"
"github.com/nuts-foundation/nuts-node/vcr/signature/proof"
vcrTypes "github.com/nuts-foundation/nuts-node/vcr/types"
"github.com/nuts-foundation/nuts-node/vcr/verifier"
"github.com/nuts-foundation/nuts-node/vdr/didsubject"
"github.com/nuts-foundation/nuts-node/vdr/resolver"
)

var clockFn = func() time.Time {
Expand Down Expand Up @@ -461,20 +461,31 @@ func (w *Wrapper) GetCredentialsInWallet(ctx context.Context, request GetCredent
}

func (w *Wrapper) RemoveCredentialFromWallet(ctx context.Context, request RemoveCredentialFromWalletRequestObject) (RemoveCredentialFromWalletResponseObject, error) {
holderDID, err := did.ParseDID(request.Did)
// get DIDs for holder
dids, err := w.SubjectManager.ListDIDs(ctx, request.SubjectID)
if err != nil {
return nil, core.InvalidInputError("invalid holder DID: %w", err)
return nil, err
}
credentialID, err := ssi.ParseURI(request.Id)
if err != nil {
return nil, core.InvalidInputError("invalid credential ID: %w", err)
}
err = w.VCR.Wallet().Remove(ctx, *holderDID, *credentialID)
if err != nil {
return nil, err
var deleted bool
for _, subjectDID := range dids {
err = w.VCR.Wallet().Remove(ctx, subjectDID, *credentialID)
if err != nil {
if errors.Is(err, vcrTypes.ErrNotFound) {
// only return vcrTypes.ErrNotFound if true for all subjectDIDs (deleted=false)
continue
}
return nil, err
}
deleted = true
}
if !deleted {
return nil, vcrTypes.ErrNotFound
}
return RemoveCredentialFromWallet204Response{}, nil

}

// TrustIssuer handles API request to start trusting an issuer of a Verifiable Credential.
Expand Down
52 changes: 42 additions & 10 deletions vcr/api/vcr/v2/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/nuts-foundation/nuts-node/vdr/didsubject"
"github.com/nuts-foundation/nuts-node/vdr/resolver"
"github.com/nuts-foundation/nuts-node/vcr/types"
"net/http"
"testing"
"time"
Expand All @@ -41,6 +40,8 @@ import (
"github.com/nuts-foundation/nuts-node/vcr/issuer"
"github.com/nuts-foundation/nuts-node/vcr/signature/proof"
"github.com/nuts-foundation/nuts-node/vcr/verifier"
"github.com/nuts-foundation/nuts-node/vdr/didsubject"
"github.com/nuts-foundation/nuts-node/vdr/resolver"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
Expand Down Expand Up @@ -827,26 +828,57 @@ func TestWrapper_GetCredentialsInWallet(t *testing.T) {
})
}

func TestWrapper_RemoveCredentialFromWallet(t *testing.T) {
func TestWrapper_RemoveCredentialFromSubjectWallet(t *testing.T) {
didNuts := did.MustParseDID("did:nuts:123")
didWeb := did.MustParseDID("did:web:example.com")
subject := "subbie"
t.Run("ok", func(t *testing.T) {
testContext := newMockContext(t)
testContext.mockWallet.EXPECT().Remove(testContext.requestCtx, holderDID, credentialID).Return(nil)
testContext.mockSubjectManager.EXPECT().ListDIDs(testContext.requestCtx, subject).Return([]did.DID{didNuts, didWeb}, nil)
testContext.mockWallet.EXPECT().Remove(testContext.requestCtx, didNuts, credentialID).Return(nil)
testContext.mockWallet.EXPECT().Remove(testContext.requestCtx, didWeb, credentialID).Return(types.ErrNotFound) // only exists on 1 DID

response, err := testContext.client.RemoveCredentialFromWallet(testContext.requestCtx, RemoveCredentialFromWalletRequestObject{
Did: holderDID.String(),
Id: credentialID.String(),
SubjectID: subject,
Id: credentialID.String(),
})

assert.NoError(t, err)
assert.Equal(t, RemoveCredentialFromWallet204Response{}, response)
})
t.Run("error", func(t *testing.T) {
t.Run("error - credential not found", func(t *testing.T) {
testContext := newMockContext(t)
testContext.mockSubjectManager.EXPECT().ListDIDs(testContext.requestCtx, subject).Return([]did.DID{didNuts, didWeb}, nil)
testContext.mockWallet.EXPECT().Remove(testContext.requestCtx, gomock.AnyOf(didNuts, didWeb), credentialID).Return(types.ErrNotFound).Times(2)

response, err := testContext.client.RemoveCredentialFromWallet(testContext.requestCtx, RemoveCredentialFromWalletRequestObject{
SubjectID: subject,
Id: credentialID.String(),
})

assert.Empty(t, response)
assert.ErrorIs(t, err, types.ErrNotFound)
})
t.Run("error - subject not found", func(t *testing.T) {
testContext := newMockContext(t)
testContext.mockSubjectManager.EXPECT().ListDIDs(testContext.requestCtx, subject).Return(nil, didsubject.ErrSubjectNotFound)

response, err := testContext.client.RemoveCredentialFromWallet(testContext.requestCtx, RemoveCredentialFromWalletRequestObject{
SubjectID: subject,
Id: credentialID.String(),
})

assert.Empty(t, response)
assert.ErrorIs(t, err, didsubject.ErrSubjectNotFound)
})
t.Run("error - general error", func(t *testing.T) {
testContext := newMockContext(t)
testContext.mockWallet.EXPECT().Remove(testContext.requestCtx, holderDID, credentialID).Return(assert.AnError)
testContext.mockSubjectManager.EXPECT().ListDIDs(testContext.requestCtx, subject).Return([]did.DID{didNuts, didWeb}, nil)
testContext.mockWallet.EXPECT().Remove(testContext.requestCtx, didNuts, credentialID).Return(assert.AnError)

response, err := testContext.client.RemoveCredentialFromWallet(testContext.requestCtx, RemoveCredentialFromWalletRequestObject{
Did: holderDID.String(),
Id: credentialID.String(),
SubjectID: subject,
Id: credentialID.String(),
})

assert.Empty(t, response)
Expand Down
Loading

0 comments on commit 869308b

Please sign in to comment.