diff --git a/pkg/document/webresolver/resolvehandler.go b/pkg/document/webresolver/resolvehandler.go index b17eedb07..062db6b83 100644 --- a/pkg/document/webresolver/resolvehandler.go +++ b/pkg/document/webresolver/resolvehandler.go @@ -90,8 +90,13 @@ func (r *ResolveHandler) ResolveDocument(id string) (*document.ResolutionResult, orbDID := getOrbDID(localResponse) + equivalentID, err := getEquivalentID(localResponse) + if err != nil { + return nil, err + } + // replace did:web ID with did:orb ID in also known as; if did:web ID is not found then add did:orb ID anyway - didWebDoc, err = updateAlsoKnownAs(didWebDoc, webDID, orbDID) + didWebDoc, err = updateAlsoKnownAs(didWebDoc, webDID, orbDID, equivalentID) if err != nil { return nil, err } @@ -101,7 +106,7 @@ func (r *ResolveHandler) ResolveDocument(id string) (*document.ResolutionResult, return &document.ResolutionResult{Document: didWebDoc, Context: localResponse.Context}, nil } -func updateAlsoKnownAs(didWebDoc document.Document, webDID, orbDID string) (document.Document, error) { +func updateAlsoKnownAs(didWebDoc document.Document, webDID, orbDID string, equivalentID []string) (document.Document, error) { //nolint:lll alsoKnownAs, err := getAlsoKnownAs(didWebDoc) if err != nil { return nil, err @@ -114,6 +119,16 @@ func updateAlsoKnownAs(didWebDoc document.Document, webDID, orbDID string) (docu updatedAlsoKnownAs = append(updatedAlsoKnownAs, orbDID) } + // unpublished doc has 1 equivalent ID, and published has 2+ (first one is canonical) + const maxEquivalentIDLength = 2 + count := minimum(maxEquivalentIDLength, len(equivalentID)) + + for i := 0; i < count; i++ { + if !contains(updatedAlsoKnownAs, equivalentID[i]) { + updatedAlsoKnownAs = append(updatedAlsoKnownAs, equivalentID[i]) + } + } + didWebDoc[document.AlsoKnownAs] = updatedAlsoKnownAs return didWebDoc, nil @@ -180,6 +195,25 @@ func getAlsoKnownAs(doc document.Document) ([]string, error) { return nil, fmt.Errorf("unexpected interface '%T' for also known as", alsoKnownAsObj) } +func getEquivalentID(result *document.ResolutionResult) ([]string, error) { + equivalentIDObj, ok := result.DocumentMetadata[document.EquivalentIDProperty] + if !ok { + return nil, nil + } + + equivalentIDArr, ok := equivalentIDObj.([]interface{}) + if ok { + return document.StringArray(equivalentIDArr), nil + } + + equivalentIDStrArr, ok := equivalentIDObj.([]string) + if ok { + return equivalentIDStrArr, nil + } + + return nil, fmt.Errorf("unexpected interface '%T' for equivalentId", equivalentIDObj) +} + func getDeactivatedFlag(result *document.ResolutionResult) bool { deactivatedObj, ok := result.DocumentMetadata[document.DeactivatedProperty] if ok { @@ -201,3 +235,11 @@ func contains(values []string, value string) bool { return false } + +func minimum(a, b int) int { + if a < b { + return a + } + + return b +} diff --git a/pkg/document/webresolver/resolvehandler_test.go b/pkg/document/webresolver/resolvehandler_test.go index af86721bd..281aa7925 100644 --- a/pkg/document/webresolver/resolvehandler_test.go +++ b/pkg/document/webresolver/resolvehandler_test.go @@ -51,8 +51,11 @@ func TestResolveHandler_Resolve(t *testing.T) { require.NotNil(t, response) require.Equal(t, "did:web:orb.domain1.com:scid:"+testSuffix, response.Document.ID()) - require.True(t, contains(response.Document[document.AlsoKnownAs].([]string), - "did:orb:uEiAZPHwtTJ7-rG0nBeD6nqyL3Xsg1IA2BX1n9iGlv5yBJQ:EiBmPHOGe4f8L4_ZVgBg5V343_nDSSX3l6X-9VKRhE57Tw")) + require.Equal(t, response.Document[document.AlsoKnownAs].([]string)[0], "https://myblog.example/") + require.Equal(t, response.Document[document.AlsoKnownAs].([]string)[1], + "did:orb:uEiAZPHwtTJ7-rG0nBeD6nqyL3Xsg1IA2BX1n9iGlv5yBJQ:EiBmPHOGe4f8L4_ZVgBg5V343_nDSSX3l6X-9VKRhE57Tw") + require.Equal(t, response.Document[document.AlsoKnownAs].([]string)[2], + "did:orb:hl:uEiAZPHwtTJ7-rG0nBeD6nqyL3Xsg1IA2BX1n9iGlv5yBJQ:uoQ-CeEtodHRwczovL29yYi5kb21haW4xLmNvbS9jYXMvdUVpQVpQSHd0VEo3LXJHMG5CZUQ2bnF5TDNYc2cxSUEyQlgxbjlpR2x2NXlCSlF4QmlwZnM6Ly9iYWZrcmVpYXpocjZjMnRlNjcyd2cyanlmNGQ1ajVsZWwzdjVzYnZlYWd5Y3gyejd3ZWdzMzdoZWJldQ:EiBmPHOGe4f8L4_ZVgBg5V343_nDSSX3l6X-9VKRhE57Tw") //nolint:lll responseBytes, err := json.Marshal(response) require.NoError(t, err) @@ -76,8 +79,17 @@ func TestResolveHandler_Resolve(t *testing.T) { require.NotNil(t, response) require.Equal(t, "did:web:orb.domain1.com:scid:"+testUnpublishedSuffix, response.Document.ID()) - require.True(t, contains(response.Document[document.AlsoKnownAs].([]string), - "did:orb:uAAA:EiBmPHOGe4f8L4_ZVgBg5V343_nDSSX3l6X-9VKRhE57Tw")) + require.Equal(t, 3, len(response.Document[document.AlsoKnownAs].([]string))) + require.Equal(t, response.Document[document.AlsoKnownAs].([]string)[0], "https://myblog.example/") + require.Equal(t, response.Document[document.AlsoKnownAs].([]string)[1], + "did:orb:uAAA:EiBmPHOGe4f8L4_ZVgBg5V343_nDSSX3l6X-9VKRhE57Tw") + require.Equal(t, response.Document[document.AlsoKnownAs].([]string)[2], + "did:orb:https:orb.domain1.com:uAAA:EiBmPHOGe4f8L4_ZVgBg5V343_nDSSX3l6X-9VKRhE57Tw") + + responseBytes, err := json.Marshal(response) + require.NoError(t, err) + + fmt.Println(string(responseBytes)) }) t.Run("success - published did but domain not in alsoKnownAs (orb canonical ID added to also known as)", func(t *testing.T) { //nolint:lll @@ -124,6 +136,55 @@ func TestResolveHandler_Resolve(t *testing.T) { "did:orb:uEiAZPHwtTJ7-rG0nBeD6nqyL3Xsg1IA2BX1n9iGlv5yBJQ:EiBmPHOGe4f8L4_ZVgBg5V343_nDSSX3l6X-9VKRhE57Tw")) }) + t.Run("success - equivalent ID does not exist in the document", func(t *testing.T) { + rr, err := getTestResolutionResult() + require.NoError(t, err) + + delete(rr.DocumentMetadata, document.EquivalentIDProperty) + + orbResolver := &mocks.OrbResolver{} + orbResolver.ResolveDocumentReturns(rr, nil) + + handler := NewResolveHandler(testDomainURL, + orbPrefix, orbUnpublishedLabel, orbResolver, + &orbmocks.MetricsProvider{}) + + response, err := handler.ResolveDocument(testSuffix) + require.NoError(t, err) + require.NotNil(t, response) + + require.Equal(t, "did:web:orb.domain1.com:scid:"+testSuffix, response.Document.ID()) + require.Equal(t, 2, len(response.Document[document.AlsoKnownAs].([]string))) + require.Equal(t, response.Document[document.AlsoKnownAs].([]string)[0], "https://myblog.example/") + require.Equal(t, response.Document[document.AlsoKnownAs].([]string)[1], + "did:orb:uEiAZPHwtTJ7-rG0nBeD6nqyL3Xsg1IA2BX1n9iGlv5yBJQ:EiBmPHOGe4f8L4_ZVgBg5V343_nDSSX3l6X-9VKRhE57Tw") + }) + + t.Run("success - equivalent ID is string array", func(t *testing.T) { + rr, err := getTestResolutionResult() + require.NoError(t, err) + + rr.DocumentMetadata[document.EquivalentIDProperty] = []string{"https://test.com"} + + orbResolver := &mocks.OrbResolver{} + orbResolver.ResolveDocumentReturns(rr, nil) + + handler := NewResolveHandler(testDomainURL, + orbPrefix, orbUnpublishedLabel, orbResolver, + &orbmocks.MetricsProvider{}) + + response, err := handler.ResolveDocument(testSuffix) + require.NoError(t, err) + require.NotNil(t, response) + + require.Equal(t, "did:web:orb.domain1.com:scid:"+testSuffix, response.Document.ID()) + require.Equal(t, 3, len(response.Document[document.AlsoKnownAs].([]string))) + require.Equal(t, response.Document[document.AlsoKnownAs].([]string)[0], "https://myblog.example/") + require.Equal(t, response.Document[document.AlsoKnownAs].([]string)[1], + "did:orb:uEiAZPHwtTJ7-rG0nBeD6nqyL3Xsg1IA2BX1n9iGlv5yBJQ:EiBmPHOGe4f8L4_ZVgBg5V343_nDSSX3l6X-9VKRhE57Tw") + require.Equal(t, response.Document[document.AlsoKnownAs].([]string)[2], "https://test.com") + }) + t.Run("success - current domain not listed in also known as(string array version)", func(t *testing.T) { rr, err := getTestResolutionResult() require.NoError(t, err) @@ -182,6 +243,25 @@ func TestResolveHandler_Resolve(t *testing.T) { require.Contains(t, err.Error(), "unexpected interface 'float64' for also known as") }) + t.Run("error - equivalent ID is an unexpected interface", func(t *testing.T) { + rr, err := getTestResolutionResult() + require.NoError(t, err) + + rr.DocumentMetadata[document.EquivalentIDProperty] = 123 + + orbResolver := &mocks.OrbResolver{} + orbResolver.ResolveDocumentReturns(rr, nil) + + handler := NewResolveHandler(testDomainURL, + orbPrefix, orbUnpublishedLabel, orbResolver, + &orbmocks.MetricsProvider{}) + + response, err := handler.ResolveDocument(testSuffix) + require.Error(t, err) + require.Nil(t, response) + require.Contains(t, err.Error(), "unexpected interface 'int' for equivalentId") + }) + t.Run("error - orb resolver error", func(t *testing.T) { orbResolver := &mocks.OrbResolver{} orbResolver.ResolveDocumentReturns(nil, fmt.Errorf("orb resolver error"))