Skip to content

Commit

Permalink
chore: add w3c loader (#503)
Browse files Browse the repository at this point in the history
* chore: add w3c loader

* feat: Test w3cDocumentLoader.LoadDocument inteception

* fix: linter

---------

Co-authored-by: x1m3 <ximebcn@yahoo.es>
  • Loading branch information
martinsaporiti and x1m3 authored Oct 10, 2023
1 parent f9baf72 commit 0599607
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 7 deletions.
5 changes: 2 additions & 3 deletions internal/api_ui/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3698,9 +3698,8 @@ func TestServer_CreateLinkQRCode(t *testing.T) {
cfg.APIUI.ServerURL = "http://localhost/issuer-admin"

server := NewServer(&cfg, NewIdentityMock(), claimsService, NewSchemaMock(), connectionsService, linkService, NewPublisherMock(), NewPackageManagerMock(), nil)

validUntil := common.ToPointer(time.Date(2023, 8, 15, 14, 30, 45, 0, time.Local))
credentialExpiration := common.ToPointer(time.Date(2025, 8, 15, 14, 30, 45, 0, time.Local))
validUntil := common.ToPointer(time.Now().Add(24 * time.Hour))
credentialExpiration := common.ToPointer(time.Now().Add(48 * time.Hour))
link, err := linkService.Save(ctx, *did, common.ToPointer(10), validUntil, importedSchema.ID, credentialExpiration, true, true, domain.CredentialSubject{"birthday": 19791109, "documentType": 12})
assert.NoError(t, err)

Expand Down
7 changes: 4 additions & 3 deletions internal/core/services/claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type claim struct {
loaderFactory loader.Factory
publisher pubsub.Publisher
ipfsClient *shell.Shell
ipfsGatewayURL string
}

// NewClaim creates a new claim service
Expand All @@ -83,6 +84,7 @@ func NewClaim(repo ports.ClaimsRepository, idenSrv ports.IdentityService, mtServ
}
if ipfsGatewayURL != "" {
s.ipfsClient = shell.NewShell(ipfsGatewayURL)
s.ipfsGatewayURL = ipfsGatewayURL
}
return s
}
Expand Down Expand Up @@ -163,9 +165,8 @@ func (c *claim) CreateCredential(ctx context.Context, req *ports.CreateClaimRequ
SubjectPosition: req.SubjectPos,
Updatable: false,
}
if c.ipfsClient != nil {
opts.MerklizerOpts = []merklize.MerklizeOption{merklize.WithIPFSClient(c.ipfsClient)}
}

opts.MerklizerOpts = []merklize.MerklizeOption{merklize.WithDocumentLoader(loader.NewW3CDocumentLoader(c.ipfsClient, c.ipfsGatewayURL))}

coreClaim, err := schemaPkg.Process(ctx, c.loaderFactory(req.Schema), credentialType, vc, opts)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion internal/jsonschema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func (s *JSONSchema) SchemaHash(schemaType string) (core.SchemaHash, error) {

// ValidateCredentialSubject validates that the given credential subject matches the given schema
func ValidateCredentialSubject(ctx context.Context, ipfsGateway string, schemaURL string, schemaType string, cSubject map[string]interface{}) error {
documentLoader := merklize.NewDocumentLoader(shell.NewShell(ipfsGateway), ipfsGateway)
documentLoader := loader.NewW3CDocumentLoader(shell.NewShell(ipfsGateway), ipfsGateway)
schemaLoader := loader.CachedFactory(loader.MultiProtocolFactory(ipfsGateway), cache.NewMemoryCache()) // nolint: contextcheck
schema, err := Load(ctx, schemaLoader(schemaURL))
if err != nil {
Expand Down
242 changes: 242 additions & 0 deletions internal/loader/w3CDocumentLoader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
package loader

import (
"strings"

"github.com/iden3/go-schema-processor/merklize"
shell "github.com/ipfs/go-ipfs-api"
"github.com/piprate/json-gold/ld"
)

// W3CDocumentLoader is a document loader that loads w3c documents
type W3CDocumentLoader struct {
l ld.DocumentLoader
}

// NewW3CDocumentLoader creates a new document loader with a predefined http schema
func NewW3CDocumentLoader(ipfsCli *shell.Shell, ipfsGW string) ld.DocumentLoader {
l := merklize.NewDocumentLoader(ipfsCli, ipfsGW)
return &W3CDocumentLoader{
l: l,
}
}

// LoadDocument loads a document from a url
func (d *W3CDocumentLoader) LoadDocument(url string) (doc *ld.RemoteDocument, err error) {
if url == W3CCredential2018ContextURL {
w3cDoc, errIn := ld.DocumentFromReader(strings.NewReader(W3CCredential2018ContextDocument))
if errIn != nil {
return nil, errIn
}
return &ld.RemoteDocument{
DocumentURL: url,
Document: w3cDoc,
ContextURL: url,
}, nil
}
return d.l.LoadDocument(url)
}

// W3CCredential2018ContextURL is w3c context url
//
//nolint:golint,gosec //reason: not credentials
const W3CCredential2018ContextURL = "https://www.w3.org/2018/credentials/v1"

// W3CCredential2018ContextDocument is w3c context file
//
//nolint:golint,gosec //reason: not credentials
const W3CCredential2018ContextDocument string = `{
"@context": {
"@version": 1.1,
"@protected": true,
"id": "@id",
"type": "@type",
"VerifiableCredential": {
"@id": "https://www.w3.org/2018/credentials#VerifiableCredential",
"@context": {
"@version": 1.1,
"@protected": true,
"id": "@id",
"type": "@type",
"cred": "https://www.w3.org/2018/credentials#",
"sec": "https://w3id.org/security#",
"xsd": "http://www.w3.org/2001/XMLSchema#",
"credentialSchema": {
"@id": "cred:credentialSchema",
"@type": "@id",
"@context": {
"@version": 1.1,
"@protected": true,
"id": "@id",
"type": "@type",
"cred": "https://www.w3.org/2018/credentials#",
"JsonSchemaValidator2018": "cred:JsonSchemaValidator2018"
}
},
"credentialStatus": {"@id": "cred:credentialStatus", "@type": "@id"},
"credentialSubject": {"@id": "cred:credentialSubject", "@type": "@id"},
"evidence": {"@id": "cred:evidence", "@type": "@id"},
"expirationDate": {"@id": "cred:expirationDate", "@type": "xsd:dateTime"},
"holder": {"@id": "cred:holder", "@type": "@id"},
"issued": {"@id": "cred:issued", "@type": "xsd:dateTime"},
"issuer": {"@id": "cred:issuer", "@type": "@id"},
"issuanceDate": {"@id": "cred:issuanceDate", "@type": "xsd:dateTime"},
"proof": {"@id": "sec:proof", "@type": "@id", "@container": "@graph"},
"refreshService": {
"@id": "cred:refreshService",
"@type": "@id",
"@context": {
"@version": 1.1,
"@protected": true,
"id": "@id",
"type": "@type",
"cred": "https://www.w3.org/2018/credentials#",
"ManualRefreshService2018": "cred:ManualRefreshService2018"
}
},
"termsOfUse": {"@id": "cred:termsOfUse", "@type": "@id"},
"validFrom": {"@id": "cred:validFrom", "@type": "xsd:dateTime"},
"validUntil": {"@id": "cred:validUntil", "@type": "xsd:dateTime"}
}
},
"VerifiablePresentation": {
"@id": "https://www.w3.org/2018/credentials#VerifiablePresentation",
"@context": {
"@version": 1.1,
"@protected": true,
"id": "@id",
"type": "@type",
"cred": "https://www.w3.org/2018/credentials#",
"sec": "https://w3id.org/security#",
"holder": {"@id": "cred:holder", "@type": "@id"},
"proof": {"@id": "sec:proof", "@type": "@id", "@container": "@graph"},
"verifiableCredential": {"@id": "cred:verifiableCredential", "@type": "@id", "@container": "@graph"}
}
},
"EcdsaSecp256k1Signature2019": {
"@id": "https://w3id.org/security#EcdsaSecp256k1Signature2019",
"@context": {
"@version": 1.1,
"@protected": true,
"id": "@id",
"type": "@type",
"sec": "https://w3id.org/security#",
"xsd": "http://www.w3.org/2001/XMLSchema#",
"challenge": "sec:challenge",
"created": {"@id": "http://purl.org/dc/terms/created", "@type": "xsd:dateTime"},
"domain": "sec:domain",
"expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"},
"jws": "sec:jws",
"nonce": "sec:nonce",
"proofPurpose": {
"@id": "sec:proofPurpose",
"@type": "@vocab",
"@context": {
"@version": 1.1,
"@protected": true,
"id": "@id",
"type": "@type",
"sec": "https://w3id.org/security#",
"assertionMethod": {"@id": "sec:assertionMethod", "@type": "@id", "@container": "@set"},
"authentication": {"@id": "sec:authenticationMethod", "@type": "@id", "@container": "@set"}
}
},
"proofValue": "sec:proofValue",
"verificationMethod": {"@id": "sec:verificationMethod", "@type": "@id"}
}
},
"EcdsaSecp256r1Signature2019": {
"@id": "https://w3id.org/security#EcdsaSecp256r1Signature2019",
"@context": {
"@version": 1.1,
"@protected": true,
"id": "@id",
"type": "@type",
"sec": "https://w3id.org/security#",
"xsd": "http://www.w3.org/2001/XMLSchema#",
"challenge": "sec:challenge",
"created": {"@id": "http://purl.org/dc/terms/created", "@type": "xsd:dateTime"},
"domain": "sec:domain",
"expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"},
"jws": "sec:jws",
"nonce": "sec:nonce",
"proofPurpose": {
"@id": "sec:proofPurpose",
"@type": "@vocab",
"@context": {
"@version": 1.1,
"@protected": true,
"id": "@id",
"type": "@type",
"sec": "https://w3id.org/security#",
"assertionMethod": {"@id": "sec:assertionMethod", "@type": "@id", "@container": "@set"},
"authentication": {"@id": "sec:authenticationMethod", "@type": "@id", "@container": "@set"}
}
},
"proofValue": "sec:proofValue",
"verificationMethod": {"@id": "sec:verificationMethod", "@type": "@id"}
}
},
"Ed25519Signature2018": {
"@id": "https://w3id.org/security#Ed25519Signature2018",
"@context": {
"@version": 1.1,
"@protected": true,
"id": "@id",
"type": "@type",
"sec": "https://w3id.org/security#",
"xsd": "http://www.w3.org/2001/XMLSchema#",
"challenge": "sec:challenge",
"created": {"@id": "http://purl.org/dc/terms/created", "@type": "xsd:dateTime"},
"domain": "sec:domain",
"expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"},
"jws": "sec:jws",
"nonce": "sec:nonce",
"proofPurpose": {
"@id": "sec:proofPurpose",
"@type": "@vocab",
"@context": {
"@version": 1.1,
"@protected": true,
"id": "@id",
"type": "@type",
"sec": "https://w3id.org/security#",
"assertionMethod": {"@id": "sec:assertionMethod", "@type": "@id", "@container": "@set"},
"authentication": {"@id": "sec:authenticationMethod", "@type": "@id", "@container": "@set"}
}
},
"proofValue": "sec:proofValue",
"verificationMethod": {"@id": "sec:verificationMethod", "@type": "@id"}
}
},
"RsaSignature2018": {
"@id": "https://w3id.org/security#RsaSignature2018",
"@context": {
"@version": 1.1,
"@protected": true,
"challenge": "sec:challenge",
"created": {"@id": "http://purl.org/dc/terms/created", "@type": "xsd:dateTime"},
"domain": "sec:domain",
"expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"},
"jws": "sec:jws",
"nonce": "sec:nonce",
"proofPurpose": {
"@id": "sec:proofPurpose",
"@type": "@vocab",
"@context": {
"@version": 1.1,
"@protected": true,
"id": "@id",
"type": "@type",
"sec": "https://w3id.org/security#",
"assertionMethod": {"@id": "sec:assertionMethod", "@type": "@id", "@container": "@set"},
"authentication": {"@id": "sec:authenticationMethod", "@type": "@id", "@container": "@set"}
}
},
"proofValue": "sec:proofValue",
"verificationMethod": {"@id": "sec:verificationMethod", "@type": "@id"}
}
},
"proof": {"@id": "https://w3id.org/security#proof", "@type": "@id", "@container": "@graph"}
}
}`
19 changes: 19 additions & 0 deletions internal/loader/w3CDocumentLoader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package loader

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestW3CDocumentLoader_LoadDocument(t *testing.T) {
w3cLoader := NewW3CDocumentLoader(nil, "https://ipfs.io")
doc, err := w3cLoader.LoadDocument(W3CCredential2018ContextURL)
require.NoError(t, err)

m, ok := doc.Document.(map[string]interface{})
require.True(t, ok)
context, ok := m["@context"]
require.True(t, ok)
require.NotNil(t, context)
}
2 changes: 2 additions & 0 deletions pkg/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/polygonid/sh-id-platform/internal/core/domain"
"github.com/polygonid/sh-id-platform/internal/loader"
"github.com/polygonid/sh-id-platform/internal/log"
)

var (
Expand Down Expand Up @@ -115,6 +116,7 @@ func Process(ctx context.Context, ld loader.Loader, credentialType string, crede

claim, err := pr.ParseClaim(ctx, credential, credentialType, schema, options)
if err != nil {
log.Error(ctx, "error parsing claim", "err", err.Error())
return nil, ErrParseClaim
}
return claim, nil
Expand Down

0 comments on commit 0599607

Please sign in to comment.