Verify ActivityPub requests

This commit is contained in:
Jan-Lukas Else 2020-11-13 15:19:09 +01:00
parent 0cb142bd20
commit 9e5af4b8a7
2 changed files with 75 additions and 13 deletions

View File

@ -85,13 +85,31 @@ func apHandleInbox(w http.ResponseWriter, r *http.Request) {
return return
} }
blogIri := blog.apIri() blogIri := blog.apIri()
// Verify request
requestActor, err := apVerifySignature(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Parse activity
activity := make(map[string]interface{}) activity := make(map[string]interface{})
err := json.NewDecoder(r.Body).Decode(&activity) err = json.NewDecoder(r.Body).Decode(&activity)
_ = r.Body.Close() _ = r.Body.Close()
if err != nil { if err != nil {
http.Error(w, "Failed to decode body", http.StatusBadRequest) http.Error(w, "Failed to decode body", http.StatusBadRequest)
return return
} }
// Get and check activity actor
activityActor, ok := activity["actor"].(string)
if !ok {
http.Error(w, "actor in activity is no string", http.StatusBadRequest)
return
}
if activityActor != requestActor.ID {
http.Error(w, "Request actor isn't activity actor", http.StatusForbidden)
return
}
// Do
switch activity["type"] { switch activity["type"] {
case "Follow": case "Follow":
apAccept(blogName, blog, activity) apAccept(blogName, blog, activity)
@ -99,8 +117,8 @@ func apHandleInbox(w http.ResponseWriter, r *http.Request) {
{ {
if object, ok := activity["object"].(map[string]interface{}); ok { if object, ok := activity["object"].(map[string]interface{}); ok {
if objectType, ok := object["type"].(string); ok && objectType == "Follow" { if objectType, ok := object["type"].(string); ok && objectType == "Follow" {
if iri, ok := object["actor"].(string); ok && iri == activity["actor"] { if iri, ok := object["actor"].(string); ok && iri == activityActor {
_ = apRemoveFollower(blogName, iri) _ = apRemoveFollower(blogName, activityActor)
} }
} }
} }
@ -128,30 +146,54 @@ func apHandleInbox(w http.ResponseWriter, r *http.Request) {
case "Delete": case "Delete":
case "Block": case "Block":
{ {
if object, ok := activity["object"].(string); ok && len(object) > 0 && activity["actor"] == object { if object, ok := activity["object"].(string); ok && len(object) > 0 && object == activityActor {
_ = apRemoveFollower(blogName, object) _ = apRemoveFollower(blogName, activityActor)
} }
} }
case "Like": case "Like":
{ {
likeActor, likeActorOk := activity["actor"].(string)
likeObject, likeObjectOk := activity["object"].(string) likeObject, likeObjectOk := activity["object"].(string)
if likeActorOk && likeObjectOk && len(likeActor) > 0 && len(likeObject) > 0 && strings.Contains(likeObject, blogIri) { if likeObjectOk && len(likeObject) > 0 && strings.Contains(likeObject, blogIri) {
sendNotification(fmt.Sprintf("%s liked %s", likeActor, likeObject)) sendNotification(fmt.Sprintf("%s liked %s", activityActor, likeObject))
} }
} }
case "Announce": case "Announce":
{ {
announceActor, announceActorOk := activity["actor"].(string)
announceObject, announceObjectOk := activity["object"].(string) announceObject, announceObjectOk := activity["object"].(string)
if announceActorOk && announceObjectOk && len(announceActor) > 0 && len(announceObject) > 0 && strings.Contains(announceObject, blogIri) { if announceObjectOk && len(announceObject) > 0 && strings.Contains(announceObject, blogIri) {
sendNotification(fmt.Sprintf("%s announced %s", announceActor, announceObject)) sendNotification(fmt.Sprintf("%s announced %s", activityActor, announceObject))
} }
} }
} }
// Return 201 // Return 201
w.WriteHeader(http.StatusCreated) w.WriteHeader(http.StatusCreated)
}
func apVerifySignature(r *http.Request) (*asPerson, error) {
verifier, err := httpsig.NewVerifier(r)
if err != nil {
// Error with signature header etc.
return nil, err
}
keyID := verifier.KeyId()
actor, err := apGetRemoteActor(keyID)
if err != nil {
// Actor not found or something else bad
return nil, err
}
if actor.PublicKey == nil {
return nil, errors.New("Actor has no public key")
}
block, _ := pem.Decode([]byte(actor.PublicKey.PublicKeyPem))
if block == nil {
return nil, errors.New("Public key invalid")
}
pubKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
// Unable to parse public key
return nil, err
}
return actor, verifier.Verify(pubKey, httpsig.RSA_SHA256)
} }
func handleWellKnownHostMeta(w http.ResponseWriter, r *http.Request) { func handleWellKnownHostMeta(w http.ResponseWriter, r *http.Request) {
@ -222,6 +264,11 @@ func (p *post) apPost() {
createActivity["type"] = "Create" createActivity["type"] = "Create"
createActivity["object"] = n createActivity["object"] = n
apSendToAllFollowers(p.Blog, createActivity) apSendToAllFollowers(p.Blog, createActivity)
if n.InReplyTo != "" {
// Is reply, so announce it
time.Sleep(30 * time.Second)
p.apAnnounce()
}
} }
func (p *post) apUpdate() { func (p *post) apUpdate() {
@ -233,11 +280,26 @@ func (p *post) apUpdate() {
updateActivity["@context"] = asContext updateActivity["@context"] = asContext
updateActivity["actor"] = appConfig.Blogs[p.Blog].apIri() updateActivity["actor"] = appConfig.Blogs[p.Blog].apIri()
updateActivity["id"] = appConfig.Server.PublicAddress + p.Path updateActivity["id"] = appConfig.Server.PublicAddress + p.Path
updateActivity["published"] = time.Now().Format("2006-01-02T15:04:05-07:00")
updateActivity["type"] = "Update" updateActivity["type"] = "Update"
updateActivity["object"] = n updateActivity["object"] = n
apSendToAllFollowers(p.Blog, updateActivity) apSendToAllFollowers(p.Blog, updateActivity)
} }
func (p *post) apAnnounce() {
if !appConfig.ActivityPub.Enabled {
return
}
announceActivity := make(map[string]interface{})
announceActivity["@context"] = asContext
announceActivity["actor"] = appConfig.Blogs[p.Blog].apIri()
announceActivity["id"] = appConfig.Server.PublicAddress + p.Path + "#announce"
announceActivity["published"] = p.toASNote().Published
announceActivity["type"] = "Announce"
announceActivity["object"] = appConfig.Server.PublicAddress + p.Path
apSendToAllFollowers(p.Blog, announceActivity)
}
func (p *post) apDelete() { func (p *post) apDelete() {
if !appConfig.ActivityPub.Enabled { if !appConfig.ActivityPub.Enabled {
return return

View File

@ -88,7 +88,7 @@ func (p *post) toASNote() *asNote {
// Content // Content
as.Content = string(p.html()) as.Content = string(p.html())
// Attachments // Attachments
if images := p.Parameters["images"]; len(images) > 0 { if images := p.Parameters[appConfig.Micropub.PhotoParam]; len(images) > 0 {
for _, image := range images { for _, image := range images {
as.Attachment = append(as.Attachment, &asAttachment{ as.Attachment = append(as.Attachment, &asAttachment{
Type: "Image", Type: "Image",
@ -109,7 +109,7 @@ func (p *post) toASNote() *asNote {
} }
} }
// Reply // Reply
if replyLink := p.firstParameter("replylink"); replyLink != "" { if replyLink := p.firstParameter(appConfig.Micropub.ReplyParam); replyLink != "" {
as.InReplyTo = replyLink as.InReplyTo = replyLink
} }
return as return as