introduce RepositoryId

This commit is contained in:
Michael Jerger 2023-12-09 18:30:47 +01:00
parent 1fe35e14a5
commit 3172eb69d2
2 changed files with 174 additions and 101 deletions

View file

@ -8,17 +8,26 @@ import (
"net/url" "net/url"
"strings" "strings"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/validation" "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 { type PersonId struct {
userId string ActorId
source string }
schema string
path string type RepositoryId struct {
host string ActorId
port string
unvalidatedInput string
} }
func NewPersonId(uri string, source string) (PersonId, error) { func NewPersonId(uri string, source string) (PersonId, error) {
@ -27,44 +36,71 @@ func NewPersonId(uri string, source string) (PersonId, error) {
} }
validatedUri, _ := url.Parse(uri) validatedUri, _ := url.Parse(uri)
pathWithUserID := strings.Split(validatedUri.Path, "/") pathWithActorID := strings.Split(validatedUri.Path, "/")
if containsEmptyString(pathWithActorID) {
if containsEmptyString(pathWithUserID) { pathWithActorID = removeEmptyStrings(pathWithActorID)
pathWithUserID = removeEmptyStrings(pathWithUserID)
} }
length := len(pathWithActorID)
pathWithoutActorID := strings.Join(pathWithActorID[0:length-1], "/")
id := pathWithActorID[length-1]
length := len(pathWithUserID) result := PersonId{}
pathWithoutUserID := strings.Join(pathWithUserID[0:length-1], "/") result.Id = id
userId := pathWithUserID[length-1] result.Source = source
result.Schema = validatedUri.Scheme
actorId := PersonId{ result.Host = validatedUri.Hostname()
userId: userId, result.Path = pathWithoutActorID
source: source, result.Port = validatedUri.Port()
schema: validatedUri.Scheme, result.UnvalidatedInput = uri
host: validatedUri.Hostname(), if valid, err := result.IsValid(); !valid {
path: pathWithoutUserID,
port: validatedUri.Port(),
unvalidatedInput: uri,
}
if valid, err := actorId.IsValid(); !valid {
return PersonId{}, err 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 := "" result := ""
if id.port == "" { if id.Port == "" {
result = fmt.Sprintf("%s://%s/%s/%s", id.schema, id.host, id.path, id.userId) result = fmt.Sprintf("%s://%s/%s/%s", id.Schema, id.Host, id.Path, id.Id)
} else { } 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 return result
} }
func (id PersonId) AsWebfinger() string { func (id ActorId) AsWebfinger() string {
result := fmt.Sprintf("@%s@%s", strings.ToLower(id.userId), strings.ToLower(id.host)) result := fmt.Sprintf("@%s@%s", strings.ToLower(id.Id), strings.ToLower(id.Host))
return result return result
} }
@ -73,22 +109,45 @@ Validate collects error strings in a slice and returns this
*/ */
func (value PersonId) Validate() []string { func (value PersonId) Validate() []string {
var result = []string{} var result = []string{}
result = append(result, validation.ValidateNotEmpty(value.userId, "userId")...) result = append(result, validation.ValidateNotEmpty(value.Id, "userId")...)
result = append(result, validation.ValidateNotEmpty(value.source, "source")...) result = append(result, validation.ValidateNotEmpty(value.Source, "source")...)
result = append(result, validation.ValidateNotEmpty(value.schema, "schema")...) result = append(result, validation.ValidateNotEmpty(value.Schema, "schema")...)
result = append(result, validation.ValidateNotEmpty(value.path, "path")...) result = append(result, validation.ValidateNotEmpty(value.Path, "path")...)
result = append(result, validation.ValidateNotEmpty(value.host, "host")...) result = append(result, validation.ValidateNotEmpty(value.Host, "host")...)
result = append(result, validation.ValidateNotEmpty(value.unvalidatedInput, "unvalidatedInput")...) result = append(result, validation.ValidateNotEmpty(value.UnvalidatedInput, "unvalidatedInput")...)
result = append(result, validation.ValidateOneOf(value.source, []string{"forgejo", "gitea"})...) result = append(result, validation.ValidateOneOf(value.Source, []string{"forgejo", "gitea"})...)
switch value.source { switch value.Source {
case "forgejo", "gitea": case "forgejo", "gitea":
if strings.ToLower(value.path) != "api/v1/activitypub/user-id" { if strings.ToLower(value.Path) != "api/v1/activitypub/user-id" && strings.ToLower(value.Path) != "api/activitypub/user-id" {
result = append(result, fmt.Sprintf("path has to be a api path")) result = append(result, fmt.Sprintf("path: %q has to be a api path", value.Path))
} }
} }
if 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())) 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 return result
@ -106,10 +165,12 @@ func (a PersonId) IsValid() (bool, error) {
return true, nil return true, nil
} }
func (a PersonId) PanicIfInvalid() { func (a RepositoryId) IsValid() (bool, error) {
if valid, err := a.IsValid(); !valid { if err := a.Validate(); len(err) > 0 {
panic(err) errString := strings.Join(err, "\n")
return false, fmt.Errorf(errString)
} }
return true, nil
} }
func containsEmptyString(ar []string) bool { func containsEmptyString(ar []string) bool {

View file

@ -5,86 +5,98 @@ package forgefed
import ( import (
"testing" "testing"
"code.gitea.io/gitea/modules/setting"
) )
func TestNewPersonId(t *testing.T) { func TestNewPersonId(t *testing.T) {
expected := PersonId{ expected := PersonId{}
userId: "1", expected.Id = "1"
source: "forgejo", expected.Source = "forgejo"
schema: "https", expected.Schema = "https"
path: "api/v1/activitypub/user-id", expected.Path = "api/v1/activitypub/user-id"
host: "an.other.host", expected.Host = "an.other.host"
port: "", expected.Port = ""
unvalidatedInput: "https://an.other.host/api/v1/activitypub/user-id/1", 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") sut, _ := NewPersonId("https://an.other.host/api/v1/activitypub/user-id/1", "forgejo")
if sut != expected { if sut != expected {
t.Errorf("expected: %v\n but was: %v\n", expected, sut) t.Errorf("expected: %v\n but was: %v\n", expected, sut)
} }
expected = PersonId{ expected = PersonId{}
userId: "1", expected.Id = "1"
source: "forgejo", expected.Source = "forgejo"
schema: "https", expected.Schema = "https"
path: "api/v1/activitypub/user-id", expected.Path = "api/v1/activitypub/user-id"
host: "an.other.host", expected.Host = "an.other.host"
port: "443", expected.Port = "443"
unvalidatedInput: "https://an.other.host:443/api/v1/activitypub/user-id/1", 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") sut, _ = NewPersonId("https://an.other.host:443/api/v1/activitypub/user-id/1", "forgejo")
if sut != expected { if sut != expected {
t.Errorf("expected: %v\n but was: %v\n", expected, sut) t.Errorf("expected: %v\n but was: %v\n", expected, sut)
} }
} }
func TestPersonIdValidation(t *testing.T) { func TestNewRepositoryId(t *testing.T) {
sut := PersonId{ setting.AppURL = "http://localhost:3000/"
source: "forgejo", expected := RepositoryId{}
schema: "https", expected.Id = "1"
path: "api/v1/activitypub/user-id", expected.Source = "forgejo"
host: "an.other.host", expected.Schema = "http"
port: "", expected.Path = "api/activitypub/repository-id"
unvalidatedInput: "https://an.other.host/api/v1/activitypub/user-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" { if sut.Validate()[0] != "Field userId may not be empty" {
t.Errorf("validation error expected but was: %v\n", sut.Validate()) t.Errorf("validation error expected but was: %v\n", sut.Validate())
} }
sut = PersonId{ sut = PersonId{}
userId: "1", sut.Id = "1"
source: "forgejox", sut.Source = "forgejox"
schema: "https", sut.Schema = "https"
path: "api/v1/activitypub/user-id", sut.Path = "api/v1/activitypub/user-id"
host: "an.other.host", sut.Host = "an.other.host"
port: "", sut.Port = ""
unvalidatedInput: "https://an.other.host/api/v1/activitypub/user-id/1", 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]]" { 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()) t.Errorf("validation error expected but was: %v\n", sut.Validate())
} }
sut = PersonId{ sut = PersonId{}
userId: "1", sut.Id = "1"
source: "forgejo", sut.Source = "forgejo"
schema: "https", sut.Schema = "https"
path: "api/v1/activitypub/user-idx", sut.Path = "path"
host: "an.other.host", sut.Host = "an.other.host"
port: "", sut.Port = ""
unvalidatedInput: "https://an.other.host/api/v1/activitypub/user-id/1", sut.UnvalidatedInput = "https://an.other.host/path/1"
} if sut.Validate()[0] != "path: \"path\" has to be a api path" {
if sut.Validate()[0] != "path has to be a api path" {
t.Errorf("validation error expected but was: %v\n", sut.Validate()) t.Errorf("validation error expected but was: %v\n", sut.Validate())
} }
sut = PersonId{ sut = PersonId{}
userId: "1", sut.Id = "1"
source: "forgejo", sut.Source = "forgejo"
schema: "https", sut.Schema = "https"
path: "api/v1/activitypub/user-id", sut.Path = "api/v1/activitypub/user-id"
host: "an.other.host", sut.Host = "an.other.host"
port: "", sut.Port = ""
unvalidatedInput: "https://an.other.host/api/v1/activitypub/user-id/1?illegal=action", 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\"" { 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()) t.Errorf("validation error expected but was: %v\n", sut.Validate())
} }