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

fix: Post one "Like" activity with multiple URIs in "to" field #787

Merged
merged 1 commit into from
Sep 22, 2021
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
8 changes: 8 additions & 0 deletions pkg/discovery/endpoint/restapi/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,8 @@ func (o *Operation) writeResponseForResourceRequest(rw http.ResponseWriter, reso
func (o *Operation) handleDIDOrbQuery(rw http.ResponseWriter, resource string) {
anchorInfo, err := o.GetAnchorInfo(resource)
if err != nil {
logger.Warnf("Error getting anchor info for [%s]: %s", resource, err)

writeErrorResponse(rw, http.StatusInternalServerError,
fmt.Sprintf("failed to get info on %s: %s", resource, err.Error()))

Expand Down Expand Up @@ -328,6 +330,12 @@ func (o *Operation) handleWebCASQuery(rw http.ResponseWriter, resource string) {

cid := resourceSplitBySlash[len(resourceSplitBySlash)-1]

if cid == "" {
writeErrorResponse(rw, http.StatusBadRequest, "resource ID not provided in request")

return
}

// Ensure that the CID is resolvable.
_, err := o.cas.Read(cid)
if err != nil {
Expand Down
9 changes: 9 additions & 0 deletions pkg/discovery/endpoint/restapi/operations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,15 @@ func TestWebFinger(t *testing.T) {
require.Equal(t, http.StatusNotFound, rr.Code)
})

t.Run("No resource ID in request", func(t *testing.T) {
casClient.ReadReturns(nil, orberrors.ErrContentNotFound)

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

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

t.Run("CAS error", func(t *testing.T) {
casClient.ReadReturns(nil, errors.New("injected CAS client error"))

Expand Down
20 changes: 10 additions & 10 deletions pkg/observer/observer.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,31 +341,31 @@ func (o *Observer) saveAnchorLinkAndPostLikeActivity(anchor *anchorinfo.AnchorIn
return fmt.Errorf("new like result for local hashlink: %w", err)
}

err = o.doPostLikeActivity(attributedTo, refURL, result)
if err != nil {
return fmt.Errorf("post like: %w", err)
}
logger.Debugf("Posting a 'Like' to the actor attributed to this activity [%s]", attributedTo)

to := []*url.URL{attributedTo}

// Also post a 'Like' to the creator of the anchor credential (if it's not the same as the actor above).
originActor, err := o.resolveActorFromHashlink(refURL.String())
if err != nil {
return fmt.Errorf("resolve origin actor for hashlink [%s]: %w", refURL, err)
}

if anchor.AttributedTo == originActor.String() {
// Already posted a 'Like' to this actor.
return nil
if anchor.AttributedTo != originActor.String() {
logger.Debugf("Also posting a 'Like' to the origin of this activity [%s]", originActor)

to = append(to, originActor)
}

err = o.doPostLikeActivity(originActor, refURL, result)
err = o.doPostLikeActivity(to, refURL, result)
if err != nil {
return fmt.Errorf("post 'Like' activity to outbox for hashlink [%s]: %w", refURL, err)
}

return nil
}

func (o *Observer) doPostLikeActivity(to, refURL *url.URL, result *vocab.ObjectProperty) error {
func (o *Observer) doPostLikeActivity(to []*url.URL, refURL *url.URL, result *vocab.ObjectProperty) error {
publishedTime := time.Now()

like := vocab.NewLikeActivity(
Expand All @@ -374,7 +374,7 @@ func (o *Observer) doPostLikeActivity(to, refURL *url.URL, result *vocab.ObjectP
vocab.WithURL(refURL),
)),
),
vocab.WithTo(to, vocab.PublicIRI),
vocab.WithTo(append(to, vocab.PublicIRI)...),
vocab.WithPublishedTime(&publishedTime),
vocab.WithResult(result),
)
Expand Down
12 changes: 6 additions & 6 deletions test/bdd/common_steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ func (d *CommonSteps) httpGet(url string) error {
}

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("received status code %d", resp.StatusCode)
return fmt.Errorf("received status code %d: %s", resp.StatusCode, resp.ErrorMsg)
}

d.state.setResponse(string(resp.Payload))
Expand All @@ -434,7 +434,7 @@ func (d *CommonSteps) httpGetWithSignature(url, pubKeyID string) error {
}

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("received status code %d", resp.StatusCode)
return fmt.Errorf("received status code %d: %s", resp.StatusCode, resp.ErrorMsg)
}

d.state.setResponse(string(resp.Payload))
Expand Down Expand Up @@ -464,7 +464,7 @@ func (d *CommonSteps) httpPostFile(url, path string) error {
}

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("received status code %d", resp.StatusCode)
return fmt.Errorf("received status code %d: %s", resp.StatusCode, resp.ErrorMsg)
}

return nil
Expand Down Expand Up @@ -492,7 +492,7 @@ func (d *CommonSteps) httpPostFileWithSignature(url, path, pubKeyID string) erro
}

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("received status code %d", resp.StatusCode)
return fmt.Errorf("received status code %d: %s", resp.StatusCode, resp.ErrorMsg)
}

return nil
Expand Down Expand Up @@ -529,7 +529,7 @@ func (d *CommonSteps) httpPost(url, data, contentType string) error {
}

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("received status code %d", resp.StatusCode)
return fmt.Errorf("received status code %d: %s", resp.StatusCode, resp.ErrorMsg)
}

d.state.setResponse(string(resp.Payload))
Expand All @@ -551,7 +551,7 @@ func (d *CommonSteps) httpPostWithSignature(url, data, contentType, domain strin
}

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("received status code %d", resp.StatusCode)
return fmt.Errorf("received status code %d: %s", resp.StatusCode, resp.ErrorMsg)
}

d.state.setResponse(string(resp.Payload))
Expand Down
4 changes: 2 additions & 2 deletions test/bdd/did_orb_steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ func (d *DIDOrbSteps) discoverEndpoints() error {
}

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("received status code %d", resp.StatusCode)
return fmt.Errorf("received status code %d: %s", resp.StatusCode, resp.ErrorMsg)
}

