summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/gotosocial/main.go32
-rw-r--r--internal/api/s2s/user/following.go58
-rw-r--r--internal/api/s2s/user/user.go3
-rw-r--r--internal/api/s2s/user/userget_test.go2
-rw-r--r--internal/db/pg/pg.go15
-rw-r--r--internal/federation/federating_db.go205
-rw-r--r--internal/federation/federatingprotocol.go2
-rw-r--r--internal/federation/util.go16
-rw-r--r--internal/message/accountprocess.go6
-rw-r--r--internal/message/fediprocess.go42
-rw-r--r--internal/message/fromfederatorprocess.go23
-rw-r--r--internal/message/frprocess.go4
-rw-r--r--internal/message/processor.go4
-rw-r--r--internal/message/processorutil.go12
-rw-r--r--internal/typeutils/asextractionutil.go35
-rw-r--r--internal/typeutils/astointernal.go22
-rw-r--r--internal/typeutils/astointernal_test.go4
-rw-r--r--internal/typeutils/converter.go8
-rw-r--r--internal/typeutils/internaltofrontend.go6
-rw-r--r--internal/util/uri.go22
20 files changed, 417 insertions, 104 deletions
diff --git a/cmd/gotosocial/main.go b/cmd/gotosocial/main.go
index a113ef2dc..1b7187809 100644
--- a/cmd/gotosocial/main.go
+++ b/cmd/gotosocial/main.go
@@ -277,16 +277,16 @@ func main() {
Usage: "create a new account",
Flags: []cli.Flag{
&cli.StringFlag{
- Name: config.UsernameFlag,
- Usage: config.UsernameUsage,
+ Name: config.UsernameFlag,
+ Usage: config.UsernameUsage,
},
&cli.StringFlag{
- Name: config.EmailFlag,
- Usage: config.EmailUsage,
+ Name: config.EmailFlag,
+ Usage: config.EmailUsage,
},
&cli.StringFlag{
- Name: config.PasswordFlag,
- Usage: config.PasswordUsage,
+ Name: config.PasswordFlag,
+ Usage: config.PasswordUsage,
},
},
Action: func(c *cli.Context) error {
@@ -298,8 +298,8 @@ func main() {
Usage: "confirm an existing account manually, thereby skipping email confirmation",
Flags: []cli.Flag{
&cli.StringFlag{
- Name: config.UsernameFlag,
- Usage: config.UsernameUsage,
+ Name: config.UsernameFlag,
+ Usage: config.UsernameUsage,
},
},
Action: func(c *cli.Context) error {
@@ -311,8 +311,8 @@ func main() {
Usage: "promote an account to admin",
Flags: []cli.Flag{
&cli.StringFlag{
- Name: config.UsernameFlag,
- Usage: config.UsernameUsage,
+ Name: config.UsernameFlag,
+ Usage: config.UsernameUsage,
},
},
Action: func(c *cli.Context) error {
@@ -324,8 +324,8 @@ func main() {
Usage: "demote an account from admin to normal user",
Flags: []cli.Flag{
&cli.StringFlag{
- Name: config.UsernameFlag,
- Usage: config.UsernameUsage,
+ Name: config.UsernameFlag,
+ Usage: config.UsernameUsage,
},
},
Action: func(c *cli.Context) error {
@@ -337,8 +337,8 @@ func main() {
Usage: "prevent an account from signing in or posting etc, but don't delete anything",
Flags: []cli.Flag{
&cli.StringFlag{
- Name: config.UsernameFlag,
- Usage: config.UsernameUsage,
+ Name: config.UsernameFlag,
+ Usage: config.UsernameUsage,
},
},
Action: func(c *cli.Context) error {
@@ -350,8 +350,8 @@ func main() {
Usage: "completely remove an account and all of its posts, media, etc",
Flags: []cli.Flag{
&cli.StringFlag{
- Name: config.UsernameFlag,
- Usage: config.UsernameUsage,
+ Name: config.UsernameFlag,
+ Usage: config.UsernameUsage,
},
},
Action: func(c *cli.Context) error {
diff --git a/internal/api/s2s/user/following.go b/internal/api/s2s/user/following.go
new file mode 100644
index 000000000..de5701f8d
--- /dev/null
+++ b/internal/api/s2s/user/following.go
@@ -0,0 +1,58 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package user
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/sirupsen/logrus"
+)
+
+func (m *Module) FollowingGETHandler(c *gin.Context) {
+ l := m.log.WithFields(logrus.Fields{
+ "func": "FollowingGETHandler",
+ "url": c.Request.RequestURI,
+ })
+
+ requestedUsername := c.Param(UsernameKey)
+ if requestedUsername == "" {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "no username specified in request"})
+ return
+ }
+
+ // make sure this actually an AP request
+ format := c.NegotiateFormat(ActivityPubAcceptHeaders...)
+ if format == "" {
+ c.JSON(http.StatusNotAcceptable, gin.H{"error": "could not negotiate format with given Accept header(s)"})
+ return
+ }
+ l.Tracef("negotiated format: %s", format)
+
+ // make a copy of the context to pass along so we don't break anything
+ cp := c.Copy()
+ user, err := m.processor.GetFediFollowing(requestedUsername, cp.Request) // handles auth as well
+ if err != nil {
+ l.Info(err.Error())
+ c.JSON(err.Code(), gin.H{"error": err.Safe()})
+ return
+ }
+
+ c.JSON(http.StatusOK, user)
+}
diff --git a/internal/api/s2s/user/user.go b/internal/api/s2s/user/user.go
index d866e47e1..e1bdb9a8d 100644
--- a/internal/api/s2s/user/user.go
+++ b/internal/api/s2s/user/user.go
@@ -44,6 +44,8 @@ const (
UsersInboxPath = UsersBasePathWithUsername + "/" + util.InboxPath
// UsersFollowersPath is for serving GET request's to a user's followers list, with the given username key.
UsersFollowersPath = UsersBasePathWithUsername + "/" + util.FollowersPath
+ // UsersFollowingPath is for serving GET request's to a user's following list, with the given username key.
+ UsersFollowingPath = UsersBasePathWithUsername + "/" + util.FollowingPath
// UsersStatusPath is for serving GET requests to a particular status by a user, with the given username key and status ID
UsersStatusPath = UsersBasePathWithUsername + "/" + util.StatusesPath + "/:" + StatusIDKey
)
@@ -76,6 +78,7 @@ func (m *Module) Route(s router.Router) error {
s.AttachHandler(http.MethodGet, UsersBasePathWithUsername, m.UsersGETHandler)
s.AttachHandler(http.MethodPost, UsersInboxPath, m.InboxPOSTHandler)
s.AttachHandler(http.MethodGet, UsersFollowersPath, m.FollowersGETHandler)
+ s.AttachHandler(http.MethodGet, UsersFollowingPath, m.FollowingGETHandler)
s.AttachHandler(http.MethodGet, UsersStatusPath, m.StatusGETHandler)
return nil
}
diff --git a/internal/api/s2s/user/userget_test.go b/internal/api/s2s/user/userget_test.go
index b45b01b63..fab490767 100644
--- a/internal/api/s2s/user/userget_test.go
+++ b/internal/api/s2s/user/userget_test.go
@@ -145,7 +145,7 @@ func (suite *UserGetTestSuite) TestGetUser() {
// convert person to account
// since this account is already known, we should get a pretty full model of it from the conversion
- a, err := suite.tc.ASRepresentationToAccount(person)
+ a, err := suite.tc.ASRepresentationToAccount(person, false)
assert.NoError(suite.T(), err)
assert.EqualValues(suite.T(), targetAccount.Username, a.Username)
}
diff --git a/internal/db/pg/pg.go b/internal/db/pg/pg.go
index 30b073bcc..01dc71434 100644
--- a/internal/db/pg/pg.go
+++ b/internal/db/pg/pg.go
@@ -500,6 +500,13 @@ func (ps *postgresService) GetStatusesByTimeDescending(accountID string, statuse
return q.Where("? IS NOT NULL", pg.Ident("attachments")).Where("attachments != '{}'"), nil
})
}
+ if maxID != "" {
+ s := &gtsmodel.Status{}
+ if err := ps.conn.Model(s).Where("id = ?", maxID).Select(); err != nil {
+ return err
+ }
+ q = q.Where("status.created_at < ?", s.CreatedAt)
+ }
if err := q.Select(); err != nil {
if err == pg.ErrNoRows {
return db.ErrNoEntries{}
@@ -1113,6 +1120,14 @@ func (ps *postgresService) GetHomeTimelineForAccount(accountID string, maxID str
Limit(limit).
Order("status.created_at DESC")
+ if maxID != "" {
+ s := &gtsmodel.Status{}
+ if err := ps.conn.Model(s).Where("id = ?", maxID).Select(); err != nil {
+ return nil, err
+ }
+ q = q.Where("status.created_at < ?", s.CreatedAt)
+ }
+
err := q.Select()
if err != nil {
if err != pg.ErrNoRows {
diff --git a/internal/federation/federating_db.go b/internal/federation/federating_db.go
index 8f203e132..af685904a 100644
--- a/internal/federation/federating_db.go
+++ b/internal/federation/federating_db.go
@@ -241,6 +241,40 @@ func (f *federatingDB) Owns(c context.Context, id *url.URL) (bool, error) {
return true, nil
}
+ if util.IsFollowersPath(id) {
+ username, err := util.ParseFollowersPath(id)
+ if err != nil {
+ return false, fmt.Errorf("error parsing statuses path for url %s: %s", id.String(), err)
+ }
+ if err := f.db.GetLocalAccountByUsername(username, &gtsmodel.Account{}); err != nil {
+ if _, ok := err.(db.ErrNoEntries); ok {
+ // there are no entries for this username
+ return false, nil
+ }
+ // an actual error happened
+ return false, fmt.Errorf("database error fetching account with username %s: %s", username, err)
+ }
+ l.Debug("we DO own this")
+ return true, nil
+ }
+
+ if util.IsFollowingPath(id) {
+ username, err := util.ParseFollowingPath(id)
+ if err != nil {
+ return false, fmt.Errorf("error parsing statuses path for url %s: %s", id.String(), err)
+ }
+ if err := f.db.GetLocalAccountByUsername(username, &gtsmodel.Account{}); err != nil {
+ if _, ok := err.(db.ErrNoEntries); ok {
+ // there are no entries for this username
+ return false, nil
+ }
+ // an actual error happened
+ return false, fmt.Errorf("database error fetching account with username %s: %s", username, err)
+ }
+ l.Debug("we DO own this")
+ return true, nil
+ }
+
return false, fmt.Errorf("could not match activityID: %s", id.String())
}
@@ -502,6 +536,15 @@ func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
l.Error("receiving account was set on context but couldn't be parsed")
}
+ requestingAcctI := ctx.Value(util.APRequestingAccount)
+ if receivingAcctI == nil {
+ l.Error("requesting account wasn't set on context")
+ }
+ requestingAcct, ok := requestingAcctI.(*gtsmodel.Account)
+ if !ok {
+ l.Error("requesting account was set on context but couldn't be parsed")
+ }
+
fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey)
if fromFederatorChanI == nil {
l.Error("from federator channel wasn't set on context")
@@ -511,51 +554,76 @@ func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
l.Error("from federator channel was set on context but couldn't be parsed")
}
- switch asType.GetTypeName() {
- case gtsmodel.ActivityStreamsUpdate:
- update, ok := asType.(vocab.ActivityStreamsCreate)
- if !ok {
- return errors.New("could not convert type to create")
+ typeName := asType.GetTypeName()
+ if typeName == gtsmodel.ActivityStreamsApplication ||
+ typeName == gtsmodel.ActivityStreamsGroup ||
+ typeName == gtsmodel.ActivityStreamsOrganization ||
+ typeName == gtsmodel.ActivityStreamsPerson ||
+ typeName == gtsmodel.ActivityStreamsService {
+ // it's an UPDATE to some kind of account
+ var accountable typeutils.Accountable
+
+ switch asType.GetTypeName() {
+ case gtsmodel.ActivityStreamsApplication:
+ l.Debug("got update for APPLICATION")
+ i, ok := asType.(vocab.ActivityStreamsApplication)
+ if !ok {
+ return errors.New("could not convert type to application")
+ }
+ accountable = i
+ case gtsmodel.ActivityStreamsGroup:
+ l.Debug("got update for GROUP")
+ i, ok := asType.(vocab.ActivityStreamsGroup)
+ if !ok {
+ return errors.New("could not convert type to group")
+ }
+ accountable = i
+ case gtsmodel.ActivityStreamsOrganization:
+ l.Debug("got update for ORGANIZATION")
+ i, ok := asType.(vocab.ActivityStreamsOrganization)
+ if !ok {
+ return errors.New("could not convert type to organization")
+ }
+ accountable = i
+ case gtsmodel.ActivityStreamsPerson:
+ l.Debug("got update for PERSON")
+ i, ok := asType.(vocab.ActivityStreamsPerson)
+ if !ok {
+ return errors.New("could not convert type to person")
+ }
+ accountable = i
+ case gtsmodel.ActivityStreamsService:
+ l.Debug("got update for SERVICE")
+ i, ok := asType.(vocab.ActivityStreamsService)
+ if !ok {
+ return errors.New("could not convert type to service")
+ }
+ accountable = i
}
- object := update.GetActivityStreamsObject()
- for objectIter := object.Begin(); objectIter != object.End(); objectIter = objectIter.Next() {
- switch objectIter.GetType().GetTypeName() {
- case string(gtsmodel.ActivityStreamsPerson):
- person := objectIter.GetActivityStreamsPerson()
- updatedAcct, err := f.typeConverter.ASRepresentationToAccount(person)
- if err != nil {
- return fmt.Errorf("error converting person to account: %s", err)
- }
- if err := f.db.Put(updatedAcct); err != nil {
- return fmt.Errorf("database error inserting updated account: %s", err)
- }
- fromFederatorChan <- gtsmodel.FromFederator{
- APObjectType: gtsmodel.ActivityStreamsProfile,
- APActivityType: gtsmodel.ActivityStreamsUpdate,
- GTSModel: updatedAcct,
- ReceivingAccount: receivingAcct,
- }
+ updatedAcct, err := f.typeConverter.ASRepresentationToAccount(accountable, true)
+ if err != nil {
+ return fmt.Errorf("error converting to account: %s", err)
+ }
- case string(gtsmodel.ActivityStreamsApplication):
- application := objectIter.GetActivityStreamsApplication()
- updatedAcct, err := f.typeConverter.ASRepresentationToAccount(application)
- if err != nil {
- return fmt.Errorf("error converting person to account: %s", err)
- }
- if err := f.db.Put(updatedAcct); err != nil {
- return fmt.Errorf("database error inserting updated account: %s", err)
- }
+ if requestingAcct.URI != updatedAcct.URI {
+ return fmt.Errorf("update for account %s was requested by account %s, this is not valid", updatedAcct.URI, requestingAcct.URI)
+ }
- fromFederatorChan <- gtsmodel.FromFederator{
- APObjectType: gtsmodel.ActivityStreamsProfile,
- APActivityType: gtsmodel.ActivityStreamsUpdate,
- GTSModel: updatedAcct,
- ReceivingAccount: receivingAcct,
- }
- }
+ updatedAcct.ID = requestingAcct.ID // set this here so the db will update properly instead of trying to PUT this and getting constraint issues
+ if err := f.db.UpdateByID(requestingAcct.ID, updatedAcct); err != nil {
+ return fmt.Errorf("database error inserting updated account: %s", err)
+ }
+
+ fromFederatorChan <- gtsmodel.FromFederator{
+ APObjectType: gtsmodel.ActivityStreamsProfile,
+ APActivityType: gtsmodel.ActivityStreamsUpdate,
+ GTSModel: updatedAcct,
+ ReceivingAccount: receivingAcct,
}
+
}
+
return nil
}
@@ -565,7 +633,7 @@ func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
// Protocol instead call Update to create a Tombstone.
//
// The library makes this call only after acquiring a lock first.
-func (f *federatingDB) Delete(c context.Context, id *url.URL) error {
+func (f *federatingDB) Delete(ctx context.Context, id *url.URL) error {
l := f.log.WithFields(
logrus.Fields{
"func": "Delete",
@@ -573,6 +641,63 @@ func (f *federatingDB) Delete(c context.Context, id *url.URL) error {
},
)
l.Debugf("received DELETE id %s", id.String())
+
+ inboxAcctI := ctx.Value(util.APAccount)
+ if inboxAcctI == nil {
+ l.Error("inbox account wasn't set on context")
+ return nil
+ }
+ inboxAcct, ok := inboxAcctI.(*gtsmodel.Account)
+ if !ok {
+ l.Error("inbox account was set on context but couldn't be parsed")
+ return nil
+ }
+
+ fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey)
+ if fromFederatorChanI == nil {
+ l.Error("from federator channel wasn't set on context")
+ return nil
+ }
+ fromFederatorChan, ok := fromFederatorChanI.(chan gtsmodel.FromFederator)
+ if !ok {
+ l.Error("from federator channel was set on context but couldn't be parsed")
+ return nil
+ }
+
+ // in a delete we only get the URI, we can't know if we have a status or a profile or something else,
+ // so we have to try a few different things...
+ where := []db.Where{{Key: "uri", Value: id.String()}}
+
+ s := &gtsmodel.Status{}
+ if err := f.db.GetWhere(where, s); err == nil {
+ // it's a status
+ l.Debugf("uri is for status with id: %s", s.ID)
+ if err := f.db.DeleteByID(s.ID, &gtsmodel.Status{}); err != nil {
+ return fmt.Errorf("Delete: err deleting status: %s", err)
+ }
+ fromFederatorChan <- gtsmodel.FromFederator{
+ APObjectType: gtsmodel.ActivityStreamsNote,
+ APActivityType: gtsmodel.ActivityStreamsDelete,
+ GTSModel: s,
+ ReceivingAccount: inboxAcct,
+ }
+ }
+
+ a := &gtsmodel.Account{}
+ if err := f.db.GetWhere(where, a); err == nil {
+ // it's an account
+ l.Debugf("uri is for an account with id: %s", s.ID)
+ if err := f.db.DeleteByID(a.ID, &gtsmodel.Account{}); err != nil {
+ return fmt.Errorf("Delete: err deleting account: %s", err)
+ }
+ fromFederatorChan <- gtsmodel.FromFederator{
+ APObjectType: gtsmodel.ActivityStreamsProfile,
+ APActivityType: gtsmodel.ActivityStreamsDelete,
+ GTSModel: a,
+ ReceivingAccount: inboxAcct,
+ }
+ }
+
return nil
}
diff --git a/internal/federation/federatingprotocol.go b/internal/federation/federatingprotocol.go
index 61fecb11a..ab4b5ccbc 100644
--- a/internal/federation/federatingprotocol.go
+++ b/internal/federation/federatingprotocol.go
@@ -136,7 +136,7 @@ func (f *federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWr
return ctx, false, fmt.Errorf("error dereferencing account with public key id %s: %s", publicKeyOwnerURI.String(), err)
}
- a, err := f.typeConverter.ASRepresentationToAccount(person)
+ a, err := f.typeConverter.ASRepresentationToAccount(person, false)
if err != nil {
return ctx, false, fmt.Errorf("error converting person with public key id %s to account: %s", publicKeyOwnerURI.String(), err)
}
diff --git a/internal/federation/util.go b/internal/federation/util.go
index 3f53ed6a7..6ae0152df 100644
--- a/internal/federation/util.go
+++ b/internal/federation/util.go
@@ -132,9 +132,11 @@ func (f *federator) AuthenticateFederatedRequest(username string, r *http.Reques
var publicKey interface{}
var pkOwnerURI *url.URL
+ requestingRemoteAccount := &gtsmodel.Account{}
+ requestingLocalAccount := &gtsmodel.Account{}
if strings.EqualFold(requestingPublicKeyID.Host, f.config.Host) {
+ // LOCAL ACCOUNT REQUEST
// the request is coming from INSIDE THE HOUSE so skip the remote dereferencing
- requestingLocalAccount := &gtsmodel.Account{}
if err := f.db.GetWhere([]db.Where{{Key: "public_key_uri", Value: requestingPublicKeyID.String()}}, requestingLocalAccount); err != nil {
return nil, fmt.Errorf("couldn't get local account with public key uri %s from the database: %s", requestingPublicKeyID.String(), err)
}
@@ -143,8 +145,18 @@ func (f *federator) AuthenticateFederatedRequest(username string, r *http.Reques
if err != nil {
return nil, fmt.Errorf("error parsing url %s: %s", requestingLocalAccount.URI, err)
}
+ } else if err := f.db.GetWhere([]db.Where{{Key: "public_key_uri", Value: requestingPublicKeyID.String()}}, requestingRemoteAccount); err == nil {
+ // REMOTE ACCOUNT REQUEST WITH KEY CACHED LOCALLY
+ // this is a remote account and we already have the public key for it so use that
+ publicKey = requestingRemoteAccount.PublicKey
+ pkOwnerURI, err = url.Parse(requestingRemoteAccount.URI)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing url %s: %s", requestingRemoteAccount.URI, err)
+ }
} else {
- // the request is remote, so we need to authenticate the request properly by dereferencing the remote key
+ // REMOTE ACCOUNT REQUEST WITHOUT KEY CACHED LOCALLY
+ // the request is remote and we don't have the public key yet,
+ // so we need to authenticate the request properly by dereferencing the remote key
transport, err := f.GetTransportForUser(username)
if err != nil {
return nil, fmt.Errorf("transport err: %s", err)
diff --git a/internal/message/accountprocess.go b/internal/message/accountprocess.go
index 29fd55034..424081c34 100644
--- a/internal/message/accountprocess.go
+++ b/internal/message/accountprocess.go
@@ -83,7 +83,7 @@ func (p *processor) AccountGet(authed *oauth.Auth, targetAccountID string) (*api
if authed.Account != nil {
requestingUsername = authed.Account.Username
}
- if err := p.dereferenceAccountFields(targetAccount, requestingUsername); err != nil {
+ if err := p.dereferenceAccountFields(targetAccount, requestingUsername, false); err != nil {
p.log.WithField("func", "AccountGet").Debugf("dereferencing account: %s", err)
}
@@ -295,7 +295,7 @@ func (p *processor) AccountFollowersGet(authed *oauth.Auth, targetAccountID stri
}
// derefence account fields in case we haven't done it already
- if err := p.dereferenceAccountFields(a, authed.Account.Username); err != nil {
+ if err := p.dereferenceAccountFields(a, authed.Account.Username, false); err != nil {
// don't bail if we can't fetch them, we'll try another time
p.log.WithField("func", "AccountFollowersGet").Debugf("error dereferencing account fields: %s", err)
}
@@ -346,7 +346,7 @@ func (p *processor) AccountFollowingGet(authed *oauth.Auth, targetAccountID stri
}
// derefence account fields in case we haven't done it already
- if err := p.dereferenceAccountFields(a, authed.Account.Username); err != nil {
+ if err := p.dereferenceAccountFields(a, authed.Account.Username, false); err != nil {
// don't bail if we can't fetch them, we'll try another time
p.log.WithField("func", "AccountFollowingGet").Debugf("error dereferencing account fields: %s", err)
}
diff --git a/internal/message/fediprocess.go b/internal/message/fediprocess.go
index 491997bf2..173da18ee 100644
--- a/internal/message/fediprocess.go
+++ b/internal/message/fediprocess.go
@@ -68,7 +68,7 @@ func (p *processor) authenticateAndDereferenceFediRequest(username string, r *ht
}
// convert it to our internal account representation
- requestingAccount, err = p.tc.ASRepresentationToAccount(requestingPerson)
+ requestingAccount, err = p.tc.ASRepresentationToAccount(requestingPerson, false)
if err != nil {
return nil, fmt.Errorf("couldn't convert dereferenced uri %s to gtsmodel account: %s", requestingAccountURI.String(), err)
}
@@ -163,6 +163,46 @@ func (p *processor) GetFediFollowers(requestedUsername string, request *http.Req
return data, nil
}
+func (p *processor) GetFediFollowing(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode) {
+ // get the account the request is referring to
+ requestedAccount := &gtsmodel.Account{}
+ if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {
+ return nil, NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err))
+ }
+
+ // authenticate the request
+ requestingAccount, err := p.authenticateAndDereferenceFediRequest(requestedUsername, request)
+ if err != nil {
+ return nil, NewErrorNotAuthorized(err)
+ }
+
+ blocked, err := p.db.Blocked(requestedAccount.ID, requestingAccount.ID)
+ if err != nil {
+ return nil, NewErrorInternalError(err)
+ }
+
+ if blocked {
+ return nil, NewErrorNotAuthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID))
+ }
+
+ requestedAccountURI, err := url.Parse(requestedAccount.URI)
+ if err != nil {
+ return nil, NewErrorInternalError(fmt.Errorf("error parsing url %s: %s", requestedAccount.URI, err))
+ }
+
+ requestedFollowing, err := p.federator.FederatingDB().Following(context.Background(), requestedAccountURI)
+ if err != nil {
+ return nil, NewErrorInternalError(fmt.Errorf("error fetching following for uri %s: %s", requestedAccountURI.String(), err))
+ }
+
+ data, err := streams.Serialize(requestedFollowing)
+ if err != nil {
+ return nil, NewErrorInternalError(err)
+ }
+
+ return data, nil
+}
+
func (p *processor) GetFediStatus(requestedUsername string, requestedStatusID string, request *http.Request) (interface{}, ErrorWithCode) {
// get the account the request is referring to
requestedAccount := &gtsmodel.Account{}
diff --git a/internal/message/fromfederatorprocess.go b/internal/message/fromfederatorprocess.go
index ffaa1b93b..d3ebce400 100644
--- a/internal/message/fromfederatorprocess.go
+++ b/internal/message/fromfederatorprocess.go
@@ -68,7 +68,7 @@ func (p *processor) processFromFederator(federatorMsg gtsmodel.FromFederator) er
}
l.Debug("will now derefence incoming account")
- if err := p.dereferenceAccountFields(incomingAccount, ""); err != nil {
+ if err := p.dereferenceAccountFields(incomingAccount, "", false); err != nil {
return fmt.Errorf("error dereferencing account from federator: %s", err)
}
if err := p.db.UpdateByID(incomingAccount.ID, incomingAccount); err != nil {
@@ -86,13 +86,26 @@ func (p *processor) processFromFederator(federatorMsg gtsmodel.FromFederator) er
}
l.Debug("will now derefence incoming account")
- if err := p.dereferenceAccountFields(incomingAccount, ""); err != nil {
+ if err := p.dereferenceAccountFields(incomingAccount, federatorMsg.ReceivingAccount.Username, true); err != nil {
return fmt.Errorf("error dereferencing account from federator: %s", err)
}
if err := p.db.UpdateByID(incomingAccount.ID, incomingAccount); err != nil {
return fmt.Errorf("error updating dereferenced account in the db: %s", err)
}
}
+ case gtsmodel.ActivityStreamsDelete:
+ // DELETE
+ switch federatorMsg.APObjectType {
+ case gtsmodel.ActivityStreamsNote:
+ // DELETE A STATUS
+ // TODO: handle side effects of status deletion here:
+ // 1. delete all media associated with status
+ // 2. delete boosts of status
+ // 3. etc etc etc
+ case gtsmodel.ActivityStreamsProfile:
+ // DELETE A PROFILE/ACCOUNT
+ // TODO: handle side effects of account deletion here: delete all objects, statuses, media etc associated with account
+ }
}
return nil
@@ -220,7 +233,7 @@ func (p *processor) dereferenceStatusFields(status *gtsmodel.Status) error {
continue
}
- targetAccount, err = p.tc.ASRepresentationToAccount(accountable)
+ targetAccount, err = p.tc.ASRepresentationToAccount(accountable, false)
if err != nil {
l.Debugf("error converting remote account with uri %s into gts model: %s", uri.String(), err)
continue
@@ -243,7 +256,7 @@ func (p *processor) dereferenceStatusFields(status *gtsmodel.Status) error {
return nil
}
-func (p *processor) dereferenceAccountFields(account *gtsmodel.Account, requestingUsername string) error {
+func (p *processor) dereferenceAccountFields(account *gtsmodel.Account, requestingUsername string, refresh bool) error {
l := p.log.WithFields(logrus.Fields{
"func": "dereferenceAccountFields",
"requestingUsername": requestingUsername,
@@ -255,7 +268,7 @@ func (p *processor) dereferenceAccountFields(account *gtsmodel.Account, requesti
}
// fetch the header and avatar
- if err := p.fetchHeaderAndAviForAccount(account, t); err != nil {
+ if err := p.fetchHeaderAndAviForAccount(account, t, refresh); err != nil {
// if this doesn't work, just skip it -- we can do it later
l.Debugf("error fetching header/avi for account: %s", err)
}
diff --git a/internal/message/frprocess.go b/internal/message/frprocess.go
index 5d02836e6..41ab285c2 100644
--- a/internal/message/frprocess.go
+++ b/internal/message/frprocess.go
@@ -68,8 +68,8 @@ func (p *processor) FollowRequestAccept(auth *oauth.Auth, accountID string) (*ap
APObjectType: gtsmodel.ActivityStreamsFollow,
APActivityType: gtsmodel.ActivityStreamsAccept,
GTSModel: follow,
- OriginAccount: originAccount,
- TargetAccount: targetAccount,
+ OriginAccount: originAccount,
+ TargetAccount: targetAccount,
}
gtsR, err := p.db.GetRelationship(auth.Account.ID, accountID)
diff --git a/internal/message/processor.go b/internal/message/processor.go
index 54b2ada04..bcd64d47a 100644
--- a/internal/message/processor.go
+++ b/internal/message/processor.go
@@ -140,6 +140,10 @@ type Processor interface {
// authentication before returning a JSON serializable interface to the caller.
GetFediFollowers(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode)
+ // GetFediFollowing handles the getting of a fedi/activitypub representation of a user/account's following, performing appropriate
+ // authentication before returning a JSON serializable interface to the caller.
+ GetFediFollowing(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode)
+
// GetFediStatus handles the getting of a fedi/activitypub representation of a particular status, performing appropriate
// authentication before returning a JSON serializable interface to the caller.
GetFediStatus(requestedUsername string, requestedStatusID string, request *http.Request) (interface{}, ErrorWithCode)
diff --git a/internal/message/processorutil.go b/internal/message/processorutil.go
index 67c96abe0..b053f31a2 100644
--- a/internal/message/processorutil.go
+++ b/internal/message/processorutil.go
@@ -130,8 +130,10 @@ func (p *processor) processReplyToID(form *apimodel.AdvancedStatusCreateForm, th
return fmt.Errorf("status with id %s not replyable: %s", form.InReplyToID, err)
}
- if !repliedStatus.VisibilityAdvanced.Replyable {
- return fmt.Errorf("status with id %s is marked as not replyable", form.InReplyToID)
+ if repliedStatus.VisibilityAdvanced != nil {
+ if !repliedStatus.VisibilityAdvanced.Replyable {
+ return fmt.Errorf("status with id %s is marked as not replyable", form.InReplyToID)
+ }
}
// check replied account is known to us
@@ -329,8 +331,8 @@ func (p *processor) updateAccountHeader(header *multipart.FileHeader, accountID
//
// SIDE EFFECTS: remote header and avatar will be stored in local storage, and the database will be updated
// to reflect the creation of these new attachments.
-func (p *processor) fetchHeaderAndAviForAccount(targetAccount *gtsmodel.Account, t transport.Transport) error {
- if targetAccount.AvatarRemoteURL != "" && targetAccount.AvatarMediaAttachmentID == "" {
+func (p *processor) fetchHeaderAndAviForAccount(targetAccount *gtsmodel.Account, t transport.Transport, refresh bool) error {
+ if targetAccount.AvatarRemoteURL != "" && (targetAccount.AvatarMediaAttachmentID == "" || refresh) {
a, err := p.mediaHandler.ProcessRemoteHeaderOrAvatar(t, &gtsmodel.MediaAttachment{
RemoteURL: targetAccount.AvatarRemoteURL,
Avatar: true,
@@ -341,7 +343,7 @@ func (p *processor) fetchHeaderAndAviForAccount(targetAccount *gtsmodel.Account,
targetAccount.AvatarMediaAttachmentID = a.ID
}
- if targetAccount.HeaderRemoteURL != "" && targetAccount.HeaderMediaAttachmentID == "" {
+ if targetAccount.HeaderRemoteURL != "" && (targetAccount.HeaderMediaAttachmentID == "" || refresh) {
a, err := p.mediaHandler.ProcessRemoteHeaderOrAvatar(t, &gtsmodel.MediaAttachment{
RemoteURL: targetAccount.HeaderRemoteURL,
Header: true,
diff --git a/internal/typeutils/asextractionutil.go b/internal/typeutils/asextractionutil.go
index 1c04272e0..b3e6eb2c4 100644
--- a/internal/typeutils/asextractionutil.go
+++ b/internal/typeutils/asextractionutil.go
@@ -29,7 +29,6 @@ import (
"time"
"github.com/go-fed/activity/pub"
- "github.com/go-fed/activity/streams"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/util"
)
@@ -63,6 +62,9 @@ func extractName(i withName) (string, error) {
func extractInReplyToURI(i withInReplyTo) (*url.URL, error) {
inReplyToProp := i.GetActivityStreamsInReplyTo()
+ if inReplyToProp == nil {
+ return nil, errors.New("in reply to prop was nil")
+ }
for iter := inReplyToProp.Begin(); iter != inReplyToProp.End(); iter = iter.Next() {
if iter.IsIRI() {
if iter.GetIRI() != nil {
@@ -76,6 +78,9 @@ func extractInReplyToURI(i withInReplyTo) (*url.URL, error) {
func extractTos(i withTo) ([]*url.URL, error) {
to := []*url.URL{}
toProp := i.GetActivityStreamsTo()
+ if toProp == nil {
+ return nil, errors.New("toProp was nil")
+ }
for iter := toProp.Begin(); iter != toProp.End(); iter = iter.Next() {
if iter.IsIRI() {
if iter.GetIRI() != nil {
@@ -89,6 +94,9 @@ func extractTos(i withTo) ([]*url.URL, error) {
func extractCCs(i withCC) ([]*url.URL, error) {
cc := []*url.URL{}
ccProp := i.GetActivityStreamsCc()
+ if ccProp == nil {
+ return cc, nil
+ }
for iter := ccProp.Begin(); iter != ccProp.End(); iter = iter.Next() {
if iter.IsIRI() {
if iter.GetIRI() != nil {
@@ -101,6 +109,9 @@ func extractCCs(i withCC) ([]*url.URL, error) {
func extractAttributedTo(i withAttributedTo) (*url.URL, error) {
attributedToProp := i.GetActivityStreamsAttributedTo()
+ if attributedToProp == nil {
+ return nil, errors.New("attributedToProp was nil")
+ }
for iter := attributedToProp.Begin(); iter != attributedToProp.End(); iter = iter.Next() {
if iter.IsIRI() {
if iter.GetIRI() != nil {
@@ -302,27 +313,21 @@ func extractContent(i withContent) (string, error) {
func extractAttachments(i withAttachment) ([]*gtsmodel.MediaAttachment, error) {
attachments := []*gtsmodel.MediaAttachment{}
-
attachmentProp := i.GetActivityStreamsAttachment()
+ if attachmentProp == nil {
+ return attachments, nil
+ }
for iter := attachmentProp.Begin(); iter != attachmentProp.End(); iter = iter.Next() {
-
t := iter.GetType()
if t == nil {
- fmt.Printf("\n\n\nGetType() nil\n\n\n")
continue
}
-
- m, _ := streams.Serialize(t)
- fmt.Printf("\n\n\n%s\n\n\n", m)
-
attachmentable, ok := t.(Attachmentable)
if !ok {
- fmt.Printf("\n\n\nnot attachmentable\n\n\n")
continue
}
attachment, err := extractAttachment(attachmentable)
if err != nil {
- fmt.Printf("\n\n\n%s\n\n\n", err)
continue
}
attachments = append(attachments, attachment)
@@ -373,8 +378,10 @@ func extractAttachment(i Attachmentable) (*gtsmodel.MediaAttachment, error) {
func extractHashtags(i withTag) ([]*gtsmodel.Tag, error) {
tags := []*gtsmodel.Tag{}
-
tagsProp := i.GetActivityStreamsTag()
+ if tagsProp == nil {
+ return tags, nil
+ }
for iter := tagsProp.Begin(); iter != tagsProp.End(); iter = iter.Next() {
t := iter.GetType()
if t == nil {
@@ -421,6 +428,9 @@ func extractHashtag(i Hashtaggable) (*gtsmodel.Tag, error) {
func extractEmojis(i withTag) ([]*gtsmodel.Emoji, error) {
emojis := []*gtsmodel.Emoji{}
tagsProp := i.GetActivityStreamsTag()
+ if tagsProp == nil {
+ return emojis, nil
+ }
for iter := tagsProp.Begin(); iter != tagsProp.End(); iter = iter.Next() {
t := iter.GetType()
if t == nil {
@@ -478,6 +488,9 @@ func extractEmoji(i Emojiable) (*gtsmodel.Emoji, error) {
func extractMentions(i withTag) ([]*gtsmodel.Mention, error) {
mentions := []*gtsmodel.Mention{}
tagsProp := i.GetActivityStreamsTag()
+ if tagsProp == nil {
+ return mentions, nil
+ }
for iter := tagsProp.Begin(); iter != tagsProp.End(); iter = iter.Next() {
t := iter.GetType()
if t == nil {
diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go
index 7eb3f5927..dcc2674cd 100644
--- a/internal/typeutils/astointernal.go
+++ b/internal/typeutils/astointernal.go
@@ -28,7 +28,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
-func (c *converter) ASRepresentationToAccount(accountable Accountable) (*gtsmodel.Account, error) {
+func (c *converter) ASRepresentationToAccount(accountable Accountable, update bool) (*gtsmodel.Account, error) {
// first check if we actually already know this account
uriProp := accountable.GetJSONLDId()
if uriProp == nil || !uriProp.IsIRI() {
@@ -37,17 +37,19 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable) (*gtsmode
uri := uriProp.GetIRI()
acct := &gtsmodel.Account{}
- err := c.db.GetWhere([]db.Where{{Key: "uri", Value: uri.String()}}, acct)
- if err == nil {
- // we already know this account so we can skip generating it
- return acct, nil
- }
- if _, ok := err.(db.ErrNoEntries); !ok {
- // we don't know the account and there's been a real error
- return nil, fmt.Errorf("error getting account with uri %s from the database: %s", uri.String(), err)
+ if !update {
+ err := c.db.GetWhere([]db.Where{{Key: "uri", Value: uri.String()}}, acct)
+ if err == nil {
+ // we already know this account so we can skip generating it
+ return acct, nil
+ }
+ if _, ok := err.(db.ErrNoEntries); !ok {
+ // we don't know the account and there's been a real error
+ return nil, fmt.Errorf("error getting account with uri %s from the database: %s", uri.String(), err)
+ }
}
- // we don't know the account so we need to generate it from the person -- at least we already have the URI!
+ // we don't know the account, or we're being told to update it, so we need to generate it from the person -- at least we already have the URI!
acct = &gtsmodel.Account{}
acct.URI = uri.String()
diff --git a/internal/typeutils/astointernal_test.go b/internal/typeutils/astointernal_test.go
index f1287e027..9d6ce4e0a 100644
--- a/internal/typeutils/astointernal_test.go
+++ b/internal/typeutils/astointernal_test.go
@@ -349,7 +349,7 @@ func (suite *ASToInternalTestSuite) TestParsePerson() {
testPerson := suite.people["new_person_1"]
- acct, err := suite.typeconverter.ASRepresentationToAccount(testPerson)
+ acct, err := suite.typeconverter.ASRepresentationToAccount(testPerson, false)
assert.NoError(suite.T(), err)
fmt.Printf("%+v", acct)
@@ -367,7 +367,7 @@ func (suite *ASToInternalTestSuite) TestParseGargron() {
rep, ok := t.(typeutils.Accountable)
assert.True(suite.T(), ok)
- acct, err := suite.typeconverter.ASRepresentationToAccount(rep)
+ acct, err := suite.typeconverter.ASRepresentationToAccount(rep, false)
assert.NoError(suite.T(), err)
fmt.Printf("%+v", acct)
diff --git a/internal/typeutils/converter.go b/internal/typeutils/converter.go
index ac2ce4317..63e201ded 100644
--- a/internal/typeutils/converter.go
+++ b/internal/typeutils/converter.go
@@ -95,8 +95,12 @@ type TypeConverter interface {
ACTIVITYSTREAMS MODEL TO INTERNAL (gts) MODEL
*/
- // ASPersonToAccount converts a remote account/person/application representation into a gts model account
- ASRepresentationToAccount(accountable Accountable) (*gtsmodel.Account, error)
+ // ASPersonToAccount converts a remote account/person/application representation into a gts model account.
+ //
+ // If update is false, and the account is already known in the database, then the existing account entry will be returned.
+ // If update is true, then even if the account is already known, all fields in the accountable will be parsed and a new *gtsmodel.Account
+ // will be generated. This is useful when one needs to force refresh of an account, eg., during an Update of a Profile.
+ ASRepresentationToAccount(accountable Accountable, update bool) (*gtsmodel.Account, error)
// ASStatus converts a remote activitystreams 'status' representation into a gts model status.
ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, error)
// ASFollowToFollowRequest converts a remote activitystreams `follow` representation into gts model follow request.
diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go
index 4891e31ee..7fbe9eb3f 100644
--- a/internal/typeutils/internaltofrontend.go
+++ b/internal/typeutils/internaltofrontend.go
@@ -233,9 +233,9 @@ func (c *converter) MentionToMasto(m *gtsmodel.Mention) (model.Mention, error) {
var acct string
if local {
- acct = fmt.Sprintf("@%s", target.Username)
+ acct = target.Username
} else {
- acct = fmt.Sprintf("@%s@%s", target.Username, target.Domain)
+ acct = fmt.Sprintf("%s@%s", target.Username, target.Domain)
}
return model.Mention{
@@ -567,7 +567,7 @@ func (c *converter) InstanceToMasto(i *gtsmodel.Instance) (*model.Instance, erro
if i.ContactAccountID != "" {
ia := &gtsmodel.Account{}
if err := c.db.GetByID(i.ContactAccountID, ia); err == nil {
- ma, err := c.AccountToMastoPublic(ia)
+ ma, err := c.AccountToMastoPublic(ia)
if err == nil {
mi.ContactAccount = ma
}
diff --git a/internal/util/uri.go b/internal/util/uri.go
index cee9dcbaa..0ee4a5120 100644
--- a/internal/util/uri.go
+++ b/internal/util/uri.go
@@ -232,3 +232,25 @@ func ParseOutboxPath(id *url.URL) (username string, err error) {
username = matches[1]
return
}
+
+// ParseFollowersPath returns the username from a path such as /users/example_username/followers
+func ParseFollowersPath(id *url.URL) (username string, err error) {
+ matches := followersPathRegex.FindStringSubmatch(id.Path)
+ if len(matches) != 2 {
+ err = fmt.Errorf("expected 2 matches but matches length was %d", len(matches))
+ return
+ }
+ username = matches[1]
+ return
+}
+
+// ParseFollowingPath returns the username from a path such as /users/example_username/following
+func ParseFollowingPath(id *url.URL) (username string, err error) {
+ matches := followingPathRegex.FindStringSubmatch(id.Path)
+ if len(matches) != 2 {
+ err = fmt.Errorf("expected 2 matches but matches length was %d", len(matches))
+ return
+ }
+ username = matches[1]
+ return
+}