Skip to content

Commit

Permalink
feat: Add connection between Orb and VCT server for /vct webfinger query
Browse files Browse the repository at this point in the history
Add connection between Orb and VCT server for /vct webfinger query

Closes #890

Signed-off-by: Sandra Vrtikapa <sandra.vrtikapa@securekey.com>
  • Loading branch information
sandrask committed Nov 30, 2021
1 parent e252870 commit ad55594
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 10 deletions.
1 change: 1 addition & 0 deletions cmd/orb-server/startcmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,7 @@ func startOrbServices(parameters *orbParameters) error {
ResourceRegistry: resourceRegistry,
CAS: coreCASClient,
AnchorLinkStore: anchorLinkStore,
WebfingerClient: wfClient,
})
if err != nil {
return fmt.Errorf("discovery rest: %w", err)
Expand Down
2 changes: 1 addition & 1 deletion pkg/activitypub/service/monitoring/monitoring_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ func TestClient_Watch(t *testing.T) {
},
time.Now().Add(time.Minute),
"https://vct.com", time.Now(),
), "failed to get key[https://vct.com] from ledger type cache: failed to resolve WebFinger resource: received unexpected status code. URL [https://vct.com/.well-known/webfinger?resource=https://vct.com/vct], status code [500], response body [internal server error]") //nolint:lll
), "failed to get key[https://vct.com] from ledger type cache: failed to resolve WebFinger resource[https://vct.com/vct]: received unexpected status code. URL [https://vct.com/.well-known/webfinger?resource=https://vct.com/vct], status code [500], response body [internal server error]") //nolint:lll

checkQueue(t, db, 0)
})
Expand Down
4 changes: 2 additions & 2 deletions pkg/anchor/writer/writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1282,13 +1282,13 @@ func TestWriter_postOfferActivity(t *testing.T) {
err = c.postOfferActivity(anchorEvent, []string{"https://abc.com/services/orb"})
require.Error(t, err)
require.Contains(t, err.Error(),
"failed to resolve WebFinger resource: received unexpected status code. URL [https://abc.com/.well-known/webfinger?resource=https://abc.com/vct], status code [500], response body [internal server error]") //nolint:lll
"failed to resolve WebFinger resource[https://abc.com/vct]: received unexpected status code. URL [https://abc.com/.well-known/webfinger?resource=https://abc.com/vct], status code [500], response body [internal server error]") //nolint:lll

// test error for system witness (no batch witnesses)
err = c.postOfferActivity(anchorEvent, []string{})
require.Error(t, err)
require.Contains(t, err.Error(),
"failed to resolve WebFinger resource: received unexpected status code. URL [http://orb.domain1.com/.well-known/webfinger?resource=http://orb.domain1.com/vct], status code [500], response body [internal server error]") //nolint:lll
"failed to resolve WebFinger resource[http://orb.domain1.com/vct]: received unexpected status code. URL [http://orb.domain1.com/.well-known/webfinger?resource=http://orb.domain1.com/vct], status code [500], response body [internal server error]") //nolint:lll
})

