mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-24 08:57:03 -05:00
Start NodeInfo implementation
This commit is contained in:
parent
3c2493902d
commit
310d740cee
5 changed files with 167 additions and 48 deletions
|
@ -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
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
78
models/forgefed/nodeinfo.go
Normal file
78
models/forgefed/nodeinfo.go
Normal 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
|
||||||
|
}
|
67
models/forgefed/nodeinfo_test.go
Normal file
67
models/forgefed/nodeinfo_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue