Start NodeInfo implementation

This commit is contained in:
bom 2023-12-29 12:10:07 +01:00 committed by Michael Jerger
parent 3c2493902d
commit 310d740cee
5 changed files with 167 additions and 48 deletions

View file

@ -36,7 +36,7 @@ type RepositoryID struct {
} }
// newActorID receives already validated inputs // newActorID receives already validated inputs
func newActorID(validatedURI *url.URL, source string) (ActorID, error) { func NewActorID(validatedURI *url.URL) (ActorID, error) {
pathWithActorID := strings.Split(validatedURI.Path, "/") pathWithActorID := strings.Split(validatedURI.Path, "/")
if containsEmptyString(pathWithActorID) { if containsEmptyString(pathWithActorID) {
pathWithActorID = removeEmptyStrings(pathWithActorID) pathWithActorID = removeEmptyStrings(pathWithActorID)
@ -47,20 +47,30 @@ func newActorID(validatedURI *url.URL, source string) (ActorID, error) {
result := ActorID{} result := ActorID{}
result.ID = id result.ID = id
result.Source = source
result.Schema = validatedURI.Scheme result.Schema = validatedURI.Scheme
result.Host = validatedURI.Hostname() result.Host = validatedURI.Hostname()
result.Path = pathWithoutActorID result.Path = pathWithoutActorID
result.Port = validatedURI.Port() result.Port = validatedURI.Port()
result.UnvalidatedInput = validatedURI.String() result.UnvalidatedInput = validatedURI.String()
if valid, err := IsValid(result); !valid { if valid, outcome := validation.IsValid(result); !valid {
return ActorId{}, err return ActorID{}, outcome
} }
return result, nil return result, nil
} }
func newActorID(validatedURI *url.URL, source string) (ActorID, error) {
result, err := NewActorID(validatedURI)
if err != nil {
return ActorID{}, err
}
result.Source = source
return result, nil
}
func NewPersonID(uri, source string) (PersonID, error) { func NewPersonID(uri, source string) (PersonID, error) {
// TODO: remove after test // TODO: remove after test
//if !validation.IsValidExternalURL(uri) { //if !validation.IsValidExternalURL(uri) {
@ -138,12 +148,10 @@ func (id PersonID) HostSuffix() string {
func (id ActorID) Validate() []string { func (id ActorID) Validate() []string {
var result []string var result []string
result = append(result, validation.ValidateNotEmpty(id.ID, "userId")...) result = append(result, validation.ValidateNotEmpty(id.ID, "userId")...)
result = append(result, validation.ValidateNotEmpty(id.Source, "source")...)
result = append(result, validation.ValidateNotEmpty(id.Schema, "schema")...) result = append(result, validation.ValidateNotEmpty(id.Schema, "schema")...)
result = append(result, validation.ValidateNotEmpty(id.Path, "path")...) result = append(result, validation.ValidateNotEmpty(id.Path, "path")...)
result = append(result, validation.ValidateNotEmpty(id.Host, "host")...) result = append(result, validation.ValidateNotEmpty(id.Host, "host")...)
result = append(result, validation.ValidateNotEmpty(id.UnvalidatedInput, "unvalidatedInput")...) result = append(result, validation.ValidateNotEmpty(id.UnvalidatedInput, "unvalidatedInput")...)
result = append(result, validation.ValidateOneOf(id.Source, []string{"forgejo", "gitea"})...)
if id.UnvalidatedInput != id.AsURI() { if id.UnvalidatedInput != id.AsURI() {
result = append(result, fmt.Sprintf("not all input: %q was parsed: %q", id.UnvalidatedInput, id.AsURI())) result = append(result, fmt.Sprintf("not all input: %q was parsed: %q", id.UnvalidatedInput, id.AsURI()))
@ -154,6 +162,8 @@ func (id ActorID) Validate() []string {
func (id PersonID) Validate() []string { func (id PersonID) Validate() []string {
result := id.ActorID.Validate() result := id.ActorID.Validate()
result = append(result, validation.ValidateNotEmpty(id.Source, "source")...)
result = append(result, validation.ValidateOneOf(id.Source, []string{"forgejo", "gitea"})...)
switch id.Source { switch id.Source {
case "forgejo", "gitea": case "forgejo", "gitea":
if strings.ToLower(id.Path) != "api/v1/activitypub/user-id" && strings.ToLower(id.Path) != "api/activitypub/user-id" { if strings.ToLower(id.Path) != "api/v1/activitypub/user-id" && strings.ToLower(id.Path) != "api/activitypub/user-id" {
@ -165,6 +175,8 @@ func (id PersonID) Validate() []string {
func (id RepositoryID) Validate() []string { func (id RepositoryID) Validate() []string {
result := id.ActorID.Validate() result := id.ActorID.Validate()
result = append(result, validation.ValidateNotEmpty(id.Source, "source")...)
result = append(result, validation.ValidateOneOf(id.Source, []string{"forgejo", "gitea"})...)
switch id.Source { switch id.Source {
case "forgejo", "gitea": case "forgejo", "gitea":
if strings.ToLower(id.Path) != "api/v1/activitypub/repository-id" && strings.ToLower(id.Path) != "api/activitypub/repository-id" { if strings.ToLower(id.Path) != "api/v1/activitypub/repository-id" && strings.ToLower(id.Path) != "api/activitypub/repository-id" {
@ -192,31 +204,3 @@ func removeEmptyStrings(ls []string) []string {
} }
return rs return rs
} }
func IsValid[T Validateables](value T) (bool, error) {
if err := value.Validate(); len(err) > 0 {
errString := strings.Join(err, "\n")
return false, fmt.Errorf(errString)
}
return true, nil
}
/*
func (a RepositoryId) IsValid() (bool, error) {
if err := a.Validate(); len(err) > 0 {
errString := strings.Join(err, "\n")
return false, fmt.Errorf(errString)
}
return true, nil
}
func (a PersonId) IsValid() (bool, error) {
if err := a.Validate(); len(err) > 0 {
errString := strings.Join(err, "\n")
return false, fmt.Errorf(errString)
}
return true, nil
}
*/

View file

@ -0,0 +1,78 @@
// Copyright 2023 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package forgefed
import (
"net/url"
"code.gitea.io/gitea/modules/validation"
"github.com/valyala/fastjson"
)
type (
SourceType string
)
type SourceTypes []SourceType
const (
ForgejoSourceType SourceType = "frogejo"
)
var KnownSourceTypes = SourceTypes{
ForgejoSourceType,
}
// NodeInfo data type
// swagger:model
type NodeInfoWellKnown struct {
Href string
}
func NodeInfoWellKnownUnmarshalJSON(data []byte) (NodeInfoWellKnown, error) {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return NodeInfoWellKnown{}, err
}
href := string(val.GetStringBytes("links", "0", "href"))
return NodeInfoWellKnown{Href: href}, nil
}
func NewNodeInfoWellKnown(body []byte) (NodeInfoWellKnown, error) {
result, err := NodeInfoWellKnownUnmarshalJSON(body)
if err != nil {
return NodeInfoWellKnown{}, err
}
if valid, outcome := validation.IsValid(result); !valid {
return NodeInfoWellKnown{}, outcome
}
return NodeInfoWellKnown{}, nil
}
// Validate collects error strings in a slice and returns this
func (node NodeInfoWellKnown) Validate() []string {
var result []string
result = append(result, validation.ValidateNotEmpty(node.Href, "Href")...)
parsedUrl, err := url.Parse(node.Href)
if err != nil {
result = append(result, err.Error())
return result
}
if parsedUrl.Host == "" {
result = append(result, "Href has to be absolute")
}
result = append(result, validation.ValidateOneOf(parsedUrl.Scheme, []string{"http", "https"})...)
if parsedUrl.RawQuery != "" {
result = append(result, "Href may not contain query")
}
return result
}

View file

@ -0,0 +1,67 @@
// Copyright 2023 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package forgefed
import (
"fmt"
"reflect"
"testing"
"code.gitea.io/gitea/modules/validation"
)
func Test_NodeInfoWellKnownUnmarshalJSON(t *testing.T) {
type testPair struct {
item []byte
want NodeInfoWellKnown
wantErr error
}
tests := map[string]testPair{
"with href": {
item: []byte(`{"links":[{"href":"https://federated-repo.prod.meissa.de/api/v1/nodeinfo","rel":"http://nodeinfo.diaspora.software/ns/schema/2.1"}]}`),
want: NodeInfoWellKnown{
Href: "https://federated-repo.prod.meissa.de/api/v1/nodeinfo",
},
},
"empty": {
item: []byte(``),
wantErr: fmt.Errorf("cannot parse JSON: cannot parse empty string; unparsed tail: \"\""),
},
// "with too long href": {
// item: []byte(`{"links":[{"href":"https://federated-repo.prod.meissa.de/api/v1/nodeinfohttps://federated-repo.prod.meissa.de/api/v1/nodeinfohttps://federated-repo.prod.meissa.de/api/v1/nodeinfohttps://federated-repo.prod.meissa.de/api/v1/nodeinfohttps://federated-repo.prod.meissa.de/api/v1/nodeinfohttps://federated-repo.prod.meissa.de/api/v1/nodeinfo","rel":"http://nodeinfo.diaspora.software/ns/schema/2.1"}]}`),
// wantErr: fmt.Errorf("cannot parse JSON: cannot parse empty string; unparsed tail: \"\""),
// },
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
got, err := NodeInfoWellKnownUnmarshalJSON(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 Test_NodeInfoWellKnownValidate(t *testing.T) {
sut := NodeInfoWellKnown{Href: "https://federated-repo.prod.meissa.de/api/v1/nodeinfo"}
if b, err := validation.IsValid(sut); !b {
t.Errorf("sut should be valid, %v, %v", sut, err)
}
sut = NodeInfoWellKnown{Href: "./federated-repo.prod.meissa.de/api/v1/nodeinfo"}
if _, err := validation.IsValid(sut); err.Error() != "Href has to be absolute\nValue is not contained in allowed values [[http https]]" {
t.Errorf("validation error expected but was: %v\n", err)
}
sut = NodeInfoWellKnown{Href: "https://federated-repo.prod.meissa.de/api/v1/nodeinfo?alert=1"}
if _, err := validation.IsValid(sut); err.Error() != "Href may not contain query" {
t.Errorf("sut should be valid, %v, %v", sut, err)
}
}

View file

@ -8,24 +8,10 @@ import (
"github.com/valyala/fastjson" "github.com/valyala/fastjson"
) )
type (
SourceType string
)
type SourceTypes []SourceType
const ( const (
StarType ap.ActivityVocabularyType = "Star" StarType ap.ActivityVocabularyType = "Star"
) )
const (
ForgejoSourceType SourceType = "frogejo"
)
var KnownSourceTypes = SourceTypes{
ForgejoSourceType,
}
// Star activity data type // Star activity data type
// swagger:model // swagger:model
type Star struct { type Star struct {

View file

@ -90,6 +90,10 @@ func RepositoryInbox(ctx *context.APIContext) {
log.Info("RepositoryInbox: activity:%v", activity) log.Info("RepositoryInbox: activity:%v", activity)
// parse actorID (person) // parse actorID (person)
// rawActorID, err := forgefed.NewActorID(activity.Actor.GetID().String())
// nodeInfo, err := createNodeInfo(rawActorID)
actorID, err := forgefed.NewPersonID(activity.Actor.GetID().String(), string(activity.Source)) actorID, err := forgefed.NewPersonID(activity.Actor.GetID().String(), string(activity.Source))
if err != nil { if err != nil {
ctx.ServerError("Validate actorId", err) ctx.ServerError("Validate actorId", err)