Skip to content

Commit

Permalink
feat: Return alternate links in WebFinger query for DID and CAS
Browse files Browse the repository at this point in the history
Implemented an anchor acknowledgement handler that stores additional links for anchor credentials from domains that returned a "Like" activity. These additional links are returned in WebFinger queries for DIDs and CAS.

closes trustbloc#766

Signed-off-by: Bob Stasyszyn <Bob.Stasyszyn@securekey.com>
  • Loading branch information
bstasyszyn committed Sep 17, 2021
1 parent 107e3f4 commit 7b6c41e
Show file tree
Hide file tree
Showing 21 changed files with 1,303 additions and 193 deletions.
45 changes: 30 additions & 15 deletions cmd/orb-server/startcmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,10 @@ import (
"github.com/trustbloc/orb/pkg/activitypub/vocab"
"github.com/trustbloc/orb/pkg/anchor/builder"
"github.com/trustbloc/orb/pkg/anchor/graph"
"github.com/trustbloc/orb/pkg/anchor/handler/acknowlegement"
"github.com/trustbloc/orb/pkg/anchor/handler/credential"
"github.com/trustbloc/orb/pkg/anchor/handler/proof"
"github.com/trustbloc/orb/pkg/anchor/linkstore"
"github.com/trustbloc/orb/pkg/anchor/policy"
policyhandler "github.com/trustbloc/orb/pkg/anchor/policy/resthandler"
"github.com/trustbloc/orb/pkg/anchor/writer"
Expand Down Expand Up @@ -607,6 +609,11 @@ func startOrbServices(parameters *orbParameters) error {

resourceResolver := resource.New(httpClient, ipfsReader)

anchorLinkStore, err := linkstore.New(storeProviders.provider)
if err != nil {
return fmt.Errorf("open store: %w", err)
}

// create new observer and start it
providers := &observer.Providers{
ProtocolClientProvider: pcp,
Expand All @@ -618,13 +625,16 @@ func startOrbServices(parameters *orbParameters) error {
WebFingerResolver: resourceResolver,
CASResolver: casResolver,
DocLoader: orbDocumentLoader,
AnchorLinkStore: anchorLinkStore,
}

o, err := observer.New(providers, observer.WithDiscoveryDomain(parameters.discoveryDomain))
if err != nil {
return fmt.Errorf("failed to create observer: %s", err.Error())
}

anchorEventHandler := acknowlegement.New(anchorLinkStore)

activityPubService, err = apservice.New(apConfig,
apStore, t, apSigVerifier, pubSub, apClient, resourceResolver, metrics.Get(),
apspi.WithProofHandler(proofHandler),
Expand All @@ -636,7 +646,7 @@ func startOrbServices(parameters *orbParameters) error {
// apspi.WithWitnessInvitationAuth(inviteWitnessAuth),
// apspi.WithFollowerAuth(followerAuth),
// apspi.WithUndeliverableHandler(undeliverableHandler),
// apspi.WithAnchorEventAcknowledgementHandler(anchorEventHandler),
apspi.WithAnchorEventAcknowledgementHandler(anchorEventHandler),
)
if err != nil {
return fmt.Errorf("failed to create ActivityPub service: %s", err.Error())
Expand Down Expand Up @@ -755,20 +765,25 @@ func startOrbServices(parameters *orbParameters) error {
orbDocUpdateHandler := updatehandler.New(didDocHandler, metrics.Get(), updateHandlerOpts...)

// create discovery rest api
endpointDiscoveryOp, err := discoveryrest.New(&discoveryrest.Config{
PubKey: pubKey,
VerificationMethodType: verificationMethodType,
KID: parameters.keyID,
ResolutionPath: baseResolvePath,
OperationPath: baseUpdatePath,
WebCASPath: casPath,
BaseURL: parameters.externalEndpoint,
DiscoveryDomains: parameters.discoveryDomains,
DiscoveryMinimumResolvers: parameters.discoveryMinimumResolvers,
VctURL: parameters.vctURL,
DiscoveryVctDomains: parameters.discoveryVctDomains,
ResourceRegistry: resourceRegistry,
})
endpointDiscoveryOp, err := discoveryrest.New(
&discoveryrest.Config{
PubKey: pubKey,
VerificationMethodType: verificationMethodType,
KID: parameters.keyID,
ResolutionPath: baseResolvePath,
OperationPath: baseUpdatePath,
WebCASPath: casPath,
BaseURL: parameters.externalEndpoint,
DiscoveryDomains: parameters.discoveryDomains,
DiscoveryMinimumResolvers: parameters.discoveryMinimumResolvers,
VctURL: parameters.vctURL,
DiscoveryVctDomains: parameters.discoveryVctDomains,
},
&discoveryrest.Providers{
ResourceRegistry: resourceRegistry,
CAS: coreCASClient,
AnchorLinkStore: anchorLinkStore,
})
if err != nil {
return fmt.Errorf("discovery rest: %w", err)
}
Expand Down
31 changes: 1 addition & 30 deletions pkg/activitypub/service/activityhandler/inboxhandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -969,36 +969,7 @@ type noOpAnchorEventAcknowledgementHandler struct{}
func (p *noOpAnchorEventAcknowledgementHandler) AnchorEventAcknowledged(actor, anchorRef *url.URL,
additionalAnchorRefs []*url.URL) error {
logger.Infof("Anchor event was acknowledged by [%s] for anchor %s. Additional anchors: %s",
actor, newHashLinkInfo(anchorRef), newHashLinkInfo(additionalAnchorRefs...))
actor, hashlink.ToString(anchorRef), hashlink.ToString(additionalAnchorRefs...))

return nil
}

type hashLinkInfo struct {
hl []*url.URL
}

func newHashLinkInfo(hl ...*url.URL) *hashLinkInfo {
return &hashLinkInfo{hl: hl}
}

func (hlInfo *hashLinkInfo) String() string {
str := ""

parser := hashlink.New()

for i, hl := range hlInfo.hl {
if i > 0 {
str += ", "
}

info, err := parser.ParseHashLink(hl.String())
if err != nil {
str += fmt.Sprintf("{INVALID HASHLINK [%s]}", hl)
} else {
str += fmt.Sprintf("{Hash [%s], Links %s}", info.ResourceHash, info.Links)
}
}

return str
}
73 changes: 73 additions & 0 deletions pkg/anchor/handler/acknowlegement/acknowledgement.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package acknowlegement

import (
"fmt"
"net/url"

"github.com/trustbloc/edge-core/pkg/log"

"github.com/trustbloc/orb/pkg/hashlink"
)

var logger = log.New("anchor-acknowledgement-handler")

type anchorLinkStore interface {
PutLinks(links []*url.URL) error
}

// Handler handles notifications of successful anchor events processed from an Orb server.
type Handler struct {
anchorLinkStore anchorLinkStore
}

// New returns a new handler.
func New(store anchorLinkStore) *Handler {
return &Handler{anchorLinkStore: store}
}

// AnchorEventAcknowledged handles a notification of a successful anchor event processed from an Orb server.
// The given additional references are added to the anchor link store so that they are available for
// WebFinger requests.
func (p *Handler) AnchorEventAcknowledged(actor, anchorRef *url.URL, additionalAnchorRefs []*url.URL) error {
logger.Infof("Anchor event was acknowledged by [%s] for anchor %s. Additional anchors: %s",
actor, hashlink.ToString(anchorRef), hashlink.ToString(additionalAnchorRefs...))

parser := hashlink.New()

info, err := parser.ParseHashLink(anchorRef.String())
if err != nil {
return fmt.Errorf("parse hashlink [%s]: %w", anchorRef, err)
}

var links []*url.URL

for _, hl := range additionalAnchorRefs {
hlInfo, err := parser.ParseHashLink(hl.String())
if err != nil {
logger.Warnf("Error parsing hashlink [%s]: %s", anchorRef, err)

continue
}

if hlInfo.ResourceHash != info.ResourceHash {
logger.Warnf("Hash in additional anchor ref [%s] does not match the hash of the acknowledged anchor event [%s]",
hlInfo.ResourceHash, info.ResourceHash)

continue
}

links = append(links, hl)
}

if err := p.anchorLinkStore.PutLinks(links); err != nil {
return fmt.Errorf("put links [%s]: %w", info.ResourceHash, err)
}

return nil
}
57 changes: 57 additions & 0 deletions pkg/anchor/handler/acknowlegement/acknowledgement_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package acknowlegement

import (
"errors"
"net/url"
"testing"

"github.com/stretchr/testify/require"

"github.com/trustbloc/orb/pkg/internal/testutil"
"github.com/trustbloc/orb/pkg/mocks"
)

func TestHandler(t *testing.T) {
actor := testutil.MustParseURL("https://domain1.com")
anchorRef := testutil.MustParseURL("hl:uEiALYp_C4wk2WegpfnCSoSTBdKZ1MVdDadn4rdmZl5GKzQ:uoQ-BeDVpcGZzOi8vUW1jcTZKV0RVa3l4ZWhxN1JWWmtQM052aUU0SHFSdW5SalgzOXZ1THZFSGFRTg") //nolint:lll

additionalRefs := []*url.URL{
// Valid hashlink.
testutil.MustParseURL("hl:uEiALYp_C4wk2WegpfnCSoSTBdKZ1MVdDadn4rdmZl5GKzQ:uoQ-BeEtodHRwczovL29yYi5kb21haW4yLmNvbS9jYXMvdUVpQlVRRFJJNXR0SXpYYmUxTFpLVWFaV2I2eUZzbk1ucmdEa3NBdFEtd0NhS3c"), //nolint:lll
// Hash in hashlink doesn't match anchor hash.
testutil.MustParseURL("hl:uEiBUQDRI5ttIzXbe1LZKUaZWb6yFsnMnrgDksAtQ-wCaKw:uoQ-BeEtodHRwczovL29yYi5kb21haW4yLmNvbS9jYXMvdUVpQlVRRFJJNXR0SXpYYmUxTFpLVWFaV2I2eUZzbk1ucmdEa3NBdFEtd0NhS3c"), //nolint:lll
// Invalid hashlink.
testutil.MustParseURL("xx:invalid"),
}

linkStore := &mocks.AnchorLinkStore{}

h := New(linkStore)

t.Run("Success", func(t *testing.T) {
linkStore.PutLinksReturns(nil)

require.NoError(t, h.AnchorEventAcknowledged(actor, anchorRef, additionalRefs))
})

t.Run("Anchor link storage error", func(t *testing.T) {
errExpected := errors.New("injected storage error")

linkStore.PutLinksReturns(errExpected)

err := h.AnchorEventAcknowledged(actor, anchorRef, additionalRefs)

require.Error(t, err)
require.Contains(t, err.Error(), errExpected.Error())
})

t.Run("Invalid anchor link", func(t *testing.T) {
require.Error(t, h.AnchorEventAcknowledged(actor, testutil.MustParseURL("xx:invalid"), additionalRefs))
})
}
Loading

0 comments on commit 7b6c41e

Please sign in to comment.