mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-22 13:45:19 +00:00
introduce nodeinfo
This commit is contained in:
parent
587bd07372
commit
8116214727
|
@ -12,11 +12,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/validation"
|
"code.gitea.io/gitea/modules/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Validateables interface {
|
// ----------------------------- ActorID --------------------------------------------
|
||||||
validation.Validateable
|
|
||||||
ActorID | PersonID | RepositoryID
|
|
||||||
}
|
|
||||||
|
|
||||||
type ActorID struct {
|
type ActorID struct {
|
||||||
ID string
|
ID string
|
||||||
Source string
|
Source string
|
||||||
|
@ -27,31 +23,12 @@ type ActorID struct {
|
||||||
UnvalidatedInput string
|
UnvalidatedInput string
|
||||||
}
|
}
|
||||||
|
|
||||||
type PersonID struct {
|
// Factory function for ActorID. Created struct is asserted to be valid
|
||||||
ActorID
|
func NewActorID(uri string) (ActorID, error) {
|
||||||
}
|
result, err := newActorID(uri)
|
||||||
|
if err != nil {
|
||||||
type RepositoryID struct {
|
return ActorID{}, err
|
||||||
ActorID
|
|
||||||
}
|
|
||||||
|
|
||||||
// newActorID receives already validated inputs
|
|
||||||
func NewActorID(validatedURI *url.URL) (ActorID, error) {
|
|
||||||
pathWithActorID := strings.Split(validatedURI.Path, "/")
|
|
||||||
if containsEmptyString(pathWithActorID) {
|
|
||||||
pathWithActorID = removeEmptyStrings(pathWithActorID)
|
|
||||||
}
|
}
|
||||||
length := len(pathWithActorID)
|
|
||||||
pathWithoutActorID := strings.Join(pathWithActorID[0:length-1], "/")
|
|
||||||
id := pathWithActorID[length-1]
|
|
||||||
|
|
||||||
result := ActorID{}
|
|
||||||
result.ID = id
|
|
||||||
result.Schema = validatedURI.Scheme
|
|
||||||
result.Host = validatedURI.Hostname()
|
|
||||||
result.Path = pathWithoutActorID
|
|
||||||
result.Port = validatedURI.Port()
|
|
||||||
result.UnvalidatedInput = validatedURI.String()
|
|
||||||
|
|
||||||
if valid, outcome := validation.IsValid(result); !valid {
|
if valid, outcome := validation.IsValid(result); !valid {
|
||||||
return ActorID{}, outcome
|
return ActorID{}, outcome
|
||||||
|
@ -60,65 +37,6 @@ func NewActorID(validatedURI *url.URL) (ActorID, error) {
|
||||||
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) {
|
|
||||||
// TODO: remove after test
|
|
||||||
//if !validation.IsValidExternalURL(uri) {
|
|
||||||
// return PersonId{}, fmt.Errorf("uri %s is not a valid external url", uri)
|
|
||||||
//}
|
|
||||||
validatedURI, err := url.ParseRequestURI(uri)
|
|
||||||
if err != nil {
|
|
||||||
return PersonID{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
actorID, err := newActorID(validatedURI, source)
|
|
||||||
if err != nil {
|
|
||||||
return PersonID{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate Person specific path
|
|
||||||
personID := PersonID{actorID}
|
|
||||||
if valid, outcome := validation.IsValid(personID); !valid {
|
|
||||||
return PersonID{}, outcome
|
|
||||||
}
|
|
||||||
|
|
||||||
return personID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRepositoryID(uri, source string) (RepositoryID, error) {
|
|
||||||
if !validation.IsAPIURL(uri) {
|
|
||||||
return RepositoryID{}, fmt.Errorf("uri %s is not a valid repo url on this host %s", uri, setting.AppURL+"api")
|
|
||||||
}
|
|
||||||
|
|
||||||
validatedURI, err := url.ParseRequestURI(uri)
|
|
||||||
if err != nil {
|
|
||||||
return RepositoryID{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
actorID, err := newActorID(validatedURI, source)
|
|
||||||
if err != nil {
|
|
||||||
return RepositoryID{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate Person specific path
|
|
||||||
repoID := RepositoryID{actorID}
|
|
||||||
if valid, outcome := validation.IsValid(repoID); !valid {
|
|
||||||
return RepositoryID{}, outcome
|
|
||||||
}
|
|
||||||
|
|
||||||
return repoID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (id ActorID) AsURI() string {
|
func (id ActorID) AsURI() string {
|
||||||
var result string
|
var result string
|
||||||
if id.Port == "" {
|
if id.Port == "" {
|
||||||
|
@ -129,6 +47,47 @@ func (id ActorID) AsURI() string {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (id ActorID) Validate() []string {
|
||||||
|
var result []string
|
||||||
|
result = append(result, validation.ValidateNotEmpty(id.ID, "userId")...)
|
||||||
|
result = append(result, validation.ValidateNotEmpty(id.Schema, "schema")...)
|
||||||
|
result = append(result, validation.ValidateNotEmpty(id.Path, "path")...)
|
||||||
|
result = append(result, validation.ValidateNotEmpty(id.Host, "host")...)
|
||||||
|
result = append(result, validation.ValidateNotEmpty(id.UnvalidatedInput, "unvalidatedInput")...)
|
||||||
|
|
||||||
|
if id.UnvalidatedInput != id.AsURI() {
|
||||||
|
result = append(result, fmt.Sprintf("not all input: %q was parsed: %q", id.UnvalidatedInput, id.AsURI()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------- PersonID --------------------------------------------
|
||||||
|
type PersonID struct {
|
||||||
|
ActorID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Factory function for PersonID. Created struct is asserted to be valid
|
||||||
|
func NewPersonID(uri, source string) (PersonID, error) {
|
||||||
|
// TODO: remove after test
|
||||||
|
//if !validation.IsValidExternalURL(uri) {
|
||||||
|
// return PersonId{}, fmt.Errorf("uri %s is not a valid external url", uri)
|
||||||
|
//}
|
||||||
|
result, err := newActorID(uri)
|
||||||
|
if err != nil {
|
||||||
|
return PersonID{}, err
|
||||||
|
}
|
||||||
|
result.Source = source
|
||||||
|
|
||||||
|
// validate Person specific path
|
||||||
|
personID := PersonID{result}
|
||||||
|
if valid, outcome := validation.IsValid(personID); !valid {
|
||||||
|
return PersonID{}, outcome
|
||||||
|
}
|
||||||
|
|
||||||
|
return personID, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (id PersonID) AsWebfinger() string {
|
func (id PersonID) AsWebfinger() string {
|
||||||
result := fmt.Sprintf("@%s@%s", strings.ToLower(id.ID), strings.ToLower(id.Host))
|
result := fmt.Sprintf("@%s@%s", strings.ToLower(id.ID), strings.ToLower(id.Host))
|
||||||
return result
|
return result
|
||||||
|
@ -144,26 +103,10 @@ func (id PersonID) HostSuffix() string {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate collects error strings in a slice and returns this
|
|
||||||
func (id ActorID) Validate() []string {
|
|
||||||
var result []string
|
|
||||||
result = append(result, validation.ValidateNotEmpty(id.ID, "userId")...)
|
|
||||||
result = append(result, validation.ValidateNotEmpty(id.Schema, "schema")...)
|
|
||||||
result = append(result, validation.ValidateNotEmpty(id.Path, "path")...)
|
|
||||||
result = append(result, validation.ValidateNotEmpty(id.Host, "host")...)
|
|
||||||
result = append(result, validation.ValidateNotEmpty(id.UnvalidatedInput, "unvalidatedInput")...)
|
|
||||||
|
|
||||||
if id.UnvalidatedInput != id.AsURI() {
|
|
||||||
result = append(result, fmt.Sprintf("not all input: %q was parsed: %q", id.UnvalidatedInput, id.AsURI()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
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.ValidateNotEmpty(id.Source, "source")...)
|
||||||
result = append(result, validation.ValidateOneOf(id.Source, []string{"forgejo", "gitea"})...)
|
result = append(result, validation.ValidateOneOf(id.Source, []any{"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" {
|
||||||
|
@ -173,10 +116,36 @@ func (id PersonID) Validate() []string {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------- RepositoryID --------------------------------------------
|
||||||
|
|
||||||
|
type RepositoryID struct {
|
||||||
|
ActorID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Factory function for RepositoryID. Created struct is asserted to be valid.
|
||||||
|
func NewRepositoryID(uri, source string) (RepositoryID, error) {
|
||||||
|
if !validation.IsAPIURL(uri) {
|
||||||
|
return RepositoryID{}, fmt.Errorf("uri %s is not a valid repo url on this host %s", uri, setting.AppURL+"api")
|
||||||
|
}
|
||||||
|
result, err := newActorID(uri)
|
||||||
|
if err != nil {
|
||||||
|
return RepositoryID{}, err
|
||||||
|
}
|
||||||
|
result.Source = source
|
||||||
|
|
||||||
|
// validate Person specific path
|
||||||
|
repoID := RepositoryID{result}
|
||||||
|
if valid, outcome := validation.IsValid(repoID); !valid {
|
||||||
|
return RepositoryID{}, outcome
|
||||||
|
}
|
||||||
|
|
||||||
|
return repoID, nil
|
||||||
|
}
|
||||||
|
|
||||||
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.ValidateNotEmpty(id.Source, "source")...)
|
||||||
result = append(result, validation.ValidateOneOf(id.Source, []string{"forgejo", "gitea"})...)
|
result = append(result, validation.ValidateOneOf(id.Source, []any{"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" {
|
||||||
|
@ -204,3 +173,26 @@ func removeEmptyStrings(ls []string) []string {
|
||||||
}
|
}
|
||||||
return rs
|
return rs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newActorID(uri string) (ActorID, error) {
|
||||||
|
validatedURI, err := url.ParseRequestURI(uri)
|
||||||
|
if err != nil {
|
||||||
|
return ActorID{}, err
|
||||||
|
}
|
||||||
|
pathWithActorID := strings.Split(validatedURI.Path, "/")
|
||||||
|
if containsEmptyString(pathWithActorID) {
|
||||||
|
pathWithActorID = removeEmptyStrings(pathWithActorID)
|
||||||
|
}
|
||||||
|
length := len(pathWithActorID)
|
||||||
|
pathWithoutActorID := strings.Join(pathWithActorID[0:length-1], "/")
|
||||||
|
id := pathWithActorID[length-1]
|
||||||
|
|
||||||
|
result := ActorID{}
|
||||||
|
result.ID = id
|
||||||
|
result.Schema = validatedURI.Scheme
|
||||||
|
result.Host = validatedURI.Hostname()
|
||||||
|
result.Path = pathWithoutActorID
|
||||||
|
result.Port = validatedURI.Port()
|
||||||
|
result.UnvalidatedInput = validatedURI.String()
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
|
@ -65,18 +65,6 @@ func TestActorIdValidation(t *testing.T) {
|
||||||
t.Errorf("validation error expected but was: %v\n", sut.Validate())
|
t.Errorf("validation error expected but was: %v\n", sut.Validate())
|
||||||
}
|
}
|
||||||
|
|
||||||
sut = ActorID{}
|
|
||||||
sut.ID = "1"
|
|
||||||
sut.Source = "forgejox"
|
|
||||||
sut.Schema = "https"
|
|
||||||
sut.Path = "api/v1/activitypub/user-id"
|
|
||||||
sut.Host = "an.other.host"
|
|
||||||
sut.Port = ""
|
|
||||||
sut.UnvalidatedInput = "https://an.other.host/api/v1/activitypub/user-id/1"
|
|
||||||
if sut.Validate()[0] != "Value forgejox is not contained in allowed values [[forgejo gitea]]" {
|
|
||||||
t.Errorf("validation error expected but was: %v\n", sut.Validate())
|
|
||||||
}
|
|
||||||
|
|
||||||
sut = ActorID{}
|
sut = ActorID{}
|
||||||
sut.ID = "1"
|
sut.ID = "1"
|
||||||
sut.Source = "forgejo"
|
sut.Source = "forgejo"
|
||||||
|
@ -102,6 +90,18 @@ func TestPersonIdValidation(t *testing.T) {
|
||||||
if _, err := IsValid(sut); err.Error() != "path: \"path\" has to be a person specific api path" {
|
if _, err := IsValid(sut); err.Error() != "path: \"path\" has to be a person specific api path" {
|
||||||
t.Errorf("validation error expected but was: %v\n", err)
|
t.Errorf("validation error expected but was: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sut = PersonID{}
|
||||||
|
sut.ID = "1"
|
||||||
|
sut.Source = "forgejox"
|
||||||
|
sut.Schema = "https"
|
||||||
|
sut.Path = "api/v1/activitypub/user-id"
|
||||||
|
sut.Host = "an.other.host"
|
||||||
|
sut.Port = ""
|
||||||
|
sut.UnvalidatedInput = "https://an.other.host/api/v1/activitypub/user-id/1"
|
||||||
|
if sut.Validate()[0] != "Value forgejox is not contained in allowed values [[forgejo gitea]]" {
|
||||||
|
t.Errorf("validation error expected but was: %v\n", sut.Validate())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebfingerId(t *testing.T) {
|
func TestWebfingerId(t *testing.T) {
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
package forgefed
|
package forgefed
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/validation"
|
"code.gitea.io/gitea/modules/validation"
|
||||||
|
@ -14,22 +15,37 @@ type (
|
||||||
SourceType string
|
SourceType string
|
||||||
)
|
)
|
||||||
|
|
||||||
type SourceTypes []SourceType
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ForgejoSourceType SourceType = "frogejo"
|
ForgejoSourceType SourceType = "forgejo"
|
||||||
|
GiteaSourceType SourceType = "gitea"
|
||||||
)
|
)
|
||||||
|
|
||||||
var KnownSourceTypes = SourceTypes{
|
var KnownSourceTypes = []any{
|
||||||
ForgejoSourceType,
|
ForgejoSourceType, GiteaSourceType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------ NodeInfoWellKnown ------------------------------------------------
|
||||||
|
|
||||||
// NodeInfo data type
|
// NodeInfo data type
|
||||||
// swagger:model
|
// swagger:model
|
||||||
type NodeInfoWellKnown struct {
|
type NodeInfoWellKnown struct {
|
||||||
Href string
|
Href string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Factory function for PersonID. Created struct is asserted to be valid
|
||||||
|
func NewNodeInfoWellKnown(body []byte) (NodeInfoWellKnown, error) {
|
||||||
|
result, err := NodeInfoWellKnownUnmarshalJSON(body)
|
||||||
|
if err != nil {
|
||||||
|
return NodeInfoWellKnown{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if valid, err := validation.IsValid(result); !valid {
|
||||||
|
return NodeInfoWellKnown{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
func NodeInfoWellKnownUnmarshalJSON(data []byte) (NodeInfoWellKnown, error) {
|
func NodeInfoWellKnownUnmarshalJSON(data []byte) (NodeInfoWellKnown, error) {
|
||||||
p := fastjson.Parser{}
|
p := fastjson.Parser{}
|
||||||
val, err := p.ParseBytes(data)
|
val, err := p.ParseBytes(data)
|
||||||
|
@ -40,19 +56,6 @@ func NodeInfoWellKnownUnmarshalJSON(data []byte) (NodeInfoWellKnown, error) {
|
||||||
return NodeInfoWellKnown{Href: href}, nil
|
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
|
// Validate collects error strings in a slice and returns this
|
||||||
func (node NodeInfoWellKnown) Validate() []string {
|
func (node NodeInfoWellKnown) Validate() []string {
|
||||||
var result []string
|
var result []string
|
||||||
|
@ -68,7 +71,7 @@ func (node NodeInfoWellKnown) Validate() []string {
|
||||||
result = append(result, "Href has to be absolute")
|
result = append(result, "Href has to be absolute")
|
||||||
}
|
}
|
||||||
|
|
||||||
result = append(result, validation.ValidateOneOf(parsedUrl.Scheme, []string{"http", "https"})...)
|
result = append(result, validation.ValidateOneOf(parsedUrl.Scheme, []any{"http", "https"})...)
|
||||||
|
|
||||||
if parsedUrl.RawQuery != "" {
|
if parsedUrl.RawQuery != "" {
|
||||||
result = append(result, "Href may not contain query")
|
result = append(result, "Href may not contain query")
|
||||||
|
@ -76,3 +79,55 @@ func (node NodeInfoWellKnown) Validate() []string {
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (id ActorID) AsWellKnownNodeInfoUri() string {
|
||||||
|
wellKnownPath := ".well-known/nodeinfo"
|
||||||
|
var result string
|
||||||
|
if id.Port == "" {
|
||||||
|
result = fmt.Sprintf("%s://%s/%s", id.Schema, id.Host, wellKnownPath)
|
||||||
|
} else {
|
||||||
|
result = fmt.Sprintf("%s://%s:%s/%s", id.Schema, id.Host, id.Port, wellKnownPath)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------ NodeInfo ------------------------------------------------
|
||||||
|
|
||||||
|
// NodeInfo data type
|
||||||
|
// swagger:model
|
||||||
|
type NodeInfo struct {
|
||||||
|
Source SourceType
|
||||||
|
}
|
||||||
|
|
||||||
|
func NodeInfoUnmarshalJSON(data []byte) (NodeInfo, error) {
|
||||||
|
p := fastjson.Parser{}
|
||||||
|
val, err := p.ParseBytes(data)
|
||||||
|
if err != nil {
|
||||||
|
return NodeInfo{}, err
|
||||||
|
}
|
||||||
|
source := string(val.GetStringBytes("software", "name"))
|
||||||
|
result := NodeInfo{}
|
||||||
|
result.Source = SourceType(source)
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNodeInfo(body []byte) (NodeInfo, error) {
|
||||||
|
result, err := NodeInfoUnmarshalJSON(body)
|
||||||
|
if err != nil {
|
||||||
|
return NodeInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if valid, err := validation.IsValid(result); !valid {
|
||||||
|
return NodeInfo{}, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate collects error strings in a slice and returns this
|
||||||
|
func (node NodeInfo) Validate() []string {
|
||||||
|
var result []string
|
||||||
|
result = append(result, validation.ValidateNotEmpty(string(node.Source), "source")...)
|
||||||
|
result = append(result, validation.ValidateOneOf(node.Source, KnownSourceTypes)...)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
|
@ -29,10 +29,6 @@ func Test_NodeInfoWellKnownUnmarshalJSON(t *testing.T) {
|
||||||
item: []byte(``),
|
item: []byte(``),
|
||||||
wantErr: fmt.Errorf("cannot parse JSON: cannot parse empty string; unparsed tail: \"\""),
|
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 {
|
for name, tt := range tests {
|
||||||
|
@ -65,3 +61,29 @@ func Test_NodeInfoWellKnownValidate(t *testing.T) {
|
||||||
t.Errorf("sut should be valid, %v, %v", sut, err)
|
t.Errorf("sut should be valid, %v, %v", sut, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_NewNodeInfoWellKnown(t *testing.T) {
|
||||||
|
sut, err := NewNodeInfoWellKnown([]byte(`{"links":[{"href":"https://federated-repo.prod.meissa.de/api/v1/nodeinfo","rel":"http://nodeinfo.diaspora.software/ns/schema/2.1"}]}`))
|
||||||
|
expected := NodeInfoWellKnown{Href: "https://federated-repo.prod.meissa.de/api/v1/nodeinfo"}
|
||||||
|
if sut != expected {
|
||||||
|
t.Errorf("expected was: %v but was: %v", expected, sut)
|
||||||
|
}
|
||||||
|
|
||||||
|
sut, err = NewNodeInfoWellKnown([]byte(`invalid`))
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("error was expected here")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_NewNodeInfo(t *testing.T) {
|
||||||
|
sut, err := NewNodeInfo([]byte(`{"version":"2.1","software":{"name":"gitea","version":"1.20.0+dev-2539-g5840cc6d3","repository":"https://github.com/go-gitea/gitea.git","homepage":"https://gitea.io/"},"protocols":["activitypub"],"services":{"inbound":[],"outbound":["rss2.0"]},"openRegistrations":true,"usage":{"users":{"total":13,"activeHalfyear":1,"activeMonth":1}},"metadata":{}}`))
|
||||||
|
expected := NodeInfo{Source: "gitea"}
|
||||||
|
if sut != expected {
|
||||||
|
t.Errorf("expected was: %v but was: %v", expected, sut)
|
||||||
|
}
|
||||||
|
|
||||||
|
sut, err = NewNodeInfo([]byte(`invalid`))
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("error was expected here")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ func ValidateNotEmpty(value, fieldName string) []string {
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateOneOf(value string, allowed []string) []string {
|
func ValidateOneOf(value any, allowed []any) []string {
|
||||||
for _, allowedElem := range allowed {
|
for _, allowedElem := range allowed {
|
||||||
if value == allowedElem {
|
if value == allowedElem {
|
||||||
return []string{}
|
return []string{}
|
||||||
|
|
|
@ -8,7 +8,6 @@ package activitypub
|
||||||
// Then maybe save the node info in a DB table - this could be useful for validation
|
// Then maybe save the node info in a DB table - this could be useful for validation
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -23,7 +22,6 @@ import (
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/routers/utils"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
ap "github.com/go-ap/activitypub"
|
ap "github.com/go-ap/activitypub"
|
||||||
|
@ -90,11 +88,12 @@ 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())
|
actorUri := activity.Actor.GetID().String()
|
||||||
|
rawActorID, err := forgefed.NewActorID(actorUri)
|
||||||
|
nodeInfo, err := createNodeInfo(ctx, rawActorID)
|
||||||
|
log.Info("RepositoryInbox: nodeInfo validated: %v", nodeInfo)
|
||||||
|
|
||||||
// nodeInfo, err := createNodeInfo(rawActorID)
|
actorID, err := forgefed.NewPersonID(actorUri, 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)
|
||||||
return
|
return
|
||||||
|
@ -184,6 +183,27 @@ func SearchUsersByLoginName(loginName string) ([]*user_model.User, error) {
|
||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createNodeInfo(ctx *context.APIContext, actorID forgefed.ActorID) (forgefed.NodeInfo, error) {
|
||||||
|
actionsUser := user_model.NewActionsUser()
|
||||||
|
client, err := api.NewClient(ctx, actionsUser, "no idea where to get key material.")
|
||||||
|
if err != nil {
|
||||||
|
return forgefed.NodeInfo{}, err
|
||||||
|
}
|
||||||
|
body, err := client.GetBody(actorID.AsWellKnownNodeInfoUri())
|
||||||
|
if err != nil {
|
||||||
|
return forgefed.NodeInfo{}, err
|
||||||
|
}
|
||||||
|
nodeInfoWellKnown, err := forgefed.NewNodeInfoWellKnown(body)
|
||||||
|
if err != nil {
|
||||||
|
return forgefed.NodeInfo{}, err
|
||||||
|
}
|
||||||
|
body, err = client.GetBody(nodeInfoWellKnown.Href)
|
||||||
|
if err != nil {
|
||||||
|
return forgefed.NodeInfo{}, err
|
||||||
|
}
|
||||||
|
return forgefed.NewNodeInfo(body)
|
||||||
|
}
|
||||||
|
|
||||||
// ToDo: Maybe use externalLoginUser
|
// ToDo: Maybe use externalLoginUser
|
||||||
func createUserFromAP(ctx *context.APIContext, personID forgefed.PersonID) (*user_model.User, error) {
|
func createUserFromAP(ctx *context.APIContext, personID forgefed.PersonID) (*user_model.User, error) {
|
||||||
// ToDo: Do we get a publicKeyId from server, repo or owner or repo?
|
// ToDo: Do we get a publicKeyId from server, repo or owner or repo?
|
||||||
|
@ -193,24 +213,10 @@ func createUserFromAP(ctx *context.APIContext, personID forgefed.PersonID) (*use
|
||||||
return &user_model.User{}, err
|
return &user_model.User{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := client.Get(personID.AsURI())
|
body, err := client.GetBody(personID.AsURI())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &user_model.User{}, err
|
return &user_model.User{}, err
|
||||||
}
|
}
|
||||||
log.Info("RepositoryInbox: got status: %v", response.Status)
|
|
||||||
|
|
||||||
// validate response; ToDo: Should we widen the restrictions here?
|
|
||||||
if response.StatusCode != 200 {
|
|
||||||
err = fmt.Errorf("got non 200 status code for id: %v", personID.ID)
|
|
||||||
return &user_model.User{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer response.Body.Close()
|
|
||||||
body, err := io.ReadAll(response.Body)
|
|
||||||
if err != nil {
|
|
||||||
return &user_model.User{}, err
|
|
||||||
}
|
|
||||||
log.Info("RepositoryInbox: got body: %v", utils.CharLimiter(string(body), 120))
|
|
||||||
|
|
||||||
person := ap.Person{}
|
person := ap.Person{}
|
||||||
err = person.UnmarshalJSON(body)
|
err = person.UnmarshalJSON(body)
|
||||||
|
|
Loading…
Reference in a new issue