t.Run("error - activity store error", func(t *testing.T) {
Expand Down
45 changes: 45 additions & 0 deletions pkg/discovery/endpoint/restapi/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ import (
"github.com/mr-tron/base58"
"github.com/trustbloc/edge-core/pkg/log"
"github.com/trustbloc/sidetree-core-go/pkg/restapi/common"
"github.com/trustbloc/vct/pkg/controller/command"

orberrors "github.com/trustbloc/orb/pkg/errors"
"github.com/trustbloc/orb/pkg/hashlink"
"github.com/trustbloc/orb/pkg/multihash"
"github.com/trustbloc/orb/pkg/resolver/resource/registry"
"github.com/trustbloc/orb/pkg/webfinger/model"
)

var logger = log.New("discovery-rest")
Expand All @@ -39,6 +41,7 @@ const (
alternateRelation = "alternate"
viaRelation = "via"
serviceRelation = "service"
vctRelation = "vct"

ldJSONType = "application/ld+json"
jrdJSONType = "application/jrd+json"
Expand Down Expand Up @@ -67,6 +70,10 @@ type anchorInfoRetriever interface {
GetAnchorInfo(resource string) (*AnchorInfo, error)
}

type webfingerClient interface {
GetLedgerType(domain string) (string, error)
}

// New returns discovery operations.
func New(c *Config, p *Providers) (*Operation, error) {
u, err := url.Parse(c.BaseURL)
Expand Down Expand Up @@ -95,6 +102,7 @@ func New(c *Config, p *Providers) (*Operation, error) {
anchorInfoRetriever: NewAnchorInfoRetriever(p.ResourceRegistry),
cas: p.CAS,
anchorStore: p.AnchorLinkStore,
wfClient: p.WebfingerClient,
}, nil
}

Expand All @@ -116,6 +124,7 @@ type Operation struct {
discoveryMinimumResolvers int
cas cas
anchorStore anchorLinkStore
wfClient webfingerClient
}

// Config defines configuration for discovery operations.
Expand All @@ -138,6 +147,7 @@ type Providers struct {
ResourceRegistry *registry.Registry
CAS cas
AnchorLinkStore anchorLinkStore
WebfingerClient webfingerClient
}

// GetRESTHandlers get all controller API handler available for this service.
Expand Down Expand Up @@ -269,6 +279,8 @@ func (o *Operation) writeResponseForResourceRequest(rw http.ResponseWriter, reso
writeResponse(rw, resp, http.StatusOK)
case strings.HasPrefix(resource, fmt.Sprintf("%s%s", o.baseURL, o.webCASPath)):
o.handleWebCASQuery(rw, resource)
case strings.HasPrefix(resource, fmt.Sprintf("%s/vct", o.baseURL)):
o.handleVCTQuery(rw, resource)
case strings.HasPrefix(resource, "did:orb:"):
o.handleDIDOrbQuery(rw, resource)
// TODO (#536): Support resources other than did:orb.
Expand Down Expand Up @@ -325,6 +337,39 @@ func (o *Operation) handleDIDOrbQuery(rw http.ResponseWriter, resource string) {
writeResponse(rw, resp, http.StatusOK)
}

func (o *Operation) handleVCTQuery(rw http.ResponseWriter, resource string) {
resp := &JRD{
Subject: resource,
}

if o.vctURL != "" {
resp.Links = append(resp.Links, Link{
Rel: vctRelation,
Type: jrdJSONType,
Href: o.vctURL,
})

lt, err := o.wfClient.GetLedgerType(o.vctURL)
if err != nil {
if errors.Is(err, model.ErrResourceNotFound) {
writeResponse(rw, resp, http.StatusOK)
} else {
logger.Warnf("Error retrieving ledger type from VCT[%s]: %s", o.vctURL, err)

writeErrorResponse(rw, http.StatusInternalServerError, "error retrieving ledger type from VCT")
}

return
}

resp.Properties = map[string]interface{}{
command.LedgerType: lt,
}
}

writeResponse(rw, resp, http.StatusOK)
}

func (o *Operation) handleWebCASQuery(rw http.ResponseWriter, resource string) {
resourceSplitBySlash := strings.Split(resource, "/")

Expand Down
110 changes: 110 additions & 0 deletions pkg/discovery/endpoint/restapi/operations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"bytes"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
Expand All @@ -17,13 +18,15 @@ import (
"github.com/gorilla/mux"
"github.com/stretchr/testify/require"
"github.com/trustbloc/sidetree-core-go/pkg/restapi/common"
"github.com/trustbloc/vct/pkg/controller/command"

"github.com/trustbloc/orb/pkg/cas/resolver/mocks"
"github.com/trustbloc/orb/pkg/discovery/endpoint/restapi"
orberrors "github.com/trustbloc/orb/pkg/errors"
"github.com/trustbloc/orb/pkg/internal/testutil"
orbmocks "github.com/trustbloc/orb/pkg/mocks"
"github.com/trustbloc/orb/pkg/resolver/resource/registry"
wfclient "github.com/trustbloc/orb/pkg/webfinger/client"
)

const (
Expand Down Expand Up @@ -205,6 +208,107 @@ func TestWebFinger(t *testing.T) {
require.Empty(t, w.Properties)
})

t.Run("test vct resource", func(t *testing.T) {
const webfingerPayload = `{"properties":{"https://trustbloc.dev/ns/ledger-type":"vct-v1"}}`

wfHTTPClient := httpMock(func(req *http.Request) (*http.Response, error) {
return &http.Response{
Body: ioutil.NopCloser(bytes.NewBufferString(webfingerPayload)),
StatusCode: http.StatusOK,
}, nil
})

wfClient := wfclient.New(wfclient.WithHTTPClient(wfHTTPClient))

c, err := restapi.New(&restapi.Config{
VctURL: "http://vct.com",
WebCASPath: "/cas",
BaseURL: "http://base",
DiscoveryVctDomains: []string{"http://vct.com/maple2020"},
},
&restapi.Providers{WebfingerClient: wfClient})
require.NoError(t, err)

handler := getHandler(t, c, restapi.WebFingerEndpoint)

rr := serveHTTP(t, handler.Handler(), http.MethodGet, restapi.WebFingerEndpoint+"?resource=http://base/vct",
nil, nil, false)

require.Equal(t, http.StatusOK, rr.Code)

var w restapi.JRD

require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &w))

require.Equal(t, "vct", w.Links[0].Rel)
require.Equal(t, "http://vct.com", w.Links[0].Href)
require.Equal(t, "vct-v1", w.Properties[command.LedgerType])
})

t.Run("error - vct resource error", func(t *testing.T) {
const webfingerPayload = `{}`

wfHTTPClient := httpMock(func(req *http.Request) (*http.Response, error) {
return &http.Response{
Body: ioutil.NopCloser(bytes.NewBufferString(webfingerPayload)),
StatusCode: http.StatusOK,
}, nil
})

wfClient := wfclient.New(wfclient.WithHTTPClient(wfHTTPClient))

c, err := restapi.New(&restapi.Config{
VctURL: "http://vct.com",
WebCASPath: "/cas",
BaseURL: "http://base",
DiscoveryVctDomains: []string{"http://vct.com/maple2020"},
},
&restapi.Providers{WebfingerClient: wfClient})
require.NoError(t, err)

handler := getHandler(t, c, restapi.WebFingerEndpoint)

rr := serveHTTP(t, handler.Handler(), http.MethodGet, restapi.WebFingerEndpoint+"?resource=http://base/vct",
nil, nil, false)

require.Equal(t, http.StatusOK, rr.Code)

var w restapi.JRD

require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &w))

require.Equal(t, "vct", w.Links[0].Rel)
require.Equal(t, "http://vct.com", w.Links[0].Href)
require.Empty(t, w.Properties[command.LedgerType])
})

t.Run("error - vct internal server error", func(t *testing.T) {
wfHTTPClient := httpMock(func(req *http.Request) (*http.Response, error) {
return &http.Response{
Body: ioutil.NopCloser(bytes.NewBufferString(" internal server error")),
StatusCode: http.StatusInternalServerError,
}, nil
})

wfClient := wfclient.New(wfclient.WithHTTPClient(wfHTTPClient))

c, err := restapi.New(&restapi.Config{
VctURL: "http://vct.com",
WebCASPath: "/cas",
BaseURL: "http://base",
DiscoveryVctDomains: []string{"http://vct.com/maple2020"},
},
&restapi.Providers{WebfingerClient: wfClient})
require.NoError(t, err)

handler := getHandler(t, c, restapi.WebFingerEndpoint)

rr := serveHTTP(t, handler.Handler(), http.MethodGet, restapi.WebFingerEndpoint+"?resource=http://base/vct",
nil, nil, false)

require.Equal(t, http.StatusInternalServerError, rr.Code)
})

t.Run("test WebCAS resource", func(t *testing.T) {
casClient := &mocks.CASClient{}

Expand Down Expand Up @@ -694,3 +798,9 @@ func handlerLookup(t *testing.T, op *restapi.Operation, lookup string) common.HT

return nil
}

type httpMock func(req *http.Request) (*http.Response, error)

func (m httpMock) Do(req *http.Request) (*http.Response, error) {
return m(req)
}
23 changes: 17 additions & 6 deletions pkg/webfinger/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,22 @@ func New(opts ...Option) *Client {
client.ledgerTypeCache = gcache.New(client.cacheSize).
Expiration(client.cacheLifetime).
LoaderFunc(func(key interface{}) (interface{}, error) {
return client.getLedgerType(key.(string))
lt, err := client.getLedgerType(key.(string))
if err != nil {
logger.Debugf("failed to load ledger type for domain '%s' into cache: %s", key.(string), err.Error())

return nil, err
}

logger.Debugf("loaded ledger type for domain '%s' into cache: %s", key.(string), lt)

return lt, nil
}).Build()

return client
}

// GetLedgerType returns ledger type for domain.
// GetLedgerType returns ledger type for VCT domain.
func (c *Client) GetLedgerType(domain string) (string, error) {
ledgerTypeObj, err := c.ledgerTypeCache.Get(domain)
if err != nil {
Expand All @@ -76,11 +85,13 @@ func (c *Client) GetLedgerType(domain string) (string, error) {
return ledgerTypeObj.(string), nil
}

// GetLedgerType returns ledger type for domain.
// GetLedgerType returns ledger type.
func (c *Client) getLedgerType(domain string) (string, error) {
jrd, err := c.ResolveWebFingerResource(domain, fmt.Sprintf("%s/vct", domain))
resource := fmt.Sprintf("%s/vct", domain)

jrd, err := c.ResolveWebFingerResource(domain, resource)
if err != nil {
return "", fmt.Errorf("failed to resolve WebFinger resource: %w", err)
return "", fmt.Errorf("failed to resolve WebFinger resource[%s]: %w", resource, err)
}

ltRaw, ok := jrd.Properties[command.LedgerType]
Expand All @@ -90,7 +101,7 @@ func (c *Client) getLedgerType(domain string) (string, error) {

lt, ok := ltRaw.(string)
if !ok {
return "", fmt.Errorf("ledger type '%T' is not a string", ltRaw)
return "", fmt.Errorf("ledger type '%T' is not a string for Webfinger resource[%s]", ltRaw, resource)
}

return lt, nil
Expand Down
4 changes: 3 additions & 1 deletion pkg/webfinger/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,9 @@ func TestHasSupportedLedgerType(t *testing.T) {
t.Run("success - no ledger type not found", func(t *testing.T) {
httpClient := httpMock(func(req *http.Request) (*http.Response, error) {
return &http.Response{
Body: ioutil.NopCloser(bytes.NewBufferString(`{}`)),
Body: ioutil.NopCloser(
bytes.NewBufferString(`{}`),
),
StatusCode: http.StatusOK,
}, nil
})
Expand Down

0 comments on commit ad55594

Please sign in to comment.