fb16747c4e
This helps ensure that the code to detect specific ActivityStream types continues to live in the code-generation algorithms, reducing maintenance burdens.
142 行
4.1 KiB
Go
142 行
4.1 KiB
Go
package pub
|
|
|
|
import (
|
|
"context"
|
|
"crypto"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/go-fed/activity/vocab"
|
|
"github.com/go-fed/httpsig"
|
|
"net/http"
|
|
"net/url"
|
|
)
|
|
|
|
// ServeActivityPubObject will serve the ActivityPub object with the given IRI
|
|
// in the request. Note that requests may be signed with HTTP signatures or be
|
|
// permitted without any authentication scheme. To change this default behavior,
|
|
// use ServeActivityPubObjectWithVerificationMethod instead.
|
|
func ServeActivityPubObject(a Application, clock Clock) HandlerFunc {
|
|
return func(c context.Context, w http.ResponseWriter, r *http.Request) (bool, error) {
|
|
return serveActivityPubObject(c, a, clock, w, r, nil)
|
|
}
|
|
}
|
|
|
|
// ServeActivityPubObjectWithVerificationMethod will serve the ActivityPub
|
|
// object with the given IRI in the request. The rules for accessing the data
|
|
// are governed by the SocialAPIVerifier's behavior and may permit accessing
|
|
// data without having any credentials in the request.
|
|
func ServeActivityPubObjectWithVerificationMethod(a Application, clock Clock, verifierFn func(context.Context) SocialAPIVerifier) HandlerFunc {
|
|
return func(c context.Context, w http.ResponseWriter, r *http.Request) (bool, error) {
|
|
if verifierFn != nil {
|
|
verifier := verifierFn(c)
|
|
return serveActivityPubObject(c, a, clock, w, r, verifier)
|
|
} else {
|
|
return serveActivityPubObject(c, a, clock, w, r, nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
func serveActivityPubObject(c context.Context, a Application, clock Clock, w http.ResponseWriter, r *http.Request, verifier SocialAPIVerifier) (handled bool, err error) {
|
|
handled = isActivityPubGet(r)
|
|
if !handled {
|
|
return
|
|
}
|
|
id := r.URL
|
|
if !a.Owns(c, id) {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
return
|
|
}
|
|
var verifiedUser *url.URL
|
|
// By default, permit unsigned access to resource. however, if there is
|
|
// an HTTP Signature present, it must pass validation.
|
|
authenticated := false
|
|
authorized := false
|
|
if verifier != nil {
|
|
verifiedUser, authenticated, authorized, err = verifier.Verify(r)
|
|
if err != nil {
|
|
return
|
|
} else if authenticated && !authorized {
|
|
w.WriteHeader(http.StatusForbidden)
|
|
return
|
|
} else if !authenticated && !authorized {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
} else if !authenticated && authorized {
|
|
// Protect against bad implementations: There is no
|
|
// recognized reason for an implementation to pass back
|
|
// a non-nil verifiedUser that is authorized but not
|
|
// authenticated.
|
|
//
|
|
// Force HTTP Signature validation to trigger by
|
|
// ensuring the verifiedUser is nil.
|
|
if verifiedUser != nil {
|
|
verifiedUser = nil
|
|
}
|
|
}
|
|
}
|
|
if verifiedUser == nil {
|
|
var v httpsig.Verifier
|
|
v, err = httpsig.NewVerifier(r)
|
|
if err != nil { // Unsigned request
|
|
if !authenticated && authorized { // Must pass HTTP Signature verification
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
err = nil
|
|
return
|
|
} // Else permit unsigned requests access
|
|
} else { // Signed request
|
|
var publicKey crypto.PublicKey
|
|
var algo httpsig.Algorithm
|
|
var user *url.URL
|
|
publicKey, algo, user, err = a.GetPublicKey(c, v.KeyId())
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = v.Verify(publicKey, algo)
|
|
if err != nil && !authenticated { // Failed and must pass HTTP Signature verification
|
|
w.WriteHeader(http.StatusForbidden)
|
|
err = nil
|
|
return
|
|
} else if err == nil {
|
|
verifiedUser = user
|
|
} // Else failed HTTP Signature verification but we still allow access.
|
|
}
|
|
}
|
|
var pObj PubObject
|
|
if verifiedUser != nil {
|
|
pObj, err = a.GetAsVerifiedUser(c, r.URL, verifiedUser, Read)
|
|
} else {
|
|
pObj, err = a.Get(c, r.URL, Read)
|
|
}
|
|
if err != nil {
|
|
return
|
|
}
|
|
if obj, ok := pObj.(vocab.ObjectType); ok {
|
|
clearSensitiveFields(obj)
|
|
}
|
|
var m map[string]interface{}
|
|
m, err = pObj.Serialize()
|
|
if err != nil {
|
|
return
|
|
}
|
|
addJSONLDContext(m)
|
|
var b []byte
|
|
b, err = json.Marshal(m)
|
|
if err != nil {
|
|
return
|
|
}
|
|
addResponseHeaders(w.Header(), clock, b)
|
|
if vocab.HasTypeTombstone(pObj) {
|
|
w.WriteHeader(http.StatusGone)
|
|
} else {
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
n, err := w.Write(b)
|
|
if err != nil {
|
|
return
|
|
} else if n != len(b) {
|
|
err = fmt.Errorf("ResponseWriter.Write wrote %d of %d bytes", n, len(b))
|
|
return
|
|
}
|
|
return
|
|
}
|