From 3172eb69d2e5e1596be675e72b42deab2e7763b3 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Sat, 9 Dec 2023 18:30:47 +0100 Subject: [PATCH] introduce RepositoryId --- models/forgefed/actor.go | 155 +++++++++++++++++++++++----------- models/forgefed/actor_test.go | 120 ++++++++++++++------------ 2 files changed, 174 insertions(+), 101 deletions(-) diff --git a/models/forgefed/actor.go b/models/forgefed/actor.go index 8599bbfd5e..dbd53e3f95 100644 --- a/models/forgefed/actor.go +++ b/models/forgefed/actor.go @@ -8,17 +8,26 @@ import ( "net/url" "strings" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/validation" ) +type ActorId struct { + Id string + Source string + Schema string + Path string + Host string + Port string + UnvalidatedInput string +} + type PersonId struct { - userId string - source string - schema string - path string - host string - port string - unvalidatedInput string + ActorId +} + +type RepositoryId struct { + ActorId } func NewPersonId(uri string, source string) (PersonId, error) { @@ -27,44 +36,71 @@ func NewPersonId(uri string, source string) (PersonId, error) { } validatedUri, _ := url.Parse(uri) - pathWithUserID := strings.Split(validatedUri.Path, "/") - - if containsEmptyString(pathWithUserID) { - pathWithUserID = removeEmptyStrings(pathWithUserID) + 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] - length := len(pathWithUserID) - pathWithoutUserID := strings.Join(pathWithUserID[0:length-1], "/") - userId := pathWithUserID[length-1] - - actorId := PersonId{ - userId: userId, - source: source, - schema: validatedUri.Scheme, - host: validatedUri.Hostname(), - path: pathWithoutUserID, - port: validatedUri.Port(), - unvalidatedInput: uri, - } - if valid, err := actorId.IsValid(); !valid { + result := PersonId{} + result.Id = id + result.Source = source + result.Schema = validatedUri.Scheme + result.Host = validatedUri.Hostname() + result.Path = pathWithoutActorID + result.Port = validatedUri.Port() + result.UnvalidatedInput = uri + if valid, err := result.IsValid(); !valid { return PersonId{}, err } - return actorId, nil + return result, nil } -func (id PersonId) AsUri() string { +// TODO: tbd how an which parts can be generalized +func NewRepositoryId(uri string, 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, _ := url.Parse(uri) + 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 := RepositoryId{} + result.Id = id + result.Source = source + result.Schema = validatedUri.Scheme + result.Host = validatedUri.Hostname() + result.Path = pathWithoutActorID + result.Port = validatedUri.Port() + result.UnvalidatedInput = uri + if valid, err := result.IsValid(); !valid { + return RepositoryId{}, err + } + + return result, nil +} + +func (id ActorId) AsUri() string { result := "" - if id.port == "" { - result = fmt.Sprintf("%s://%s/%s/%s", id.schema, id.host, id.path, id.userId) + if id.Port == "" { + result = fmt.Sprintf("%s://%s/%s/%s", id.Schema, id.Host, id.Path, id.Id) } else { - result = fmt.Sprintf("%s://%s:%s/%s/%s", id.schema, id.host, id.port, id.path, id.userId) + result = fmt.Sprintf("%s://%s:%s/%s/%s", id.Schema, id.Host, id.Port, id.Path, id.Id) } return result } -func (id PersonId) AsWebfinger() string { - result := fmt.Sprintf("@%s@%s", strings.ToLower(id.userId), strings.ToLower(id.host)) +func (id ActorId) AsWebfinger() string { + result := fmt.Sprintf("@%s@%s", strings.ToLower(id.Id), strings.ToLower(id.Host)) return result } @@ -73,22 +109,45 @@ Validate collects error strings in a slice and returns this */ func (value PersonId) Validate() []string { var result = []string{} - result = append(result, validation.ValidateNotEmpty(value.userId, "userId")...) - result = append(result, validation.ValidateNotEmpty(value.source, "source")...) - result = append(result, validation.ValidateNotEmpty(value.schema, "schema")...) - result = append(result, validation.ValidateNotEmpty(value.path, "path")...) - result = append(result, validation.ValidateNotEmpty(value.host, "host")...) - result = append(result, validation.ValidateNotEmpty(value.unvalidatedInput, "unvalidatedInput")...) + result = append(result, validation.ValidateNotEmpty(value.Id, "userId")...) + result = append(result, validation.ValidateNotEmpty(value.Source, "source")...) + result = append(result, validation.ValidateNotEmpty(value.Schema, "schema")...) + result = append(result, validation.ValidateNotEmpty(value.Path, "path")...) + result = append(result, validation.ValidateNotEmpty(value.Host, "host")...) + result = append(result, validation.ValidateNotEmpty(value.UnvalidatedInput, "unvalidatedInput")...) - result = append(result, validation.ValidateOneOf(value.source, []string{"forgejo", "gitea"})...) - switch value.source { + result = append(result, validation.ValidateOneOf(value.Source, []string{"forgejo", "gitea"})...) + switch value.Source { case "forgejo", "gitea": - if strings.ToLower(value.path) != "api/v1/activitypub/user-id" { - result = append(result, fmt.Sprintf("path has to be a api path")) + if strings.ToLower(value.Path) != "api/v1/activitypub/user-id" && strings.ToLower(value.Path) != "api/activitypub/user-id" { + result = append(result, fmt.Sprintf("path: %q has to be a api path", value.Path)) } } - if value.unvalidatedInput != value.AsUri() { - result = append(result, fmt.Sprintf("not all input: %q was parsed: %q", value.unvalidatedInput, value.AsUri())) + if value.UnvalidatedInput != value.AsUri() { + result = append(result, fmt.Sprintf("not all input: %q was parsed: %q", value.UnvalidatedInput, value.AsUri())) + } + + return result +} + +func (value RepositoryId) Validate() []string { + var result = []string{} + result = append(result, validation.ValidateNotEmpty(value.Id, "userId")...) + result = append(result, validation.ValidateNotEmpty(value.Source, "source")...) + result = append(result, validation.ValidateNotEmpty(value.Schema, "schema")...) + result = append(result, validation.ValidateNotEmpty(value.Path, "path")...) + result = append(result, validation.ValidateNotEmpty(value.Host, "host")...) + result = append(result, validation.ValidateNotEmpty(value.UnvalidatedInput, "unvalidatedInput")...) + + result = append(result, validation.ValidateOneOf(value.Source, []string{"forgejo", "gitea"})...) + switch value.Source { + case "forgejo", "gitea": + if strings.ToLower(value.Path) != "api/v1/activitypub/repository-id" && strings.ToLower(value.Path) != "api/activitypub/repository-id" { + result = append(result, fmt.Sprintf("path: %q has to be a api path", value.Path)) + } + } + if value.UnvalidatedInput != value.AsUri() { + result = append(result, fmt.Sprintf("not all input: %q was parsed: %q", value.UnvalidatedInput, value.AsUri())) } return result @@ -106,10 +165,12 @@ func (a PersonId) IsValid() (bool, error) { return true, nil } -func (a PersonId) PanicIfInvalid() { - if valid, err := a.IsValid(); !valid { - panic(err) +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 containsEmptyString(ar []string) bool { diff --git a/models/forgefed/actor_test.go b/models/forgefed/actor_test.go index 897580e0a0..3ce80a62c6 100644 --- a/models/forgefed/actor_test.go +++ b/models/forgefed/actor_test.go @@ -5,86 +5,98 @@ package forgefed import ( "testing" + + "code.gitea.io/gitea/modules/setting" ) func TestNewPersonId(t *testing.T) { - expected := PersonId{ - userId: "1", - source: "forgejo", - schema: "https", - path: "api/v1/activitypub/user-id", - host: "an.other.host", - port: "", - unvalidatedInput: "https://an.other.host/api/v1/activitypub/user-id/1", - } + expected := PersonId{} + expected.Id = "1" + expected.Source = "forgejo" + expected.Schema = "https" + expected.Path = "api/v1/activitypub/user-id" + expected.Host = "an.other.host" + expected.Port = "" + expected.UnvalidatedInput = "https://an.other.host/api/v1/activitypub/user-id/1" sut, _ := NewPersonId("https://an.other.host/api/v1/activitypub/user-id/1", "forgejo") if sut != expected { t.Errorf("expected: %v\n but was: %v\n", expected, sut) } - expected = PersonId{ - userId: "1", - source: "forgejo", - schema: "https", - path: "api/v1/activitypub/user-id", - host: "an.other.host", - port: "443", - unvalidatedInput: "https://an.other.host:443/api/v1/activitypub/user-id/1", - } + expected = PersonId{} + expected.Id = "1" + expected.Source = "forgejo" + expected.Schema = "https" + expected.Path = "api/v1/activitypub/user-id" + expected.Host = "an.other.host" + expected.Port = "443" + expected.UnvalidatedInput = "https://an.other.host:443/api/v1/activitypub/user-id/1" sut, _ = NewPersonId("https://an.other.host:443/api/v1/activitypub/user-id/1", "forgejo") if sut != expected { t.Errorf("expected: %v\n but was: %v\n", expected, sut) } } -func TestPersonIdValidation(t *testing.T) { - sut := PersonId{ - source: "forgejo", - schema: "https", - path: "api/v1/activitypub/user-id", - host: "an.other.host", - port: "", - unvalidatedInput: "https://an.other.host/api/v1/activitypub/user-id/", +func TestNewRepositoryId(t *testing.T) { + setting.AppURL = "http://localhost:3000/" + expected := RepositoryId{} + expected.Id = "1" + expected.Source = "forgejo" + expected.Schema = "http" + expected.Path = "api/activitypub/repository-id" + expected.Host = "localhost" + expected.Port = "3000" + expected.UnvalidatedInput = "http://localhost:3000/api/activitypub/repository-id/1" + sut, _ := NewRepositoryId("http://localhost:3000/api/activitypub/repository-id/1", "forgejo") + if sut != expected { + t.Errorf("expected: %v\n but was: %v\n", expected, sut) } +} + +func TestPersonIdValidation(t *testing.T) { + sut := PersonId{} + sut.Source = "forgejo" + 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/" if sut.Validate()[0] != "Field userId may not be empty" { t.Errorf("validation error expected but was: %v\n", sut.Validate()) } - sut = PersonId{ - userId: "1", - source: "forgejox", - schema: "https", - path: "api/v1/activitypub/user-id", - host: "an.other.host", - port: "", - unvalidatedInput: "https://an.other.host/api/v1/activitypub/user-id/1", - } + 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()) } - sut = PersonId{ - userId: "1", - source: "forgejo", - schema: "https", - path: "api/v1/activitypub/user-idx", - host: "an.other.host", - port: "", - unvalidatedInput: "https://an.other.host/api/v1/activitypub/user-id/1", - } - if sut.Validate()[0] != "path has to be a api path" { + sut = PersonId{} + sut.Id = "1" + sut.Source = "forgejo" + sut.Schema = "https" + sut.Path = "path" + sut.Host = "an.other.host" + sut.Port = "" + sut.UnvalidatedInput = "https://an.other.host/path/1" + if sut.Validate()[0] != "path: \"path\" has to be a api path" { t.Errorf("validation error expected but was: %v\n", sut.Validate()) } - sut = PersonId{ - userId: "1", - source: "forgejo", - schema: "https", - path: "api/v1/activitypub/user-id", - host: "an.other.host", - port: "", - unvalidatedInput: "https://an.other.host/api/v1/activitypub/user-id/1?illegal=action", - } + sut = PersonId{} + sut.Id = "1" + sut.Source = "forgejo" + 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?illegal=action" if sut.Validate()[0] != "not all input: \"https://an.other.host/api/v1/activitypub/user-id/1?illegal=action\" was parsed: \"https://an.other.host/api/v1/activitypub/user-id/1\"" { t.Errorf("validation error expected but was: %v\n", sut.Validate()) }