Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Move did:orb to did:web transformation code to separate function #1466

Merged
merged 1 commit into from
Aug 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 2 additions & 141 deletions pkg/document/webresolver/resolvehandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ SPDX-License-Identifier: Apache-2.0
package webresolver

import (
"encoding/json"
"fmt"
"net/url"
"strings"
Expand All @@ -18,6 +17,7 @@ import (
"github.com/trustbloc/sidetree-core-go/pkg/docutil"

orberrors "github.com/trustbloc/orb/pkg/errors"
diddoctransformer "github.com/trustbloc/orb/pkg/orbclient/doctransformer"
)

var logger = log.New("did-web-resolver")
Expand Down Expand Up @@ -83,20 +83,7 @@ func (r *ResolveHandler) ResolveDocument(id string) (*document.ResolutionResult,

webDID := fmt.Sprintf("did:web:%s:scid:%s", r.domain.Host, id)

didWebDoc, err := transformToDIDWeb(webDID, localResponse.Document)
if err != nil {
return nil, err
}

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, equivalentID)
didWebDoc, err := diddoctransformer.WebDocumentFromOrbDocument(webDID, localResponse)
if err != nil {
return nil, err
}
Expand All @@ -106,114 +93,6 @@ 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, equivalentID []string) (document.Document, error) { //nolint:lll
alsoKnownAs, err := getAlsoKnownAs(didWebDoc)
if err != nil {
return nil, err
}

// replace did:orb value with did:web values
updatedAlsoKnownAs := updateValues(alsoKnownAs, webDID, orbDID)

if !contains(updatedAlsoKnownAs, orbDID) {
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
}

func getOrbDID(result *document.ResolutionResult) string {
canonicalIDObj, ok := result.DocumentMetadata[document.CanonicalIDProperty]
if ok {
canonicalID, ok := canonicalIDObj.(string)
if ok {
return canonicalID
}
}

return result.Document.ID()
}

func transformToDIDWeb(id string, doc document.Document) (document.Document, error) {
docBytes, err := doc.Bytes()
if err != nil {
return nil, fmt.Errorf("failed to marshal document for id[%s]: %w", id, err)
}

// replace all occurrences of did:orb ID with did:web ID
didWebDocStr := strings.ReplaceAll(string(docBytes), doc.ID(), id)

var didWebDoc document.Document

err = json.Unmarshal([]byte(didWebDocStr), &didWebDoc)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal document for id[%s]: %w", id, err)
}

return didWebDoc, nil
}

// updateValues will replace old value with new value in an array of strings.
func updateValues(values []string, oldValue, newValue string) []string {
for i, v := range values {
if v == oldValue {
values[i] = newValue
}
}

return values
}

func getAlsoKnownAs(doc document.Document) ([]string, error) {
alsoKnownAsObj, ok := doc[document.AlsoKnownAs]
if !ok || alsoKnownAsObj == nil {
return nil, nil
}

alsoKnownAsObjArr, ok := alsoKnownAsObj.([]interface{})
if ok {
return document.StringArray(alsoKnownAsObjArr), nil
}

alsoKnownAsStrArr, ok := alsoKnownAsObj.([]string)
if ok {
return alsoKnownAsStrArr, nil
}

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 {
Expand All @@ -225,21 +104,3 @@ func getDeactivatedFlag(result *document.ResolutionResult) bool {

return false
}

func contains(values []string, value string) bool {
for _, v := range values {
if v == value {
return true
}
}

return false
}

func minimum(a, b int) int {
if a < b {
return a
}

return b
}
28 changes: 22 additions & 6 deletions pkg/document/webresolver/resolvehandler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,19 @@ func TestResolveHandler_Resolve(t *testing.T) {
response, err := handler.ResolveDocument(testSuffix)
require.NoError(t, err)
require.NotNil(t, response)

responseBytes, err := json.Marshal(response)
require.NoError(t, err)

fmt.Println(string(responseBytes))

require.Equal(t, "did:web:other.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, 4, 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:web:orb.domain1.com:scid:"+testSuffix)
require.Equal(t, response.Document[document.AlsoKnownAs].([]string)[2],
"did:orb:uEiAZPHwtTJ7-rG0nBeD6nqyL3Xsg1IA2BX1n9iGlv5yBJQ:EiBmPHOGe4f8L4_ZVgBg5V343_nDSSX3l6X-9VKRhE57Tw")
})

t.Run("success - also known as does not exist in the document", func(t *testing.T) {
Expand All @@ -132,8 +142,10 @@ 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],
"did:orb:uEiAZPHwtTJ7-rG0nBeD6nqyL3Xsg1IA2BX1n9iGlv5yBJQ:EiBmPHOGe4f8L4_ZVgBg5V343_nDSSX3l6X-9VKRhE57Tw")
require.Equal(t, response.Document[document.AlsoKnownAs].([]string)[1],
"did:orb:hl:uEiAZPHwtTJ7-rG0nBeD6nqyL3Xsg1IA2BX1n9iGlv5yBJQ:uoQ-CeEtodHRwczovL29yYi5kb21haW4xLmNvbS9jYXMvdUVpQVpQSHd0VEo3LXJHMG5CZUQ2bnF5TDNYc2cxSUEyQlgxbjlpR2x2NXlCSlF4QmlwZnM6Ly9iYWZrcmVpYXpocjZjMnRlNjcyd2cyanlmNGQ1ajVsZWwzdjVzYnZlYWd5Y3gyejd3ZWdzMzdoZWJldQ:EiBmPHOGe4f8L4_ZVgBg5V343_nDSSX3l6X-9VKRhE57Tw") //nolint:lll
})

