some lil fixes for kibou compatibility

This commit is contained in:
tsmethurst 2021-06-12 16:40:11 +02:00
parent 6994859d03
commit d9d9a7a626
12 changed files with 102 additions and 36 deletions

View File

@ -47,12 +47,13 @@ func (m *Module) InboxPOSTHandler(c *gin.Context) {
c.JSON(withCode.Code(), withCode.Safe()) c.JSON(withCode.Code(), withCode.Safe())
return return
} }
l.Debug(err) l.Debugf("InboxPOSTHandler: error processing request: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "unable to process request"}) c.JSON(http.StatusBadRequest, gin.H{"error": "unable to process request"})
return return
} }
if !posted { if !posted {
l.Debugf("request could not be handled as an AP request; headers were: %+v", c.Request.Header)
c.JSON(http.StatusBadRequest, gin.H{"error": "unable to process request"}) c.JSON(http.StatusBadRequest, gin.H{"error": "unable to process request"})
} }
} }

View File

@ -24,42 +24,53 @@ import (
"strings" "strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
) )
// WebfingerGETRequest handles requests to, for example, https://example.org/.well-known/webfinger?resource=acct:some_user@example.org // WebfingerGETRequest handles requests to, for example, https://example.org/.well-known/webfinger?resource=acct:some_user@example.org
func (m *Module) WebfingerGETRequest(c *gin.Context) { func (m *Module) WebfingerGETRequest(c *gin.Context) {
l := m.log.WithFields(logrus.Fields{
"func": "WebfingerGETRequest",
"user-agent": c.Request.UserAgent(),
})
q, set := c.GetQuery("resource") q, set := c.GetQuery("resource")
if !set || q == "" { if !set || q == "" {
l.Debug("aborting request because no resource was set in query")
c.JSON(http.StatusBadRequest, gin.H{"error": "no 'resource' in request query"}) c.JSON(http.StatusBadRequest, gin.H{"error": "no 'resource' in request query"})
return return
} }
withAcct := strings.Split(q, "acct:") withAcct := strings.Split(q, "acct:")
if len(withAcct) != 2 { if len(withAcct) != 2 {
l.Debugf("aborting request because resource query %s could not be split by 'acct:'", q)
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"}) c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
return return
} }
usernameDomain := strings.Split(withAcct[1], "@") usernameDomain := strings.Split(withAcct[1], "@")
if len(usernameDomain) != 2 { if len(usernameDomain) != 2 {
l.Debugf("aborting request because username and domain could not be parsed from %s", withAcct[1])
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"}) c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
return return
} }
username := strings.ToLower(usernameDomain[0]) username := strings.ToLower(usernameDomain[0])
domain := strings.ToLower(usernameDomain[1]) domain := strings.ToLower(usernameDomain[1])
if username == "" || domain == "" { if username == "" || domain == "" {
l.Debug("aborting request because username or domain was empty")
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"}) c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
return return
} }
if domain != m.config.Host { if domain != m.config.Host {
l.Debug("aborting request because domain %s does not belong to this instance", domain)
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("domain %s does not belong to this instance", domain)}) c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("domain %s does not belong to this instance", domain)})
return return
} }
resp, err := m.processor.GetWebfingerAccount(username, c.Request) resp, err := m.processor.GetWebfingerAccount(username, c.Request)
if err != nil { if err != nil {
l.Debugf("aborting request with an error: %s", err.Error())
c.JSON(err.Code(), gin.H{"error": err.Safe()}) c.JSON(err.Code(), gin.H{"error": err.Safe()})
return return
} }

View File

@ -0,0 +1,17 @@
package security
import (
"net/http"
"github.com/gin-gonic/gin"
)
const robotsString = `User-agent: *
Disallow: /
`
// RobotsGETHandler returns the most restrictive possible robots.txt file in response to a call to /robots.txt.
// The response instructs bots with *any* user agent not to index the instance at all.
func (m *Module) RobotsGETHandler(c *gin.Context) {
c.String(http.StatusOK, robotsString)
}

