diff --git a/credential/builder.go b/credential/builder.go index b1eed6c6..50592d92 100644 --- a/credential/builder.go +++ b/credential/builder.go @@ -2,6 +2,7 @@ package credential import ( "fmt" + "net/url" "reflect" "github.com/google/uuid" @@ -11,6 +12,8 @@ import ( "github.com/pkg/errors" ) +type IDValue string // IDValue represents different types of ID values for building Verifiable Credentials + const ( VerifiableCredentialsLinkedDataContext string = "https://www.w3.org/2018/credentials/v1" VerifiableCredentialType string = "VerifiableCredential" @@ -20,6 +23,9 @@ const ( VerifiablePresentationType string = "VerifiablePresentation" BuilderEmptyError string = "builder cannot be empty" + + EmptyIDValue IDValue = "" // EmptyIDValue indicates setting the ID value to empty + GenerateIDValue IDValue = "generate" // GenerateIDValue indicates generating a UUID as the ID value. ) // VerifiableCredentialBuilder uses the builder pattern to construct a verifiable credential @@ -31,19 +37,32 @@ type VerifiableCredentialBuilder struct { } // NewVerifiableCredentialBuilder returns an initialized credential builder with some default fields populated -func NewVerifiableCredentialBuilder() VerifiableCredentialBuilder { +// idValue determines whether VC will have empty ID/ random UUID/ customID. +func NewVerifiableCredentialBuilder(idValue IDValue) VerifiableCredentialBuilder { contexts := []string{VerifiableCredentialsLinkedDataContext} types := []string{VerifiableCredentialType} - return VerifiableCredentialBuilder{ + var id string + switch { + case idValue == EmptyIDValue: + id = "" + case idValue == GenerateIDValue: + id = uuid.NewString() + default: + id = string(idValue) + } + + vcb := VerifiableCredentialBuilder{ contexts: contexts, types: types, VerifiableCredential: &VerifiableCredential{ - ID: uuid.NewString(), + ID: id, Context: contexts, Type: types, IssuanceDate: util.GetRFC3339Timestamp(), }, } + return vcb + } // Build attempts to turn a builder into a valid verifiable credential, doing some object model validation. @@ -85,7 +104,9 @@ func (vcb *VerifiableCredentialBuilder) SetID(id string) error { if vcb.IsEmpty() { return errors.New(BuilderEmptyError) } - + if _, err := url.Parse(id); err != nil { + return errors.Wrap(err, "malformed id") + } vcb.ID = id return nil } diff --git a/credential/builder_test.go b/credential/builder_test.go index 717bfd56..a6ca7549 100644 --- a/credential/builder_test.go +++ b/credential/builder_test.go @@ -44,7 +44,7 @@ func TestCredential(t *testing.T) { assert.NoError(t, err) // re-build with our builder - builder := NewVerifiableCredentialBuilder() + builder := NewVerifiableCredentialBuilder(EmptyIDValue) err = builder.AddContext(knownContext) assert.NoError(t, err) @@ -73,7 +73,14 @@ func TestCredential(t *testing.T) { // Exercise all builder methods func TestCredentialBuilder(t *testing.T) { - builder := NewVerifiableCredentialBuilder() + + builder := NewVerifiableCredentialBuilder(EmptyIDValue) + assert.Empty(t, builder.ID) + + builder = NewVerifiableCredentialBuilder(IDValue("customid-123")) + assert.Equal(t, builder.ID, "customid-123") + + builder = NewVerifiableCredentialBuilder(GenerateIDValue) _, err := builder.Build() assert.Error(t, err) notReadyErr := "credential not ready to be built" @@ -93,11 +100,11 @@ func TestCredentialBuilder(t *testing.T) { err = builder.AddContext("https://www.w3.org/2018/credentials/examples/v1") assert.NoError(t, err) - // there is a default id + //default id is not empty assert.NotEmpty(t, builder.ID) // set id - id := "test-id" + id := "p" err = builder.SetID(id) assert.NoError(t, err) diff --git a/credential/status/statuslist2021.go b/credential/status/statuslist2021.go index 47c53b5b..5e0dca9f 100644 --- a/credential/status/statuslist2021.go +++ b/credential/status/statuslist2021.go @@ -73,7 +73,7 @@ func GenerateStatusList2021Credential(id string, issuer string, purpose StatusPu EncodedList: bitString, } - builder := credential.NewVerifiableCredentialBuilder() + builder := credential.NewVerifiableCredentialBuilder(credential.GenerateIDValue) errMsgFragment := "could not generate status list credential: error setting " if err = builder.SetID(id); err != nil { return nil, errors.Wrap(err, errMsgFragment+"id") diff --git a/did/peer/peer_test.go b/did/peer/peer_test.go index 7a62050b..6699af2e 100644 --- a/did/peer/peer_test.go +++ b/did/peer/peer_test.go @@ -195,7 +195,7 @@ func makeSamplePeerDIDDocument() *did.Document { PublicKeyMultibase: "z6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc", }, }, - Services: []did.Service{did.Service{ + Services: []did.Service{{ ID: "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0#didcommmessaging-0", Type: "DIDCommMessaging", ServiceEndpoint: "https://example.com/endpoint", diff --git a/example/manifest/manifest.go b/example/manifest/manifest.go index de8ff951..8fc1e518 100644 --- a/example/manifest/manifest.go +++ b/example/manifest/manifest.go @@ -181,7 +181,7 @@ func prepareCredentialManifest(issuerDID key.DIDKey, licenseSchemaID string) (*m // Prepare a credential which is required to fill out the credential manifest's application's // input descriptor's requirements func issueApplicationCredential(id key.DIDKey, s schema.JSONSchema) (*credential.VerifiableCredential, error) { - builder := credential.NewVerifiableCredentialBuilder() + builder := credential.NewVerifiableCredentialBuilder(credential.GenerateIDValue) if err := builder.SetIssuer(id.String()); err != nil { return nil, err @@ -272,7 +272,7 @@ type driversLicenseFields struct { } func issueDriversLicenseCredential(issuerDID key.DIDKey, subjectDID string, s schema.JSONSchema, data driversLicenseFields) (*credential.VerifiableCredential, error) { - builder := credential.NewVerifiableCredentialBuilder() + builder := credential.NewVerifiableCredentialBuilder(credential.GenerateIDValue) if err := builder.SetIssuer(issuerDID.String()); err != nil { return nil, err diff --git a/example/usecase/apartment_application/apartment_application.go b/example/usecase/apartment_application/apartment_application.go index 46776b8a..22d510cb 100644 --- a/example/usecase/apartment_application/apartment_application.go +++ b/example/usecase/apartment_application/apartment_application.go @@ -76,7 +76,7 @@ func main() { "birthdate": "1975-01-01", } - vcBuilder := credential.NewVerifiableCredentialBuilder() + vcBuilder := credential.NewVerifiableCredentialBuilder(credential.GenerateIDValue) err = vcBuilder.SetIssuer(string(*knownIssuer)) example.HandleExampleError(err, "Failed to set issuer") diff --git a/example/usecase/steel_thread/steel_thread.go b/example/usecase/steel_thread/steel_thread.go index 249eccde..28e7a315 100644 --- a/example/usecase/steel_thread/steel_thread.go +++ b/example/usecase/steel_thread/steel_thread.go @@ -273,7 +273,7 @@ func createVerifiableCredential(issuerDID string, walletDID string) credential.V credSubject := vc.CredentialSubject credSubject["id"] = walletDID - builder := credential.NewVerifiableCredentialBuilder() + builder := credential.NewVerifiableCredentialBuilder(credential.GenerateIDValue) _ = builder.SetIssuer(issuerDID) _ = builder.SetCredentialSubject(credSubject) _ = builder.SetCredentialSchema(*vc.CredentialSchema) diff --git a/go.work.sum b/go.work.sum index 16900942..1736fb19 100644 --- a/go.work.sum +++ b/go.work.sum @@ -224,6 +224,7 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1: github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= @@ -259,12 +260,14 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=