t.Run("success - equivalent ID does not exist in the document", func(t *testing.T) {
Expand Down Expand Up @@ -203,8 +215,12 @@ 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, 3, len(response.Document[document.AlsoKnownAs].([]string)))
require.Equal(t, response.Document[document.AlsoKnownAs].([]string)[0], "other.com")
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
})

t.Run("error - deactivated document returns not found error", func(t *testing.T) {
Expand Down
168 changes: 168 additions & 0 deletions pkg/orbclient/doctransformer/transformer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package diddoctransformer

import (
"encoding/json"
"fmt"
"strings"

"github.com/trustbloc/sidetree-core-go/pkg/document"
)

// WebDocumentFromOrbDocument creates did:web document from did:orb resolution result.
// Rules:
// 1. Replace did:orb ID with did:web ID in Orb did document
// 2. add up to two Orb equivalent IDs to also known as
// (equivalentID with discovery domain for unpublished or canonical ID and HL ID for published).
func WebDocumentFromOrbDocument(webDID string, orbResolutionResult *document.ResolutionResult) (document.Document, error) { // nolint:lll
orbDID := getOrbDID(orbResolutionResult)

didWebDoc, err := transformToDIDWeb(webDID, orbResolutionResult.Document)
if err != nil {
return nil, err
}

equivalentID, err := getEquivalentID(orbResolutionResult)
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, equivalentID)
if err != nil {
return nil, err
}

return didWebDoc, nil
}

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
}

// replace did:orb value with did:web values
updatedAlsoKnownAs := updateValues(alsoKnownAs, webDID, orbDID)

if !contains(updatedAlsoKnownAs, orbDID) {
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
}

func getOrbDID(result *document.ResolutionResult) string {
canonicalIDObj, ok := result.DocumentMetadata[document.CanonicalIDProperty]
if ok {
canonicalID, ok := canonicalIDObj.(string)
if ok {
return canonicalID
}
}

return result.Document.ID()
}

func transformToDIDWeb(id string, doc document.Document) (document.Document, error) {
docBytes, err := doc.Bytes()
if err != nil {
return nil, fmt.Errorf("failed to marshal document for id[%s]: %w", id, err)
}

// replace all occurrences of did:orb ID with did:web ID
didWebDocStr := strings.ReplaceAll(string(docBytes), doc.ID(), id)

var didWebDoc document.Document

err = json.Unmarshal([]byte(didWebDocStr), &didWebDoc)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal document for id[%s]: %w", id, err)
}

return didWebDoc, nil
}

// updateValues will replace old value with new value in an array of strings.
func updateValues(values []string, oldValue, newValue string) []string {
for i, v := range values {
if v == oldValue {
values[i] = newValue
}
}

return values
}

func getAlsoKnownAs(doc document.Document) ([]string, error) {
alsoKnownAsObj, ok := doc[document.AlsoKnownAs]
if !ok || alsoKnownAsObj == nil {
return nil, nil
}

alsoKnownAsObjArr, ok := alsoKnownAsObj.([]interface{})
if ok {
return document.StringArray(alsoKnownAsObjArr), nil
}

alsoKnownAsStrArr, ok := alsoKnownAsObj.([]string)
if ok {
return alsoKnownAsStrArr, nil
}

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 contains(values []string, value string) bool {
for _, v := range values {
if v == value {
return true
}
}

return false
}

func minimum(a, b int) int {
if a < b {
return a
}

return b
}
Loading