View File

@ -19,12 +19,16 @@
package security package security
import ( import (
"net/http"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/api" "github.com/superseriousbusiness/gotosocial/internal/api"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/router" "github.com/superseriousbusiness/gotosocial/internal/router"
) )
const robotsPath = "/robots.txt"
// Module implements the ClientAPIModule interface for security middleware // Module implements the ClientAPIModule interface for security middleware
type Module struct { type Module struct {
config *config.Config config *config.Config
@ -44,5 +48,6 @@ func (m *Module) Route(s router.Router) error {
s.AttachMiddleware(m.FlocBlock) s.AttachMiddleware(m.FlocBlock)
s.AttachMiddleware(m.ExtraHeaders) s.AttachMiddleware(m.ExtraHeaders)
s.AttachMiddleware(m.UserAgentBlock) s.AttachMiddleware(m.UserAgentBlock)
s.AttachHandler(http.MethodGet, robotsPath, m.RobotsGETHandler)
return nil return nil
} }

View File

@ -23,20 +23,24 @@ import (
"strings" "strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
) )
// UserAgentBlock is a middleware that prevents google chrome cohort tracking by // UserAgentBlock blocks requests with undesired, empty, or invalid user-agent strings.
// writing the Permissions-Policy header after all other parts of the request have been completed.
// See: https://plausible.io/blog/google-floc
func (m *Module) UserAgentBlock(c *gin.Context) { func (m *Module) UserAgentBlock(c *gin.Context) {
l := m.log.WithFields(logrus.Fields{
"func": "UserAgentBlock",
})
ua := c.Request.UserAgent() ua := c.Request.UserAgent()
if ua == "" { if ua == "" {
l.Debug("aborting request because there's no user-agent set")
c.AbortWithStatus(http.StatusTeapot) c.AbortWithStatus(http.StatusTeapot)
return return
} }
if strings.Contains(strings.ToLower(c.Request.UserAgent()), strings.ToLower("friendica")) { if strings.Contains(strings.ToLower(ua), strings.ToLower("friendica")) {
l.Debugf("aborting request with user-agent %s because it contains 'friendica'", ua)
c.AbortWithStatus(http.StatusTeapot) c.AbortWithStatus(http.StatusTeapot)
return return
} }

View File

@ -9,6 +9,7 @@ import (
"github.com/go-fed/activity/streams" "github.com/go-fed/activity/streams"
"github.com/go-fed/activity/streams/vocab" "github.com/go-fed/activity/streams/vocab"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/util" "github.com/superseriousbusiness/gotosocial/internal/util"
) )
@ -58,7 +59,43 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA
} }
for iter := acceptObject.Begin(); iter != acceptObject.End(); iter = iter.Next() { for iter := acceptObject.Begin(); iter != acceptObject.End(); iter = iter.Next() {
// check if the object is an IRI
if iter.IsIRI() {
// we have just the URI of whatever is being accepted, so we need to find out what it is
acceptedObjectIRI := iter.GetIRI()
if util.IsFollowPath(acceptedObjectIRI) {
// ACCEPT FOLLOW
gtsFollowRequest := &gtsmodel.FollowRequest{}
if err := f.db.GetWhere([]db.Where{{Key: "uri", Value: acceptedObjectIRI.String()}}, gtsFollowRequest); err != nil {
return fmt.Errorf("ACCEPT: couldn't get follow request with id %s from the database: %s", acceptedObjectIRI.String(), err)
}
// make sure the addressee of the original follow is the same as whatever inbox this landed in
if gtsFollowRequest.AccountID != inboxAcct.ID {
return errors.New("ACCEPT: follow object account and inbox account were not the same")
}
follow, err := f.db.AcceptFollowRequest(gtsFollowRequest.AccountID, gtsFollowRequest.TargetAccountID)
if err != nil {
return err
}
fromFederatorChan <- gtsmodel.FromFederator{
APObjectType: gtsmodel.ActivityStreamsFollow,
APActivityType: gtsmodel.ActivityStreamsAccept,
GTSModel: follow,
ReceivingAccount: inboxAcct,
}
return nil
}
}
// check if iter is an AP object / type
if iter.GetType() == nil {
continue
}
switch iter.GetType().GetTypeName() { switch iter.GetType().GetTypeName() {
// we have the whole object so we can figure out what we're accepting
case string(gtsmodel.ActivityStreamsFollow): case string(gtsmodel.ActivityStreamsFollow):
// ACCEPT FOLLOW // ACCEPT FOLLOW
asFollow, ok := iter.GetType().(vocab.ActivityStreamsFollow) asFollow, ok := iter.GetType().(vocab.ActivityStreamsFollow)

View File

@ -164,7 +164,7 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
return fmt.Errorf("could not convert Like to fave: %s", err) return fmt.Errorf("could not convert Like to fave: %s", err)
} }
newID, err := id.NewULIDFromTime(fave.CreatedAt) newID, err := id.NewULID()
if err != nil { if err != nil {
return err return err
} }

View File

@ -48,6 +48,9 @@ func (f *federatingDB) Undo(ctx context.Context, undo vocab.ActivityStreamsUndo)
} }
for iter := undoObject.Begin(); iter != undoObject.End(); iter = iter.Next() { for iter := undoObject.Begin(); iter != undoObject.End(); iter = iter.Next() {
if iter.GetType() == nil {
continue
}
switch iter.GetType().GetTypeName() { switch iter.GetType().GetTypeName() {
case string(gtsmodel.ActivityStreamsFollow): case string(gtsmodel.ActivityStreamsFollow):
// UNDO FOLLOW // UNDO FOLLOW

View File

@ -34,15 +34,6 @@ import (
) )
func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.StatusTimelineResponse, gtserror.WithCode) { func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.StatusTimelineResponse, gtserror.WithCode) {
l := p.log.WithFields(logrus.Fields{
"func": "HomeTimelineGet",
"maxID": maxID,
"sinceID": sinceID,
"minID": minID,
"limit": limit,
"local": local,
})
resp := &apimodel.StatusTimelineResponse{ resp := &apimodel.StatusTimelineResponse{
Statuses: []*apimodel.Status{}, Statuses: []*apimodel.Status{},
} }
@ -53,9 +44,6 @@ func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID st
sinceIDMarker := sinceID sinceIDMarker := sinceID
minIDMarker := minID minIDMarker := minID
l.Debugf("\n entering grabloop \n")
l.Debugf("\n querying the db \n")
gtsStatuses, err := p.db.GetStatusesWhereFollowing(authed.Account.ID, maxIDMarker, sinceIDMarker, minIDMarker, limit, local) gtsStatuses, err := p.db.GetStatusesWhereFollowing(authed.Account.ID, maxIDMarker, sinceIDMarker, minIDMarker, limit, local)
if err != nil { if err != nil {
if _, ok := err.(db.ErrNoEntries); !ok { if _, ok := err.(db.ErrNoEntries); !ok {
@ -64,18 +52,6 @@ func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID st
} }
for _, gtsStatus := range gtsStatuses { for _, gtsStatus := range gtsStatuses {
// haveAlready := false
// for _, apiStatus := range apiStatuses {
// if apiStatus.ID == gtsStatus.ID {
// haveAlready = true
// break
// }
// }
// if haveAlready {
// l.Debugf("\n we have status with id %d already so continuing past this iteration of the loop \n", gtsStatus.ID)
// continue
// }
// pull relevant accounts from the status -- we need this both for checking visibility and for serializing // pull relevant accounts from the status -- we need this both for checking visibility and for serializing
relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(gtsStatus) relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(gtsStatus)
if err != nil { if err != nil {
@ -103,7 +79,6 @@ func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID st
continue continue
} }
l.Debug("\n appending to the statuses slice \n")
apiStatuses = append(apiStatuses, apiStatus) apiStatuses = append(apiStatuses, apiStatus)
sort.Slice(apiStatuses, func(i int, j int) bool { sort.Slice(apiStatuses, func(i int, j int) bool {
is, err := time.Parse(time.RFC3339, apiStatuses[i].CreatedAt) is, err := time.Parse(time.RFC3339, apiStatuses[i].CreatedAt)
@ -120,7 +95,6 @@ func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID st
}) })
if len(apiStatuses) == limit { if len(apiStatuses) == limit {
l.Debugf("\n we have enough statuses, returning \n")
// we have enough // we have enough
break break
} }
@ -143,7 +117,7 @@ func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID st
Host: p.config.Host, Host: p.config.Host,
Path: "/api/v1/timelines/home", Path: "/api/v1/timelines/home",
RawPath: url.PathEscape("api/v1/timelines/home"), RawPath: url.PathEscape("api/v1/timelines/home"),
RawQuery: url.QueryEscape(fmt.Sprintf("limit=%d&max_id=%s", limit, apiStatuses[len(apiStatuses)-1].ID)), RawQuery: fmt.Sprintf("limit=%d&max_id=%s", limit, apiStatuses[len(apiStatuses)-1].ID),
} }
next := fmt.Sprintf("<%s>; rel=\"next\"", nextLink.String()) next := fmt.Sprintf("<%s>; rel=\"next\"", nextLink.String())

View File

@ -117,10 +117,14 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable, update bo
// url property // url property
url, err := extractURL(accountable) url, err := extractURL(accountable)
if err != nil { if err == nil {
return nil, fmt.Errorf("could not extract url for person with id %s: %s", uri.String(), err) // take the URL if we can find it
acct.URL = url.String()
} else {
// otherwise just take the account URI as the URL
acct.URL = uri.String()
} }
acct.URL = url.String()
// InboxURI // InboxURI
if accountable.GetActivityStreamsInbox() != nil && accountable.GetActivityStreamsInbox().GetIRI() != nil { if accountable.GetActivityStreamsInbox() != nil && accountable.GetActivityStreamsInbox().GetIRI() != nil {

View File

@ -85,6 +85,11 @@ var (
// followingPathRegex parses a path that validates and captures the username part from eg /users/example_username/following // followingPathRegex parses a path that validates and captures the username part from eg /users/example_username/following
followingPathRegex = regexp.MustCompile(followingPathRegexString) followingPathRegex = regexp.MustCompile(followingPathRegexString)
followPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, UsersPath, usernameRegexString, FollowPath, ulidRegexString)
// followPathRegex parses a path that validates and captures the username part and the ulid part
// from eg /users/example_username/follow/01F7XT5JZW1WMVSW1KADS8PVDH
followPathRegex = regexp.MustCompile(followPathRegexString)
ulidRegexString = `[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}` ulidRegexString = `[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}`
likedPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, LikedPath) likedPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, LikedPath)

View File

@ -189,6 +189,11 @@ func IsFollowingPath(id *url.URL) bool {
return followingPathRegex.MatchString(id.Path) return followingPathRegex.MatchString(id.Path)
} }
// IsFollowPath returns true if the given URL path corresponds to eg /users/example_username/follow/SOME_ULID_OF_A_FOLLOW
func IsFollowPath(id *url.URL) bool {
return followPathRegex.MatchString(id.Path)
}
// IsLikedPath returns true if the given URL path corresponds to eg /users/example_username/liked // IsLikedPath returns true if the given URL path corresponds to eg /users/example_username/liked
func IsLikedPath(id *url.URL) bool { func IsLikedPath(id *url.URL) bool {
return likedPathRegex.MatchString(id.Path) return likedPathRegex.MatchString(id.Path)