Found NPE & added integration test

This commit is contained in:
Michael Jerger 2024-05-01 16:03:42 +02:00
parent 978e4a25a2
commit 720367af68
8 changed files with 240 additions and 22 deletions

View file

@ -45,9 +45,17 @@ func (like ForgeLike) IsNewer(compareTo time.Time) bool {
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.ValidateOneOf(string(like.Type), []any{"Like"}, "type")...)
if like.Actor == nil {
result = append(result, "Actor should not be nil.")
} else {
result = append(result, validation.ValidateNotEmpty(like.Actor.GetID().String(), "actor")...)
}
if like.Object == nil {
result = append(result, "Object should not be nil.")
} else {
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.")

View file

@ -4,7 +4,9 @@
package forgefed
import (
"fmt"
"reflect"
"strings"
"testing"
"time"
@ -37,7 +39,7 @@ func Test_NewForgeLike(t *testing.T) {
}
}
func Test_StarMarshalJSON(t *testing.T) {
func Test_LikeMarshalJSON(t *testing.T) {
type testPair struct {
item ForgeLike
want []byte
@ -75,7 +77,7 @@ func Test_StarMarshalJSON(t *testing.T) {
}
}
func Test_StarUnmarshalJSON(t *testing.T) {
func Test_LikeUnmarshalJSON(t *testing.T) {
type testPair struct {
item []byte
want *ForgeLike
@ -92,19 +94,25 @@ func Test_StarUnmarshalJSON(t *testing.T) {
Object: ap.IRI("https://codeberg.org/api/activitypub/repository-id/1"),
},
},
wantErr: nil,
},
"invalid": { // ToDo: Here we are testing if the json parser detects invalid json, we could keep this test in case we bould our own.
item: []byte(`{"type":"Invalid","actor":"https://repo.prod.meissa.de/api/activitypub/user-id/1","object":"https://codeberg.org/api/activitypub/repository-id/1"`),
want: &ForgeLike{},
wantErr: fmt.Errorf("cannot parse JSON:"),
},
}
for name, tt := range tests {
for name, test := 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)
err := got.UnmarshalJSON(test.item)
if (err != nil || test.wantErr != nil) && !strings.Contains(err.Error(), test.wantErr.Error()) {
t.Errorf("UnmarshalJSON() error = \"%v\", wantErr \"%v\"", err, test.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("UnmarshalJSON() got = %q, want %q", got, tt.want)
if !reflect.DeepEqual(got, test.want) {
t.Errorf("UnmarshalJSON() got = %q, want %q, err %q", got, test.want, err.Error())
}
})
}
@ -123,7 +131,7 @@ func TestActivityValidation(t *testing.T) {
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" {
if sut.Validate()[0] != "type should not be empty" {
t.Errorf("validation error expected but was: %v\n", sut.Validate()[0])
}
@ -142,4 +150,20 @@ func TestActivityValidation(t *testing.T) {
if sut.Validate()[0] != "StartTime was invalid." {
t.Errorf("validation error expected but was: %v\n", sut.Validate())
}
sut.UnmarshalJSON([]byte(`{"type":"Wrong",
"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 Wrong is not contained in allowed values [Like]" {
t.Errorf("validation error expected but was: %v\n", sut.Validate())
}
}
func TestActivityValidation_Attack(t *testing.T) {
sut := new(ForgeLike)
sut.UnmarshalJSON([]byte(`{rubbish}`))
if len(sut.Validate()) != 5 {
t.Errorf("5 validateion errors expected but was: %v\n", len(sut.Validate()))
}
}

View file

@ -25,7 +25,7 @@ func IsValid(v Validateable) (bool, error) {
return true, nil
}
func ValidateNotEmpty(value any, fieldName string) []string {
func ValidateNotEmpty(value any, name string) []string {
isValid := true
switch v := value.(type) {
case string:
@ -47,17 +47,17 @@ func ValidateNotEmpty(value any, fieldName string) []string {
if isValid {
return []string{}
}
return []string{fmt.Sprintf("Field %v should not be empty", fieldName)}
return []string{fmt.Sprintf("%v should not be empty", name)}
}
func ValidateMaxLen(value string, maxLen int, fieldName string) []string {
func ValidateMaxLen(value string, maxLen int, name string) []string {
if utf8.RuneCountInString(value) > maxLen {
return []string{fmt.Sprintf("Value in field %v was longer than %v", fieldName, maxLen)}
return []string{fmt.Sprintf("Value %v was longer than %v", name, maxLen)}
}
return []string{}
}
func ValidateOneOf(value any, allowed []any) []string {
func ValidateOneOf(value any, allowed []any, name string) []string {
for _, allowedElem := range allowed {
if value == allowedElem {
return []string{}
@ -65,7 +65,3 @@ func ValidateOneOf(value any, allowed []any) []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

@ -9,6 +9,29 @@ import (
"code.gitea.io/gitea/modules/timeutil"
)
type Sut struct {
valid bool
}
func (sut Sut) Validate() []string {
if sut.valid {
return []string{}
} else {
return []string{"invalid"}
}
}
func Test_IsValid(t *testing.T) {
sut := Sut{valid: true}
if res, _ := IsValid(sut); !res {
t.Errorf("sut expected to be valid: %v\n", sut.Validate())
}
sut = Sut{valid: false}
if res, _ := IsValid(sut); res {
t.Errorf("sut expected to be invalid: %v\n", sut.Validate())
}
}
func Test_ValidateNotEmpty_ForString(t *testing.T) {
sut := ""
if len(ValidateNotEmpty(sut, "dummyField")) == 0 {

View file

@ -1,5 +1,6 @@
// Copyright 2015 The Gogs Authors. All rights reserved.
// Copyright 2016 The Gitea Authors. All rights reserved.
// Copyright 2023 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
// Package v1 Gitea API
@ -72,6 +73,7 @@ import (
actions_model "code.gitea.io/gitea/models/actions"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/forgefed"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
@ -773,6 +775,13 @@ func Routes() *web.Route {
m.Get("", activitypub.Person)
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
}, context.UserIDAssignmentAPI())
m.Group("/repository-id/{repository-id}", func() {
m.Get("", activitypub.Repository)
m.Post("/inbox",
bind(forgefed.ForgeLike{}),
// TODO: activitypub.ReqHTTPSignature(),
activitypub.RepositoryInbox)
}, context.RepositoryIDAssignmentAPI())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub))
}
@ -973,7 +982,10 @@ func Routes() *web.Route {
repo.CreateOrgRepoDeprecated)
// requires repo scope
m.Combo("/repositories/{id}", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(repo.GetByID)
m.Combo("/repositories/{id}",
reqToken(),
tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository),
).Get(repo.GetByID)
// Repos (requires repo scope)
m.Group("/repos", func() {

View file

@ -1,9 +1,11 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package swagger
import (
ffed "code.gitea.io/gitea/models/forgefed"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/forms"
)
@ -14,6 +16,9 @@ import (
// parameterBodies
// swagger:response parameterBodies
type swaggerParameterBodies struct {
// in:body
Star ffed.ForgeLike
// in:body
AddCollaboratorOption api.AddCollaboratorOption

View file

@ -0,0 +1,25 @@
// Copyright 2023, 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package context
import (
"net/http"
repo_model "code.gitea.io/gitea/models/repo"
)
// RepositoryIDAssignmentAPI returns a middleware to handle context-repo assignment for api routes
func RepositoryIDAssignmentAPI() func(ctx *APIContext) {
return func(ctx *APIContext) {
repositoryID := ctx.ParamsInt64(":repository-id")
var err error
repository := new(Repository)
repository.Repository, err = repo_model.GetRepositoryByID(ctx, repositoryID)
if err != nil {
ctx.Error(http.StatusNotFound, "GetRepositoryByID", err)
}
ctx.Repo = repository
}
}

View file

@ -0,0 +1,125 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"code.gitea.io/gitea/models/db"
forgefed_model "code.gitea.io/gitea/models/forgefed"
"code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/activitypub"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers"
"github.com/stretchr/testify/assert"
)
func TestActivityPubRepository(t *testing.T) {
setting.Federation.Enabled = true
testWebRoutes = routers.NormalRoutes()
defer func() {
setting.Federation.Enabled = false
testWebRoutes = routers.NormalRoutes()
}()
onGiteaRun(t, func(*testing.T, *url.URL) {
repositoryID := 2
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/activitypub/repository-id/%v", repositoryID))
resp := MakeRequest(t, req, http.StatusOK)
body := resp.Body.Bytes()
assert.Contains(t, string(body), "@context")
var repository forgefed_model.Repository
err := repository.UnmarshalJSON(body)
assert.NoError(t, err)
assert.Regexp(t, fmt.Sprintf("activitypub/repository-id/%v$", repositoryID), repository.GetID().String())
})
}
func TestActivityPubMissingRepository(t *testing.T) {
setting.Federation.Enabled = true
testWebRoutes = routers.NormalRoutes()
defer func() {
setting.Federation.Enabled = false
testWebRoutes = routers.NormalRoutes()
}()
onGiteaRun(t, func(*testing.T, *url.URL) {
repositoryID := 9999999
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/activitypub/repository-id/%v", repositoryID))
resp := MakeRequest(t, req, http.StatusNotFound)
assert.Contains(t, resp.Body.String(), "repository does not exist")
})
}
func TestActivityPubRepositoryInboxValid(t *testing.T) {
setting.Federation.Enabled = true
testWebRoutes = routers.NormalRoutes()
defer func() {
setting.Federation.Enabled = false
testWebRoutes = routers.NormalRoutes()
}()
srv := httptest.NewServer(testWebRoutes)
defer srv.Close()
onGiteaRun(t, func(*testing.T, *url.URL) {
appURL := setting.AppURL
setting.AppURL = srv.URL + "/"
defer func() {
setting.Database.LogSQL = false
setting.AppURL = appURL
}()
actionsUser := user.NewActionsUser()
repositoryID := 2
c, err := activitypub.NewClient(db.DefaultContext, actionsUser, "not used")
assert.NoError(t, err)
repoInboxUrl := fmt.Sprintf("%s/api/v1/activitypub/repository-id/%v/inbox",
srv.URL, repositoryID)
activity := []byte(fmt.Sprintf(`{"type":"Like","startTime":"2024-03-27T00:00:00Z","actor":"%s/api/v1/activitypub/user-id/2","object":"%s/api/v1/activitypub/repository-id/%v"}`,
srv.URL, srv.URL, repositoryID))
resp, err := c.Post(activity, repoInboxUrl)
assert.NoError(t, err)
assert.Equal(t, http.StatusNoContent, resp.StatusCode)
})
}
func TestActivityPubRepositoryInboxInvalid(t *testing.T) {
setting.Federation.Enabled = true
testWebRoutes = routers.NormalRoutes()
defer func() {
setting.Federation.Enabled = false
testWebRoutes = routers.NormalRoutes()
}()
srv := httptest.NewServer(testWebRoutes)
defer srv.Close()
onGiteaRun(t, func(*testing.T, *url.URL) {
appURL := setting.AppURL
setting.AppURL = srv.URL + "/"
defer func() {
setting.Database.LogSQL = false
setting.AppURL = appURL
}()
actionsUser := user.NewActionsUser()
repositoryID := 2
c, err := activitypub.NewClient(db.DefaultContext, actionsUser, "not used")
assert.NoError(t, err)
repoInboxUrl := fmt.Sprintf("%s/api/v1/activitypub/repository-id/%v/inbox",
srv.URL, repositoryID)
activity := []byte(fmt.Sprintf(`{"type":"Wrong"}`))
resp, err := c.Post(activity, repoInboxUrl)
assert.NoError(t, err)
assert.Equal(t, http.StatusNotAcceptable, resp.StatusCode)
})
}