validate like activities

This commit is contained in:
Michael Jerger 2024-04-27 13:01:28 +02:00
parent 08f5a25d3b
commit 095a7a9d22
13 changed files with 844 additions and 1 deletions

2
go.mod
View file

@ -93,6 +93,7 @@ require (
github.com/syndtr/goleveldb v1.0.0
github.com/ulikunitz/xz v0.5.11
github.com/urfave/cli/v2 v2.27.1
github.com/valyala/fastjson v1.6.4
github.com/xanzy/go-gitlab v0.96.0
github.com/yohcop/openid-go v1.0.1
github.com/yuin/goldmark v1.6.0
@ -266,7 +267,6 @@ require (
github.com/unknwon/com v1.0.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/fastjson v1.6.4 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect

View file

@ -0,0 +1,57 @@
// Copyright 2023, 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package forgefed
import (
"time"
"code.gitea.io/gitea/modules/validation"
ap "github.com/go-ap/activitypub"
)
// ForgeLike activity data type
// swagger:model
type ForgeLike struct {
// swagger:ignore
ap.Activity
}
func NewForgeLike(actorIRI, objectIRI string, startTime time.Time) (ForgeLike, error) {
result := ForgeLike{}
result.Type = ap.LikeType
result.Actor = ap.IRI(actorIRI) // Thats us, a User
result.Object = ap.IRI(objectIRI) // Thats them, a Repository
result.StartTime = startTime
if valid, err := validation.IsValid(result); !valid {
return ForgeLike{}, err
}
return result, nil
}
func (like ForgeLike) MarshalJSON() ([]byte, error) {
return like.Activity.MarshalJSON()
}
func (like *ForgeLike) UnmarshalJSON(data []byte) error {
return like.Activity.UnmarshalJSON(data)
}
func (like ForgeLike) IsNewer(compareTo time.Time) bool {
return like.StartTime.After(compareTo)
}
func (like ForgeLike) Validate() []string {
var result []string
result = append(result, validation.ValidateNotEmpty(string(like.Type), "type")...)
result = append(result, validation.ValidateOneOf(string(like.Type), []any{"Like"})...)
result = append(result, validation.ValidateNotEmpty(like.Actor.GetID().String(), "actor")...)
result = append(result, validation.ValidateNotEmpty(like.Object.GetID().String(), "object")...)
result = append(result, validation.ValidateNotEmpty(like.StartTime.String(), "startTime")...)
if like.StartTime.IsZero() {
result = append(result, "StartTime was invalid.")
}
return result
}

View file

@ -0,0 +1,145 @@
// Copyright 2023, 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package forgefed
import (
"reflect"
"testing"
"time"
"code.gitea.io/gitea/modules/validation"
ap "github.com/go-ap/activitypub"
)
func Test_NewForgeLike(t *testing.T) {
actorIRI := "https://repo.prod.meissa.de/api/v1/activitypub/user-id/1"
objectIRI := "https://codeberg.org/api/v1/activitypub/repository-id/1"
want := []byte(`{"type":"Like","startTime":"2024-03-27T00:00:00Z","actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1","object":"https://codeberg.org/api/v1/activitypub/repository-id/1"}`)
startTime, _ := time.Parse("2006-Jan-02", "2024-Mar-27")
sut, err := NewForgeLike(actorIRI, objectIRI, startTime)
if err != nil {
t.Errorf("unexpected error: %v\n", err)
}
if valid, _ := validation.IsValid(sut); !valid {
t.Errorf("sut expected to be valid: %v\n", sut.Validate())
}
got, err := sut.MarshalJSON()
if err != nil {
t.Errorf("MarshalJSON() error = \"%v\"", err)
return
}
if !reflect.DeepEqual(got, want) {
t.Errorf("MarshalJSON() got = %q, want %q", got, want)
}
}
func Test_StarMarshalJSON(t *testing.T) {
type testPair struct {
item ForgeLike
want []byte
wantErr error
}
tests := map[string]testPair{
"empty": {
item: ForgeLike{},
want: nil,
},
"with ID": {
item: ForgeLike{
Activity: ap.Activity{
Actor: ap.IRI("https://repo.prod.meissa.de/api/v1/activitypub/user-id/1"),
Type: "Like",
Object: ap.IRI("https://codeberg.org/api/v1/activitypub/repository-id/1"),
},
},
want: []byte(`{"type":"Like","actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1","object":"https://codeberg.org/api/v1/activitypub/repository-id/1"}`),
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
got, err := tt.item.MarshalJSON()
if (err != nil || tt.wantErr != nil) && tt.wantErr.Error() != err.Error() {
t.Errorf("MarshalJSON() error = \"%v\", wantErr \"%v\"", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("MarshalJSON() got = %q, want %q", got, tt.want)
}
})
}
}
func Test_StarUnmarshalJSON(t *testing.T) {
type testPair struct {
item []byte
want *ForgeLike
wantErr error
}
tests := map[string]testPair{
"with ID": {
item: []byte(`{"type":"Like","actor":"https://repo.prod.meissa.de/api/activitypub/user-id/1","object":"https://codeberg.org/api/activitypub/repository-id/1"}`),
want: &ForgeLike{
Activity: ap.Activity{
Actor: ap.IRI("https://repo.prod.meissa.de/api/activitypub/user-id/1"),
Type: "Like",
Object: ap.IRI("https://codeberg.org/api/activitypub/repository-id/1"),
},
},
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
got := new(ForgeLike)
err := got.UnmarshalJSON(tt.item)
if (err != nil || tt.wantErr != nil) && tt.wantErr.Error() != err.Error() {
t.Errorf("UnmarshalJSON() error = \"%v\", wantErr \"%v\"", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("UnmarshalJSON() got = %q, want %q", got, tt.want)
}
})
}
}
func TestActivityValidation(t *testing.T) {
sut := new(ForgeLike)
sut.UnmarshalJSON([]byte(`{"type":"Like",
"actor":"https://repo.prod.meissa.de/api/activitypub/user-id/1",
"object":"https://codeberg.org/api/activitypub/repository-id/1",
"startTime": "2014-12-31T23:00:00-08:00"}`))
if res, _ := validation.IsValid(sut); !res {
t.Errorf("sut expected to be valid: %v\n", sut.Validate())
}
sut.UnmarshalJSON([]byte(`{"actor":"https://repo.prod.meissa.de/api/activitypub/user-id/1",
"object":"https://codeberg.org/api/activitypub/repository-id/1",
"startTime": "2014-12-31T23:00:00-08:00"}`))
if sut.Validate()[0] != "Field type should not be empty" {
t.Errorf("validation error expected but was: %v\n", sut.Validate()[0])
}
sut.UnmarshalJSON([]byte(`{"type":"bad-type",
"actor":"https://repo.prod.meissa.de/api/activitypub/user-id/1",
"object":"https://codeberg.org/api/activitypub/repository-id/1",
"startTime": "2014-12-31T23:00:00-08:00"}`))
if sut.Validate()[0] != "Value bad-type is not contained in allowed values [Like]" {
t.Errorf("validation error expected but was: %v\n", sut.Validate()[0])
}
sut.UnmarshalJSON([]byte(`{"type":"Like",
"actor":"https://repo.prod.meissa.de/api/activitypub/user-id/1",
"object":"https://codeberg.org/api/activitypub/repository-id/1",
"startTime": "not a date"}`))
if sut.Validate()[0] != "StartTime was invalid." {
t.Errorf("validation error expected but was: %v\n", sut.Validate())
}
}

View file

@ -0,0 +1,49 @@
// Copyright 2023 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package forgefed
import (
ap "github.com/go-ap/activitypub"
"github.com/valyala/fastjson"
)
const ForgeFedNamespaceURI = "https://forgefed.org/ns"
// GetItemByType instantiates a new ForgeFed object if the type matches
// otherwise it defaults to existing activitypub package typer function.
func GetItemByType(typ ap.ActivityVocabularyType) (ap.Item, error) {
switch typ {
case RepositoryType:
return RepositoryNew(""), nil
}
return ap.GetItemByType(typ)
}
// JSONUnmarshalerFn is the function that will load the data from a fastjson.Value into an Item
// that the go-ap/activitypub package doesn't know about.
func JSONUnmarshalerFn(typ ap.ActivityVocabularyType, val *fastjson.Value, i ap.Item) error {
switch typ {
case RepositoryType:
return OnRepository(i, func(r *Repository) error {
return JSONLoadRepository(val, r)
})
}
return nil
}
// NotEmpty is the function that checks if an object is empty
func NotEmpty(i ap.Item) bool {
if ap.IsNil(i) {
return false
}
switch i.GetType() {
case RepositoryType:
r, err := ToRepository(i)
if err != nil {
return false
}
return ap.NotEmpty(r.Actor)
}
return ap.NotEmpty(i)
}

View file

@ -0,0 +1,111 @@
// Copyright 2023 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package forgefed
import (
"reflect"
"unsafe"
ap "github.com/go-ap/activitypub"
"github.com/valyala/fastjson"
)
const (
RepositoryType ap.ActivityVocabularyType = "Repository"
)
type Repository struct {
ap.Actor
// Team Collection of actors who have management/push access to the repository
Team ap.Item `jsonld:"team,omitempty"`
// Forks OrderedCollection of repositories that are forks of this repository
Forks ap.Item `jsonld:"forks,omitempty"`
// ForkedFrom Identifies the repository which this repository was created as a fork
ForkedFrom ap.Item `jsonld:"forkedFrom,omitempty"`
}
// RepositoryNew initializes a Repository type actor
func RepositoryNew(id ap.ID) *Repository {
a := ap.ActorNew(id, RepositoryType)
a.Type = RepositoryType
o := Repository{Actor: *a}
return &o
}
func (r Repository) MarshalJSON() ([]byte, error) {
b, err := r.Actor.MarshalJSON()
if len(b) == 0 || err != nil {
return nil, err
}
b = b[:len(b)-1]
if r.Team != nil {
ap.JSONWriteItemProp(&b, "team", r.Team)
}
if r.Forks != nil {
ap.JSONWriteItemProp(&b, "forks", r.Forks)
}
if r.ForkedFrom != nil {
ap.JSONWriteItemProp(&b, "forkedFrom", r.ForkedFrom)
}
ap.JSONWrite(&b, '}')
return b, nil
}
func JSONLoadRepository(val *fastjson.Value, r *Repository) error {
if err := ap.OnActor(&r.Actor, func(a *ap.Actor) error {
return ap.JSONLoadActor(val, a)
}); err != nil {
return err
}
r.Team = ap.JSONGetItem(val, "team")
r.Forks = ap.JSONGetItem(val, "forks")
r.ForkedFrom = ap.JSONGetItem(val, "forkedFrom")
return nil
}
func (r *Repository) UnmarshalJSON(data []byte) error {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
return JSONLoadRepository(val, r)
}
// ToRepository tries to convert the it Item to a Repository Actor.
func ToRepository(it ap.Item) (*Repository, error) {
switch i := it.(type) {
case *Repository:
return i, nil
case Repository:
return &i, nil
case *ap.Actor:
return (*Repository)(unsafe.Pointer(i)), nil
case ap.Actor:
return (*Repository)(unsafe.Pointer(&i)), nil
default:
// NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes
typ := reflect.TypeOf(new(Repository))
if i, ok := reflect.ValueOf(it).Convert(typ).Interface().(*Repository); ok {
return i, nil
}
}
return nil, ap.ErrorInvalidType[ap.Actor](it)
}
type withRepositoryFn func(*Repository) error
// OnRepository calls function fn on it Item if it can be asserted to type *Repository
func OnRepository(it ap.Item, fn withRepositoryFn) error {
if it == nil {
return nil
}
ob, err := ToRepository(it)
if err != nil {
return err
}
return fn(ob)
}

View file

@ -0,0 +1,145 @@
// Copyright 2023 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package forgefed
import (
"fmt"
"reflect"
"testing"
"code.gitea.io/gitea/modules/json"
ap "github.com/go-ap/activitypub"
)
func Test_RepositoryMarshalJSON(t *testing.T) {
type testPair struct {
item Repository
want []byte
wantErr error
}
tests := map[string]testPair{
"empty": {
item: Repository{},
want: nil,
},
"with ID": {
item: Repository{
Actor: ap.Actor{
ID: "https://example.com/1",
},
Team: nil,
},
want: []byte(`{"id":"https://example.com/1"}`),
},
"with Team as IRI": {
item: Repository{
Team: ap.IRI("https://example.com/1"),
Actor: ap.Actor{
ID: "https://example.com/1",
},
},
want: []byte(`{"id":"https://example.com/1","team":"https://example.com/1"}`),
},
"with Team as IRIs": {
item: Repository{
Team: ap.ItemCollection{
ap.IRI("https://example.com/1"),
ap.IRI("https://example.com/2"),
},
Actor: ap.Actor{
ID: "https://example.com/1",
},
},
want: []byte(`{"id":"https://example.com/1","team":["https://example.com/1","https://example.com/2"]}`),
},
"with Team as Object": {
item: Repository{
Team: ap.Object{ID: "https://example.com/1"},
Actor: ap.Actor{
ID: "https://example.com/1",
},
},
want: []byte(`{"id":"https://example.com/1","team":{"id":"https://example.com/1"}}`),
},
"with Team as slice of Objects": {
item: Repository{
Team: ap.ItemCollection{
ap.Object{ID: "https://example.com/1"},
ap.Object{ID: "https://example.com/2"},
},
Actor: ap.Actor{
ID: "https://example.com/1",
},
},
want: []byte(`{"id":"https://example.com/1","team":[{"id":"https://example.com/1"},{"id":"https://example.com/2"}]}`),
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
got, err := tt.item.MarshalJSON()
if (err != nil || tt.wantErr != nil) && tt.wantErr.Error() != err.Error() {
t.Errorf("MarshalJSON() error = \"%v\", wantErr \"%v\"", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("MarshalJSON() got = %q, want %q", got, tt.want)
}
})
}
}
func Test_RepositoryUnmarshalJSON(t *testing.T) {
type testPair struct {
data []byte
want *Repository
wantErr error
}
tests := map[string]testPair{
"nil": {
data: nil,
wantErr: fmt.Errorf("cannot parse JSON: %w", fmt.Errorf("cannot parse empty string; unparsed tail: %q", "")),
},
"empty": {
data: []byte{},
wantErr: fmt.Errorf("cannot parse JSON: %w", fmt.Errorf("cannot parse empty string; unparsed tail: %q", "")),
},
"with Type": {
data: []byte(`{"type":"Repository"}`),
want: &Repository{
Actor: ap.Actor{
Type: RepositoryType,
},
},
},
"with Type and ID": {
data: []byte(`{"id":"https://example.com/1","type":"Repository"}`),
want: &Repository{
Actor: ap.Actor{
ID: "https://example.com/1",
Type: RepositoryType,
},
},
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
got := new(Repository)
err := got.UnmarshalJSON(tt.data)
if (err != nil || tt.wantErr != nil) && tt.wantErr.Error() != err.Error() {
t.Errorf("UnmarshalJSON() error = \"%v\", wantErr \"%v\"", err, tt.wantErr)
return
}
if tt.want != nil && !reflect.DeepEqual(got, tt.want) {
jGot, _ := json.Marshal(got)
jWant, _ := json.Marshal(tt.want)
t.Errorf("UnmarshalJSON() got = %s, want %s", jGot, jWant)
}
})
}
}

View file

@ -1,3 +1,4 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// Copyright 2018 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
@ -7,6 +8,7 @@ import (
"net"
"net/url"
"regexp"
"strconv"
"strings"
"code.gitea.io/gitea/modules/setting"
@ -116,6 +118,51 @@ func IsValidExternalTrackerURLFormat(uri string) bool {
return true
}
func IsValidForgejoActivityPubURL(url string) bool {
if !IsValidURL(url) {
return false
}
if !strings.Contains(url, "api/v1/activitypub") {
return false
}
return true
}
func IsValidFollowingRepoURL(url string) bool {
if !IsValidForgejoActivityPubURL(url) {
return false
}
if !strings.Contains(url, "repository-id") {
return false
}
splitURL := strings.Split(url, "/")
repoIDIndex := len(splitURL) - 1
// Is there a valid integer denoting a repo id?
if _, err := strconv.Atoi(splitURL[repoIDIndex]); err != nil {
return false
}
return true
}
func IsValidFollowingRepoURLList(urls string) bool {
switch {
case len(strings.Split(urls, ";")) == 1:
return IsValidFollowingRepoURL(urls)
default:
for _, url := range strings.Split(urls, ";") {
if !IsValidFollowingRepoURLList(url) {
return false
}
}
}
return true
}
// TODO: use validateable/ValidateMaxLen instead !!
func IsOfValidLength(str string) bool {
return len(str) <= 2048
}
var (
validUsernamePatternWithDots = regexp.MustCompile(`^[\da-zA-Z][-.\w]*$`)
validUsernamePatternWithoutDots = regexp.MustCompile(`^[\da-zA-Z][-\w]*$`)

View file

@ -0,0 +1,71 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// Copyright 2023 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package validation
import (
"fmt"
"strings"
"unicode/utf8"
"code.gitea.io/gitea/modules/timeutil"
)
type Validateable interface {
Validate() []string
}
func IsValid(v Validateable) (bool, error) {
if err := v.Validate(); len(err) > 0 {
errString := strings.Join(err, "\n")
return false, fmt.Errorf(errString)
}
return true, nil
}
func ValidateNotEmpty(value any, fieldName string) []string {
isValid := true
switch v := value.(type) {
case string:
if v == "" {
isValid = false
}
case timeutil.TimeStamp:
if v.IsZero() {
isValid = false
}
case int64:
if v == 0 {
isValid = false
}
default:
isValid = false
}
if isValid {
return []string{}
}
return []string{fmt.Sprintf("Field %v should not be empty", fieldName)}
}
func ValidateMaxLen(value string, maxLen int, fieldName string) []string {
if utf8.RuneCountInString(value) > maxLen {
return []string{fmt.Sprintf("Value in field %v was longer than %v", fieldName, maxLen)}
}
return []string{}
}
func ValidateOneOf(value any, allowed []any) []string {
for _, allowedElem := range allowed {
if value == allowedElem {
return []string{}
}
}
return []string{fmt.Sprintf("Value %v is not contained in allowed values %v", value, allowed)}
}
func ValidateSuffix(str, suffix string) bool {
return strings.HasSuffix(str, suffix)
}

View file

@ -0,0 +1,43 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package validation
import (
"testing"
"code.gitea.io/gitea/modules/timeutil"
)
func Test_ValidateNotEmpty_ForString(t *testing.T) {
sut := ""
if len(ValidateNotEmpty(sut, "dummyField")) == 0 {
t.Errorf("sut should be invalid")
}
sut = "not empty"
if res := ValidateNotEmpty(sut, "dummyField"); len(res) > 0 {
t.Errorf("sut should be valid but was %q", res)
}
}
func Test_ValidateNotEmpty_ForTimestamp(t *testing.T) {
sut := timeutil.TimeStamp(0)
if res := ValidateNotEmpty(sut, "dummyField"); len(res) == 0 {
t.Errorf("sut should be invalid")
}
sut = timeutil.TimeStampNow()
if res := ValidateNotEmpty(sut, "dummyField"); len(res) > 0 {
t.Errorf("sut should be valid but was %q", res)
}
}
func Test_ValidateMaxLen(t *testing.T) {
sut := "0123456789"
if len(ValidateMaxLen(sut, 9, "dummyField")) == 0 {
t.Errorf("sut should be invalid")
}
sut = "0123456789"
if res := ValidateMaxLen(sut, 11, "dummyField"); len(res) > 0 {
t.Errorf("sut should be valid but was %q", res)
}
}

View file

@ -0,0 +1,83 @@
// Copyright 2023, 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package activitypub
import (
"fmt"
"net/http"
"strings"
forgefed_model "code.gitea.io/gitea/models/forgefed"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/federation"
ap "github.com/go-ap/activitypub"
)
// Repository function returns the Repository actor for a repo
func Repository(ctx *context.APIContext) {
// swagger:operation GET /activitypub/repository-id/{repository-id} activitypub activitypubRepository
// ---
// summary: Returns the Repository actor for a repo
// produces:
// - application/json
// parameters:
// - name: repository-id
// in: path
// description: repository ID of the repo
// type: integer
// required: true
// responses:
// "200":
// "$ref": "#/responses/ActivityPub"
link := fmt.Sprintf("%s/api/v1/activitypub/repository-id/%d", strings.TrimSuffix(setting.AppURL, "/"), ctx.Repo.Repository.ID)
repo := forgefed_model.RepositoryNew(ap.IRI(link))
repo.Name = ap.NaturalLanguageValuesNew()
err := repo.Name.Set("en", ap.Content(ctx.Repo.Repository.Name))
if err != nil {
ctx.Error(http.StatusInternalServerError, "Set Name", err)
return
}
response(ctx, repo)
}
// PersonInbox function handles the incoming data for a repository inbox
func RepositoryInbox(ctx *context.APIContext) {
// swagger:operation POST /activitypub/repository-id/{repository-id}/inbox activitypub activitypubRepositoryInbox
// ---
// summary: Send to the inbox
// produces:
// - application/json
// parameters:
// - name: repository-id
// in: path
// description: repository ID of the repo
// type: integer
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/ForgeLike"
// responses:
// "204":
// "$ref": "#/responses/empty"
repository := ctx.Repo.Repository
log.Info("RepositoryInbox: repo: %v", repository)
form := web.GetForm(ctx)
httpStatus, title, err := federation.ProcessLikeActivity(ctx, form, repository.ID)
if err != nil {
log.Error("Status: %v", httpStatus)
log.Error("Title: %v", title)
log.Error("Error: %v", err)
ctx.Error(httpStatus, title, err)
}
ctx.Status(http.StatusNoContent)
}

View file

@ -0,0 +1,27 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package activitypub
import (
"testing"
"code.gitea.io/gitea/models/user"
)
func Test_UserEmailValidate(t *testing.T) {
sut := "ab@cd.ef"
if err := user.ValidateEmail(sut); err != nil {
t.Errorf("sut should be valid, %v, %v", sut, err)
}
sut = "83ce13c8-af0b-4112-8327-55a54e54e664@code.cartoon-aa.xyz"
if err := user.ValidateEmail(sut); err != nil {
t.Errorf("sut should be valid, %v, %v", sut, err)
}
sut = "1"
if err := user.ValidateEmail(sut); err == nil {
t.Errorf("sut should not be valid, %v", sut)
}
}

View file

@ -0,0 +1,35 @@
// Copyright 2023 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package activitypub
import (
"net/http"
"code.gitea.io/gitea/models/forgefed"
"code.gitea.io/gitea/modules/activitypub"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/services/context"
ap "github.com/go-ap/activitypub"
"github.com/go-ap/jsonld"
)
// Respond with an ActivityStreams object
func response(ctx *context.APIContext, v interface{}) {
binary, err := jsonld.WithContext(
jsonld.IRI(ap.ActivityBaseURI),
jsonld.IRI(ap.SecurityContextURI),
jsonld.IRI(forgefed.ForgeFedNamespaceURI),
).Marshal(v)
if err != nil {
ctx.ServerError("Marshal", err)
return
}
ctx.Resp.Header().Add("Content-Type", activitypub.ActivityStreamsContentType)
ctx.Resp.WriteHeader(http.StatusOK)
if _, err = ctx.Resp.Write(binary); err != nil {
log.Error("write to resp err: %v", err)
}
}

View file

@ -0,0 +1,30 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package federation
import (
"context"
"net/http"
"code.gitea.io/gitea/models/forgefed"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/validation"
)
// ProcessLikeActivity receives a ForgeLike activity and does the following:
// Validation of the activity
// Creation of a (remote) federationHost if not existing
// Creation of a forgefed Person if not existing
// Validation of incoming RepositoryID against Local RepositoryID
// Star the repo if it wasn't already stared
// Do some mitigation against out of order attacks
func ProcessLikeActivity(ctx context.Context, form any, repositoryID int64) (int, string, error) {
activity := form.(*forgefed.ForgeLike)
if res, err := validation.IsValid(activity); !res {
return http.StatusNotAcceptable, "Invalid activity", err
}
log.Info("Activity validated:%v", activity)
return 0, "", nil
}