moar work

This commit is contained in:
tsmethurst 2021-04-04 23:17:10 +02:00
parent 1710158b39
commit d1ca4a1219
5 changed files with 120 additions and 40 deletions

View file

@ -87,6 +87,9 @@ func (m *statusModule) CreateTables(db db.DB) error {
&model.Application{},
&model.EmailDomainBlock{},
&model.MediaAttachment{},
&model.Emoji{},
&model.Tag{},
&model.Mention{},
}
for _, m := range models {

View file

@ -27,7 +27,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/db/model"
"github.com/superseriousbusiness/gotosocial/internal/distributor"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
@ -62,13 +61,15 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) {
return
}
// check this user/account is permitted to post new statuses
// First check this user/account is permitted to post new statuses.
// There's no point continuing otherwise.
if authed.User.Disabled || !authed.User.Approved || !authed.Account.SuspendedAt.IsZero() {
l.Debugf("couldn't auth: %s", err)
c.JSON(http.StatusForbidden, gin.H{"error": "account is disabled, not yet approved, or suspended"})
return
}
// Give the fields on the request form a first pass to make sure the request is superficially valid.
l.Trace("parsing request form")
form := &advancedStatusCreateForm{}
if err := c.ShouldBind(form); err != nil || form == nil {
@ -76,24 +77,70 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": "missing one or more required form values"})
return
}
l.Tracef("validating form %+v", form)
if err := validateCreateStatus(form, m.config.StatusesConfig, authed.Account.ID, m.db); err != nil {
if err := validateCreateStatus(form, m.config.StatusesConfig); err != nil {
l.Debugf("error validating form: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// At this point we know the account is permitted to post, and we know the request form
// is valid (at least according to the API specifications and the instance configuration).
// So now we can start digging a bit deeper into the status itself.
// If this status is a reply to another status, we need to do a bit of work to establish whether or not this status can be posted:
//
// 1. Does the replied status exist in the database?
// 2. Is the replied status marked as replyable?
// 3. Does a block exist between either the current account or the account that posted the status it's replying to?
//
// If this is all OK, then we fetch the repliedStatus and the repliedAccount for later processing.
repliedStatus := &model.Status{}
repliedAccount := &model.Account{}
if form.InReplyToID != "" {
// check replied status exists + is replyable
if err := m.db.GetByID(form.InReplyToID, repliedStatus); err != nil || !repliedStatus.VisibilityAdvanced.Replyable {
l.Debugf("status id %s cannot be retrieved from the db: %s", form.InReplyToID, err)
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("status with id %s not replyable", form.InReplyToID)})
return
}
// check replied account is known to us
if err := m.db.GetByID(repliedStatus.AccountID, repliedAccount); err != nil {
l.Debugf("error getting account with id %s from the database: %s", repliedStatus.AccountID, err)
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("status with id %s not replyable", form.InReplyToID)})
return
}
// check if a block exists
if blocked, err := m.db.Blocked(authed.Account.ID, repliedAccount.ID); err != nil || blocked {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("status with id %s not replyable", form.InReplyToID)})
return
}
}
attachments := []*model.MediaAttachment{}
for _, mediaID := range form.MediaIDs {
// check these attachments exist
a := &model.MediaAttachment{}
if err := m.db.GetByID(mediaID, a); err != nil {
l.Debugf("invalid media type or media not found for media id %s: %s", m, err)
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("invalid media type or media not found for media id %s", mediaID)})
return
}
// check they belong to the requesting account id
if a.AccountID != authed.Account.ID {
l.Debugf("media attachment %s does not belong to account id %s", m, authed.Account.ID)
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("media with id %s does not belong to account %s", mediaID, authed.Account.ID)})
return
}
attachments = append(attachments, a)
}
// here we check if any advanced visibility flags have been set and fiddle with them if so
l.Trace("deriving visibility")
basicVis, advancedVis, err := deriveTotalVisibility(form.Visibility, form.AdvancedVisibility, authed.Account.Privacy)
clientIP := c.ClientIP()
l.Tracef("attempting to parse client ip address %s", clientIP)
signUpIP := net.ParseIP(clientIP)
if signUpIP == nil {
l.Debugf("error validating client ip address %s", clientIP)
c.JSON(http.StatusBadRequest, gin.H{"error": "ip address could not be parsed from request"})
if err != nil {
l.Debugf("error parsing visibility: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
@ -142,17 +189,31 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) {
// take care of side effects -- federation, mentions, updating metadata, etc, etc
m.distributor.FromClientAPI() <- distributor.FromClientAPI{
APObjectType: model.ActivityStreamsNote,
APObjectType: model.ActivityStreamsNote,
APActivityType: model.ActivityStreamsCreate,
Activity: newStatus,
Activity: newStatus,
}
// return populated status to submitter
// mastoStatus := &mastotypes.Status{
// ID: newStatus.ID,
// CreatedAt: time.Now().Format(time.RFC3339),
// InReplyToID: newStatus.InReplyToID,
// InReplyToAccountID: newStatus.InReplyToAccountID,
// }
clientIP := c.ClientIP()
l.Tracef("attempting to parse client ip address %s", clientIP)
signUpIP := net.ParseIP(clientIP)
if signUpIP == nil {
l.Debugf("error validating client ip address %s", clientIP)
c.JSON(http.StatusBadRequest, gin.H{"error": "ip address could not be parsed from request"})
return
}
}
func validateCreateStatus(form *advancedStatusCreateForm, config *config.StatusesConfig, accountID string, db db.DB) error {
func validateCreateStatus(form *advancedStatusCreateForm, config *config.StatusesConfig) error {
// validate that, structurally, we have a valid status/post
if form.Status == "" && form.MediaIDs == nil && form.Poll == nil {
return errors.New("no status, media, or poll provided")
@ -174,18 +235,6 @@ func validateCreateStatus(form *advancedStatusCreateForm, config *config.Statuse
return fmt.Errorf("too many media files attached to status, %d attached but limit is %d", len(form.MediaIDs), config.MaxMediaFiles)
}
for _, m := range form.MediaIDs {
// check these attachments exist
a := &model.MediaAttachment{}
if err := db.GetByID(m, a); err != nil {
return fmt.Errorf("invalid media type or media not found for media id %s: %s", m, err)
}
// check they belong to the requesting account id
if a.AccountID != accountID {
return fmt.Errorf("media attachment %s does not belong to account id %s", m, accountID)
}
}
// validate poll
if form.Poll != nil {
if form.Poll.Options == nil {
@ -201,17 +250,6 @@ func validateCreateStatus(form *advancedStatusCreateForm, config *config.Statuse
}
}
// validate reply-to status exists and is reply-able
if form.InReplyToID != "" {
s := &model.Status{}
if err := db.GetByID(form.InReplyToID, s); err != nil {
return fmt.Errorf("status id %s cannot be retrieved from the db: %s", form.InReplyToID, err)
}
if !s.VisibilityAdvanced.Replyable {
return fmt.Errorf("status with id %s is not replyable", form.InReplyToID)
}
}
// validate spoiler text/cw
if form.SpoilerText != "" {
if len(form.SpoilerText) > config.CWMaxChars {

View file

@ -174,6 +174,10 @@ type DB interface {
// The passed mediaAttachment pointer will be populated with the value of the header, if it exists.
GetHeaderForAccountID(header *model.MediaAttachment, accountID string) error
// Blocked checks whether a block exists in eiher direction between two accounts.
// That is, it returns true if account1 blocks account2, OR if account2 blocks account1.
Blocked(account1 string, account2 string) (bool, error)
/*
USEFUL CONVERSION FUNCTIONS
*/

View file

@ -0,0 +1,19 @@
package model
import "time"
// Block refers to the blocking of one account by another.
type Block struct {
// id of this block in the database
ID string `pg:"type:uuid,default:gen_random_uuid(),pk,notnull"`
// When was this block created
CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
// When was this block updated
UpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
// Who created this block?
AccountID string `pg:",notnull"`
// Who is targeted by this block?
TargetAccountID string `pg:",notnull"`
// Activitypub URI for this block
URI string
}

View file

@ -511,6 +511,22 @@ func (ps *postgresService) GetAvatarForAccountID(avatar *model.MediaAttachment,
return nil
}
func (ps *postgresService) Blocked(account1 string, account2 string) (bool, error) {
var blocked bool
if err := ps.conn.Model(&model.Block{}).
Where("account_id = ?", account1).Where("target_account_id = ?", account2).
WhereOr("target_account_id = ?", account1).Where("account_id = ?", account2).
Select(); err != nil {
if err == pg.ErrNoRows {
blocked = false
} else {
return blocked, err
}
}
blocked = true
return blocked, nil
}
/*
CONVERSION FUNCTIONS
*/
@ -712,7 +728,7 @@ func (ps *postgresService) MentionStringsToMentions(targetAccounts []string, ori
// id, createdAt and updatedAt will be populated by the db, so we have everything we need!
menchies = append(menchies, &model.Mention{
StatusID: statusID,
StatusID: statusID,
OriginAccountID: originAccountID,
TargetAccountID: mentionedAccount.ID,
})
@ -745,7 +761,7 @@ func (ps *postgresService) EmojiStringsToEmojis(emojis []string, originAccountID
continue
}
// a serious error has happened so bail
return nil, fmt.Errorf("error getting emoji with shortcode %s: %s",e, err)
return nil, fmt.Errorf("error getting emoji with shortcode %s: %s", e, err)
}
newEmojis = append(newEmojis, emoji)
}