var w restapi.WellKnownResponse
Expand All @@ -186,7 +186,7 @@ func (d *DIDOrbSteps) discoverEndpoints() error {
}

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("received status code %d", resp.StatusCode)
return fmt.Errorf("received status code %d: %s", resp.StatusCode, resp.ErrorMsg)
}

var webFingerResponse restapi.JRD
Expand Down
17 changes: 9 additions & 8 deletions test/bdd/features/did-orb.feature
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,11 @@ Feature:
And the authorization bearer token for "POST" requests to path "/policy" is set to "ADMIN_TOKEN"

# domain2 server follows domain1 server
Given variable "followID" is assigned a unique ID
And variable "followActivity" is assigned the JSON value '{"@context":"https://www.w3.org/ns/activitystreams","id":"${domain2IRI}/activities/${followID}","type":"Follow","actor":"${domain2IRI}","to":"${domain1IRI}","object":"${domain1IRI}"}'
And variable "followActivity" is assigned the JSON value '{"@context":"https://www.w3.org/ns/activitystreams","type":"Follow","actor":"${domain2IRI}","to":"${domain1IRI}","object":"${domain1IRI}"}'
When an HTTP POST is sent to "https://orb.domain2.com/services/orb/outbox" with content "${followActivity}" of type "application/json"

# domain1 server follows domain2 server
Given variable "followID" is assigned a unique ID
And variable "followActivity" is assigned the JSON value '{"@context":"https://www.w3.org/ns/activitystreams","id":"${domain1IRI}/activities/${followID}","type":"Follow","actor":"${domain1IRI}","to":"${domain2IRI}","object":"${domain2IRI}"}'
And variable "followActivity" is assigned the JSON value '{"@context":"https://www.w3.org/ns/activitystreams","type":"Follow","actor":"${domain1IRI}","to":"${domain2IRI}","object":"${domain2IRI}"}'
When an HTTP POST is sent to "https://orb.domain1.com/services/orb/outbox" with content "${followActivity}" of type "application/json"

# domain3 server follows domain1 server. Domain3 needs to be a follower of domain1 so that HTTP signature validation succeeds when
Expand All @@ -40,13 +38,11 @@ Feature:
When an HTTP POST is sent to "https://orb.domain3.com/services/orb/outbox" with content "${followActivity}" of type "application/json"

# domain1 invites domain2 to be a witness
Given variable "inviteWitnessID" is assigned a unique ID
And variable "inviteWitnessActivity" is assigned the JSON value '{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/activityanchors/v1"],"id":"${domain1IRI}/activities/${inviteWitnessID}","type":"Invite","actor":"${domain1IRI}","to":"${domain2IRI}","object":"https://w3id.org/activityanchors#AnchorWitness","target":"${domain2IRI}"}'
And variable "inviteWitnessActivity" is assigned the JSON value '{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/activityanchors/v1"],"type":"Invite","actor":"${domain1IRI}","to":"${domain2IRI}","object":"https://w3id.org/activityanchors#AnchorWitness","target":"${domain2IRI}"}'
When an HTTP POST is sent to "https://orb.domain1.com/services/orb/outbox" with content "${inviteWitnessActivity}" of type "application/json"

# domain2 invites domain1 to be a witness
Given variable "inviteWitnessID" is assigned a unique ID
And variable "inviteWitnessActivity" is assigned the JSON value '{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/activityanchors/v1"],"id":"${domain2IRI}/activities/${inviteWitnessID}","type":"Invite","actor":"${domain2IRI}","to":"${domain1IRI}","object":"https://w3id.org/activityanchors#AnchorWitness","target":"${domain1IRI}"}'
And variable "inviteWitnessActivity" is assigned the JSON value '{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/activityanchors/v1"],"type":"Invite","actor":"${domain2IRI}","to":"${domain1IRI}","object":"https://w3id.org/activityanchors#AnchorWitness","target":"${domain1IRI}"}'
When an HTTP POST is sent to "https://orb.domain2.com/services/orb/outbox" with content "${inviteWitnessActivity}" of type "application/json"

# set witness policy for domain1
Expand Down Expand Up @@ -318,6 +314,11 @@ Feature:
And the JSON path 'links.#(rel=="via").href' of the response is saved to variable "anchorLink"
And variable "anchorHash" is assigned the value "$hashlink(|${anchorLink}|).ResourceHash"

# domain3 is following domain1 so it should also have the DID.
When an HTTP GET is sent to "https://orb.domain3.com/.well-known/webfinger?resource=${didID}"
And the JSON path "links.#.href" of the response contains expression ".*orb\.domain1\.com.*"
And the JSON path "links.#.href" of the response contains expression ".*orb\.domain3\.com.*"

When an HTTP GET is sent to "https://orb.domain1.com/.well-known/webfinger?resource=https://orb.domain1.com/cas/${anchorHash}"
And the JSON path 'links.#(rel=="self").href' of the response equals "https://orb.domain1.com/cas/${anchorHash}"
And the JSON path "links.#.href" of the response contains expression ".*orb\.domain2\.com.*"
Expand Down