Commit b8bc4420 authored by Ghost User's avatar Ghost User Committed by Lukas Matt

Implement ActivityPub

parent d2cb30af
image: golang:1.9
variables:
SRC_DIR: "/go/src/git.feneas.org/ganggo"
BUILD_DIR: "/builds/ganggo/federation"
before_script:
- mkdir -p $SRC_DIR
- cp -r ${BUILD_DIR} ${SRC_DIR}/federation
- cd ${SRC_DIR}/federation
- go get -t -v ./...
run unit tests:
before_script:
- mkdir -p $SRC_DIR
- cp -r /builds/ganggo/federation ${SRC_DIR}/federation
- cd ${SRC_DIR}/federation
- go get -t -v ./...
script:
- go test -v -race -covermode=atomic
- cat gotest.log
sast:
image: registry.gitlab.com/gitlab-org/security-products/analyzers/gosec:11-3-stable
allow_failure: true
script:
- /analyzer run
artifacts:
paths: [gl-sast-report.json]
package federation
//
// GangGo Diaspora Federation Library
// Copyright (C) 2017 Lukas Matt <lukas@zauberstuhl.de>
// GangGo Federation Library
// Copyright (C) 2017-2018 Lukas Matt <lukas@zauberstuhl.de>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
......@@ -17,31 +17,30 @@ package federation
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
import "github.com/Zauberstuhl/go-xml"
import (
"crypto/rsa"
"encoding/json"
)
type EntityComment struct {
XMLName xml.Name `xml:"comment"`
Author string `xml:"author"`
CreatedAt Time `xml:"created_at"`
Guid string `xml:"guid"`
ParentGuid string `xml:"parent_guid"`
Text string `xml:"text"`
AuthorSignature string `xml:"author_signature"`
type ActivityPubAccept struct {
ActivityPubContext
Actor string `json:"actor"`
Object ActivityPubFollow `json:"object"`
}
func (e EntityComment) Signature() string {
return e.AuthorSignature
func (e *ActivityPubAccept) Unmarshal(b []byte) error {
return json.Unmarshal(b, e)
}
func (e EntityComment) SignatureText(order string) (signatureOrder []string) {
if order != "" {
return ExractSignatureText(order, e)
}
return []string{
e.Author,
e.CreatedAt.String(),
e.Guid,
e.ParentGuid,
e.Text,
func (e *ActivityPubAccept) Marshal(priv *rsa.PrivateKey, pub *rsa.PublicKey) ([]byte, error) {
e.ActivityPubContext = ActivityPubContext{
Context: []interface{}{ACTIVITY_STREAMS},
ActivityPubBase: ActivityPubBase{
Id: e.Id,
Type: ActivityTypeAccept,
},
}
b, err := json.MarshalIndent(e, "", " ")
Log.Info("ActivityPubAccept", string(b))
return b, err
}
......@@ -17,11 +17,12 @@ package federation
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
import "github.com/Zauberstuhl/go-xml"
type ActivityPubBase struct {
Id string `json:"id"`
Type string `json:"type"`
}
type EntityRetraction struct {
XMLName xml.Name `xml:"retraction"`
Author string `xml:"author"`
TargetGuid string `xml:"target_guid"`
TargetType string `xml:"target_type"`
type ActivityPubContext struct {
Context []interface{} `json:"@context"`
ActivityPubBase
}
......@@ -17,17 +17,17 @@ package federation
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
type EntityPhoto struct {
Guid string `xml:"guid"`
Author string `xml:"author"`
Public bool `xml:"public"`
CreatedAt Time `xml:"created_at"`
RemotePhotoPath string `xml:"remote_photo_path"`
RemotePhotoName string `xml:"remote_photo_name"`
Text string `xml:"text"`
StatusMessageGuid string `xml:"status_message_guid"`
Height int `xml:"height"`
Width int `xml:"width"`
type ActivityPubCollection struct {
ActivityPubContext
TotalItems int `json:"totalItems"`
First *string `json:"first,omitempty"`
}
type EntityPhotos []EntityPhoto
type ActivityPubCollectionPage struct {
ActivityPubContext
TotalItems int `json:"totalItems"`
Next *string `json:"next,omitempty"`
PartOf string `json:"partOf"`
Items interface{} `json:"items,omitempty"`
OrderedItems interface{} `json:"orderedItems,omitempty"`
}
package federation
//
// GangGo Federation Library
// Copyright (C) 2017-2018 Lukas Matt <lukas@zauberstuhl.de>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
import (
"net/http"
"crypto/rsa"
"encoding/json"
"bytes"
"git.feneas.org/ganggo/federation/helpers"
"fmt"
)
type ActivityPubCreate struct {
ActivityPubContext `json:",omitempty"`
// Id string `json:"id"`
Actor string `json:"actor"`
Published helpers.Time `json:"published"`
To []string `json:"to"`
Cc []string `json:"cc"`
Object ActivityPubNote `json:"object"`
}
type ActivityPubNote struct {
ActivityPubBase `json:",omitempty"`
// Id string `json:"id"`
Summary string `json:"summary"`
Content string `json:"content"`
InReplyTo string `json:"inReplyTo"`
Published helpers.Time `json:"published"`
Url string `json:"url"`
AttributedTo string `json:"attributedTo"`
// NOTE mastodon is not using it ??
// see Public method for current implementation
Sensitive bool `json:"sensitive"`
To []string `json:"to"`
Cc []string `json:"cc"`
Attachment []ActivityPubAttachment `json:"attachment,omitempty"`
Tags *ActivityPubNoteTags `json:"tag,omitempty"`
}
type ActivityPubAttachment struct {
Type string `json:"type"`
MediaType string `json:"mediaType"`
Url string `json:"url"`
}
type ActivityPubNoteTag struct {
Type string `json:"type"`
Href string `json:"href"`
Name string `json:"name"`
}
type ActivityPubNoteTags []ActivityPubNoteTag
func (e *ActivityPubCreate) Author() string { return e.Actor }
func (e *ActivityPubCreate) SetAuthor(author string) {
username, err := helpers.ParseHandle(author)
if err != nil {
(*e).Actor = author
} else {
(*e).Actor = fmt.Sprintf(config.ApURLFormat,
fmt.Sprintf("user/%s/actor", username))
}
(*e).Object.AttributedTo = e.Actor
}
func (e *ActivityPubCreate) Send(inbox string, priv *rsa.PrivateKey, pub *rsa.PublicKey) error {
payload, err := e.Marshal(priv, pub)
if err != nil {
return err
}
client := (&HttpClient{}).New(e.Author() + "#main-key", priv)
return client.Push(inbox, http.Header{
"Content-Type": []string{CONTENT_TYPE_JSON},
}, bytes.NewBuffer(payload))
}
func (e *ActivityPubCreate) Unmarshal(b []byte) error {
err := json.Unmarshal(b, e)
Log.Info("ActivityPubCreate Unmarshal", *e)
return err
}
func (e *ActivityPubCreate) Marshal(priv *rsa.PrivateKey, pub *rsa.PublicKey) ([]byte, error) {
e.ActivityPubContext = ActivityPubContext{
[]interface{}{ACTIVITY_STREAMS}, ActivityPubBase{
Id: e.Id, Type: ActivityTypeCreate,
},
}
e.Object.Type = ActivityTypeNote
b, err := json.MarshalIndent(e, "", " ")
Log.Info("ActivityPubCreate", string(b))
return b, err
}
func (e *ActivityPubCreate) Recipients() []string {
return e.To
}
func (e *ActivityPubCreate) SetRecipients(recipients []string) {
(*e).To = recipients
(*e).Object.To = e.To
(*e).Cc = []string{}
(*e).Object.Cc = e.Cc
(*e).Object.AttributedTo = e.Actor
tags := ActivityPubNoteTags{}
for _, recipient := range recipients {
tags = append(tags, ActivityPubNoteTag{
Type: "Mention", Href: recipient, Name: recipient,
})
}
(*e).Object.Tags = &tags
}
package federation
//
// GangGo Federation Library
// Copyright (C) 2017-2018 Lukas Matt <lukas@zauberstuhl.de>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
import (
"crypto/rsa"
"git.feneas.org/ganggo/federation/helpers"
"time"
"fmt"
)
type ActivityPubCreateComment struct {
ActivityPubCreate
}
func (e *ActivityPubCreateComment) Type() MessageType {
return MessageType{
Proto: ActivityPubProtocol,
Entity: Comment,
}
}
func (e *ActivityPubCreateComment) SetAuthor(author string) {
username, err := helpers.ParseHandle(author)
if err != nil {
(*e).Actor = author
} else {
(*e).Actor = fmt.Sprintf(config.ApURLFormat,
fmt.Sprintf("user/%s", username))
}
if len(e.To) == 0 {
(*e).To = []string{ACTIVITY_STREAMS_PUBLIC}
(*e).Object.To = e.To
(*e).Cc = []string{e.Actor + "/followers"}
(*e).Object.Cc = (*e).Cc
}
(*e).Actor = e.Actor + "/actor"
}
func (e *ActivityPubCreateComment) Guid() string {
return helpers.LinkToGuid(e.Object.Id)
}
func (e *ActivityPubCreateComment) SetGuid(guid string) {
link, err := helpers.GuidToLink(guid)
if err != nil {
(*e).Object.Id = fmt.Sprintf(config.GuidURLFormat, guid)
} else {
(*e).Object.Id = link
}
(*e).Id = e.Object.Id + "#create"
}
func (e *ActivityPubCreateComment) Parent() string {
parts, err := helpers.ParseStringHelper(e.Object.InReplyTo, config.GuidURLRegExp(), 1)
if err != nil {
Log.Info(err)
return helpers.LinkToGuid(e.Object.InReplyTo)
}
return parts[1]
}
func (e *ActivityPubCreateComment) SetParent(guid string) {
link, err := helpers.GuidToLink(guid)
if err == nil {
(*e).Object.InReplyTo = link
} else {
(*e).Object.InReplyTo = fmt.Sprintf(config.GuidURLFormat, guid)
}
(*e).Object.Url = e.Object.InReplyTo
}
func (e *ActivityPubCreateComment) Signature() string { return "" }
func (e *ActivityPubCreateComment) SetSignature(priv *rsa.PrivateKey) error {
return nil
}
func (e *ActivityPubCreateComment) SignatureOrder() string { return "" }
func (e *ActivityPubCreateComment) CreatedAt() helpers.Time {
return e.Object.Published
}
func (e *ActivityPubCreateComment) SetCreatedAt(createdAt time.Time) {
e.Published.New(createdAt)
e.Object.Published.New(createdAt)
}
func (e *ActivityPubCreateComment) Text() string {
return e.Object.Content
}
func (e *ActivityPubCreateComment) SetText(text string) {
(*e).Object.Content = text
}
package federation
//
// GangGo Federation Library
// Copyright (C) 2017-2018 Lukas Matt <lukas@zauberstuhl.de>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
import (
"git.feneas.org/ganggo/federation/helpers"
"time"
"fmt"
)
type ActivityPubCreatePost struct {
ActivityPubCreate
}
func (e *ActivityPubCreatePost) Type() MessageType {
return MessageType{
Proto: ActivityPubProtocol,
Entity: StatusMessage,
}
}
func (e *ActivityPubCreatePost) SetAuthor(author string) {
username, err := helpers.ParseHandle(author)
if err != nil {
(*e).Actor = author
} else {
(*e).Actor = fmt.Sprintf(config.ApURLFormat,
fmt.Sprintf("user/%s", username))
}
if len(e.To) == 0 {
(*e).To = []string{ACTIVITY_STREAMS_PUBLIC}
(*e).Object.To = e.To
(*e).Cc = []string{e.Actor + "/followers"}
(*e).Object.Cc = (*e).Cc
}
(*e).Actor = e.Actor + "/actor"
(*e).Object.AttributedTo = e.Actor
}
func (e *ActivityPubCreatePost) Guid() string {
return helpers.LinkToGuid(e.Object.Id)
}
func (e *ActivityPubCreatePost) SetGuid(guid string) {
link, err := helpers.GuidToLink(guid)
if err != nil {
(*e).Object.Id = fmt.Sprintf(config.GuidURLFormat, guid)
} else {
(*e).Object.Id = link
}
(*e).Id = e.Object.Id + "#create"
(*e).Object.Url = e.Object.Id
}
func (e *ActivityPubCreatePost) CreatedAt() helpers.Time {
return e.Object.Published
}
func (e *ActivityPubCreatePost) SetCreatedAt(createdAt time.Time) {
e.Published.New(createdAt)
e.Object.Published.New(createdAt)
}
func (e *ActivityPubCreatePost) Provider() string { return "" }
func (e *ActivityPubCreatePost) SetProvider(provider string) {}
func (e *ActivityPubCreatePost) Text() string {
return e.Object.Content
}
func (e *ActivityPubCreatePost) SetText(text string) {
(*e).Object.Content = text
}
func (e *ActivityPubCreatePost) Public() bool {
for _, to := range e.Object.To {
if to == ACTIVITY_STREAMS_PUBLIC {
return true
}
}
return false
}
func (e *ActivityPubCreatePost) SetPublic(public bool) {}
func (e *ActivityPubCreatePost) FilePaths() (files []string) {
for _, attachment := range e.Object.Attachment {
files = append(files, attachment.Url)
}
return
}
package federation
//
// GangGo Federation Library
// Copyright (C) 2017-2018 Lukas Matt <lukas@zauberstuhl.de>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
import (
"net/http"
"crypto/rsa"
"encoding/json"
"bytes"
"fmt"
"git.feneas.org/ganggo/federation/helpers"
)
type ActivityPubFollow struct {
ActivityPubContext
Actor string `json:"actor"`
Object string `json:"object"`
Following helpers.ReadOnlyBool
}
func (e *ActivityPubFollow) Author() string { return e.Actor }
func (e *ActivityPubFollow) SetAuthor(author string) {
username, err := helpers.ParseHandle(author)
if err != nil {
(*e).Actor = author
} else {
(*e).Actor = fmt.Sprintf(config.ApURLFormat,
fmt.Sprintf("user/%s/actor", username))
}
(*e).Id = e.Actor + "#follow"
}
func (e *ActivityPubFollow) Send(inbox string, priv *rsa.PrivateKey, pub *rsa.PublicKey) error {
payload, err := e.Marshal(priv, pub)
if err != nil {
return err
}
client := (&HttpClient{}).New(e.Author() + "#main-key", priv)
return client.Push(inbox, http.Header{
"Content-Type": []string{CONTENT_TYPE_JSON},
}, bytes.NewBuffer(payload))
}
func (e *ActivityPubFollow) Unmarshal(b []byte) error {
return json.Unmarshal(b, e)
}
func (e *ActivityPubFollow) Marshal(priv *rsa.PrivateKey, pub *rsa.PublicKey) ([]byte, error) {
e.ActivityPubContext = ActivityPubContext{
Context: []interface{}{ACTIVITY_STREAMS},
ActivityPubBase: ActivityPubBase{
Id: e.Id,
Type: ActivityTypeFollow,
},
}
b, err := json.MarshalIndent(e, "", " ")
Log.Info("ActivityPubFollow", string(b))
return b, err
}
func (e *ActivityPubFollow) Type() MessageType {
return MessageType{
Proto: ActivityPubProtocol,
Entity: Contact,
}
}
func (e *ActivityPubFollow) Recipient() string {
return e.Object
}
func (e *ActivityPubFollow) SetRecipient(recipient string) {
(*e).Object = recipient
}
func (e *ActivityPubFollow) Sharing() bool { return bool(e.Following) }
func (e *ActivityPubFollow) SetSharing(sharing bool) {}
package federation
//
// GangGo Federation Library
// Copyright (C) 2017-2018 Lukas Matt <lukas@zauberstuhl.de>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
import (
"net/http"
"crypto/rsa"
"encoding/json"
"bytes"
"fmt"
"git.feneas.org/ganggo/federation/helpers"
)
type ActivityPubLike struct {
ActivityPubContext
Actor string `json:"actor"`
Object string `json:"object"`
}
func (e *ActivityPubLike) Author() string { return e.Actor }
func (e *ActivityPubLike) SetAuthor(author string) {
username, err := helpers.ParseHandle(author)
if err != nil {
(*e).Actor = author
} else {
(*e).Actor = fmt.Sprintf(config.ApURLFormat,
fmt.Sprintf("user/%s/actor", username))
}
}
func (e *ActivityPubLike) Send(inbox string, priv *rsa.PrivateKey, pub *rsa.PublicKey) error {
payload, err := e.Marshal(priv, pub)
if err != nil {
return err
}
client := (&HttpClient{}).New(e.Author() + "#main-key", priv)
return client.Push(inbox, http.Header{
"Content-Type": []string{CONTENT_TYPE_JSON},
}, bytes.NewBuffer(payload))
}
func (e *ActivityPubLike) Unmarshal(b []byte) error {
return json.Unmarshal(b, e)
}
func (e *ActivityPubLike) Marshal(priv *rsa.PrivateKey, pub *rsa.PublicKey) ([]byte, error) {
e.ActivityPubContext = ActivityPubContext{
Context: []interface{}{ACTIVITY_STREAMS},
ActivityPubBase: ActivityPubBase{
Id: e.Id,
Type: ActivityTypeLike,
},
}
b, err := json.MarshalIndent(e, "", " ")
Log.Info("ActivityPubLike", string(b))
return b, err
}
func (e *ActivityPubLike) Type() MessageType {
return MessageType{
Proto: ActivityPubProtocol,
Entity: Like,
}
}
func (e *ActivityPubLike) Guid() string {
return helpers.LinkToGuid(e.Id)
}
func (e *ActivityPubLike) SetGuid(guid string) {
link, err := helpers.GuidToLink(guid)
if err != nil {
if e.Actor == "" {