1
0
Fork 0
mirror of https://codeberg.org/forgejo/forgejo.git synced 2025-08-02 00:15:21 +02:00
forgejo/models/moderation/abuse_report.go
floss4good 69d374435b fix: abuse reports string data types (#8267)
Follow-up of !6977

I was fooled by the fact that for SQLite the columns corresponding to `string` fields were created as `TEXT`; but this is not the case for PostgreSQL and MariaDB/MySQL. According to XORM default mapping rules[^1] _String is corresponding to varchar(255)_.

Therefore `abuse_report`.`remarks` should be of type `VARCHAR(500)` and `abuse_report_shadow_copy`.`raw_value` of type `LONGTEXT`.

### Testing

I have dropped the affected columns (or the entire tables) and checked that for PostgreSQL and MariaDB they are created with the correct type and also manually tested the abusive content reporting functionality in order to make sure that no DB error will be returned if for 'Remarks' a text longer than 255 characters is submitted or when a (big) shadow copy is created.

[^1]: https://xorm.io/docs/chapter-02/4.columns/

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8267
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: floss4good <floss4good@disroot.org>
Co-committed-by: floss4good <floss4good@disroot.org>
2025-06-27 15:47:58 +02:00

177 lines
7 KiB
Go

// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package moderation
import (
"context"
"database/sql"
"errors"
"slices"
"forgejo.org/models/db"
"forgejo.org/modules/log"
"forgejo.org/modules/timeutil"
"xorm.io/builder"
)
// ReportStatusType defines the statuses a report (of abusive content) can have.
type ReportStatusType int
const (
// ReportStatusTypeOpen represents the status of open reports that were not yet handled in any way.
ReportStatusTypeOpen ReportStatusType = iota + 1 // 1
// ReportStatusTypeHandled represents the status of valid reports, that have been acted upon.
ReportStatusTypeHandled // 2
// ReportStatusTypeIgnored represents the status of ignored reports, that were closed without any action.
ReportStatusTypeIgnored // 3
)
type (
// AbuseCategoryType defines the categories in which a user can include the reported content.
AbuseCategoryType int
// AbuseCategoryItem defines a pair of value and it's corresponding translation key
// (used to add options within the dropdown shown when new reports are submitted).
AbuseCategoryItem struct {
Value AbuseCategoryType
TranslationKey string
}
)
const (
AbuseCategoryTypeOther AbuseCategoryType = iota + 1 // 1 (Other violations of platform rules)
AbuseCategoryTypeSpam // 2
AbuseCategoryTypeMalware // 3
AbuseCategoryTypeIllegalContent // 4
)
// GetAbuseCategoriesList returns a list of pairs with the available abuse category types
// and their corresponding translation keys
func GetAbuseCategoriesList() []AbuseCategoryItem {
return []AbuseCategoryItem{
{AbuseCategoryTypeSpam, "moderation.abuse_category.spam"},
{AbuseCategoryTypeMalware, "moderation.abuse_category.malware"},
{AbuseCategoryTypeIllegalContent, "moderation.abuse_category.illegal_content"},
{AbuseCategoryTypeOther, "moderation.abuse_category.other_violations"},
}
}
// ReportedContentType defines the types of content that can be reported
// (i.e. user/organization profile, repository, issue/pull, comment).
type ReportedContentType int
const (
// ReportedContentTypeUser should be used when reporting abusive users or organizations.
ReportedContentTypeUser ReportedContentType = iota + 1 // 1
// ReportedContentTypeRepository should be used when reporting a repository with abusive content.
ReportedContentTypeRepository // 2
// ReportedContentTypeIssue should be used when reporting an issue or pull request with abusive content.
ReportedContentTypeIssue // 3
// ReportedContentTypeComment should be used when reporting a comment with abusive content.
ReportedContentTypeComment // 4
)
var allReportedContentTypes = []ReportedContentType{
ReportedContentTypeUser,
ReportedContentTypeRepository,
ReportedContentTypeIssue,
ReportedContentTypeComment,
}
func (t ReportedContentType) IsValid() bool {
return slices.Contains(allReportedContentTypes, t)
}
// AbuseReport represents a report of abusive content.
type AbuseReport struct {
ID int64 `xorm:"pk autoincr"`
Status ReportStatusType `xorm:"INDEX NOT NULL DEFAULT 1"`
// The ID of the user who submitted the report.
ReporterID int64 `xorm:"NOT NULL"`
// Reported content type: user/organization profile, repository, issue/pull or comment.
ContentType ReportedContentType `xorm:"INDEX NOT NULL"`
// The ID of the reported item (based on ContentType: user, repository, issue or comment).
ContentID int64 `xorm:"NOT NULL"`
// The abuse category selected by the reporter.
Category AbuseCategoryType `xorm:"INDEX NOT NULL"`
// Remarks provided by the reporter.
Remarks string `xorm:"VARCHAR(500)"`
// The ID of the corresponding shadow-copied content when exists; otherwise null.
ShadowCopyID sql.NullInt64 `xorm:"DEFAULT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
}
var ErrSelfReporting = errors.New("reporting yourself is not allowed")
func init() {
// RegisterModel will create the table if does not already exist
// or any missing columns if the table was previously created.
// It will not drop or rename existing columns (when struct has changed).
db.RegisterModel(new(AbuseReport))
}
// IsShadowCopyNeeded reports whether one or more reports were already submitted
// for contentType and contentID and not yet linked to a shadow copy (regardless their status).
func IsShadowCopyNeeded(ctx context.Context, contentType ReportedContentType, contentID int64) (bool, error) {
return db.GetEngine(ctx).Cols("id").Where(builder.IsNull{"shadow_copy_id"}).Exist(
&AbuseReport{ContentType: contentType, ContentID: contentID},
)
}
// AlreadyReportedByAndOpen returns if doerID has already submitted a report for contentType and contentID that is still Open.
func AlreadyReportedByAndOpen(ctx context.Context, doerID int64, contentType ReportedContentType, contentID int64) bool {
reported, _ := db.GetEngine(ctx).Exist(&AbuseReport{
Status: ReportStatusTypeOpen,
ReporterID: doerID,
ContentType: contentType,
ContentID: contentID,
})
return reported
}
// ReportAbuse creates a new abuse report in the DB with 'Open' status.
// If the reported content is the user profile of the reporter ErrSelfReporting is returned.
// If there is already an open report submitted by the same user for the same content,
// the request will be ignored without returning an error (and a warning will be logged).
func ReportAbuse(ctx context.Context, report *AbuseReport) error {
if report.ContentType == ReportedContentTypeUser && report.ReporterID == report.ContentID {
return ErrSelfReporting
}
if AlreadyReportedByAndOpen(ctx, report.ReporterID, report.ContentType, report.ContentID) {
log.Warn("Seems that user %d wanted to report again the content with type %d and ID %d; this request will be ignored.", report.ReporterID, report.ContentType, report.ContentID)
return nil
}
report.Status = ReportStatusTypeOpen
_, err := db.GetEngine(ctx).Insert(report)
return err
}
/*
// MarkAsHandled will change the status to 'Handled' for all reports linked to the same item (user, repository, issue or comment).
func MarkAsHandled(ctx context.Context, contentType ReportedContentType, contentID int64) error {
return updateStatus(ctx, contentType, contentID, ReportStatusTypeHandled)
}
// MarkAsIgnored will change the status to 'Ignored' for all reports linked to the same item (user, repository, issue or comment).
func MarkAsIgnored(ctx context.Context, contentType ReportedContentType, contentID int64) error {
return updateStatus(ctx, contentType, contentID, ReportStatusTypeIgnored)
}
// updateStatus will set the provided status for any reports linked to the item with the given type and ID.
func updateStatus(ctx context.Context, contentType ReportedContentType, contentID int64, status ReportStatusType) error {
_, err := db.GetEngine(ctx).Where(builder.Eq{
"content_type": contentType,
"content_id": contentID,
}).Cols("status").Update(&AbuseReport{Status: status})
return err
}
*/