Merge pull request 'Creation of federated user' (#3792) from meissa/forgejo:forgejo-federated-pr3 into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3792 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
This commit is contained in:
commit
d8e21e673d
|
@ -131,11 +131,13 @@ package "code.gitea.io/gitea/models/user"
|
|||
func (ErrUserInactive).Unwrap
|
||||
func IsErrExternalLoginUserAlreadyExist
|
||||
func IsErrExternalLoginUserNotExist
|
||||
func NewFederatedUser
|
||||
func IsErrUserSettingIsNotExist
|
||||
func GetUserAllSettings
|
||||
func DeleteUserSetting
|
||||
func GetUserEmailsByNames
|
||||
func GetUserNamesByIDs
|
||||
func DeleteFederatedUser
|
||||
|
||||
package "code.gitea.io/gitea/modules/activitypub"
|
||||
func (*Client).Post
|
||||
|
@ -169,16 +171,6 @@ package "code.gitea.io/gitea/modules/eventsource"
|
|||
|
||||
package "code.gitea.io/gitea/modules/forgefed"
|
||||
func NewForgeLike
|
||||
func NewPersonID
|
||||
func (PersonID).AsWebfinger
|
||||
func (PersonID).AsLoginName
|
||||
func (PersonID).HostSuffix
|
||||
func (PersonID).Validate
|
||||
func NewRepositoryID
|
||||
func (RepositoryID).Validate
|
||||
func (ForgePerson).MarshalJSON
|
||||
func (*ForgePerson).UnmarshalJSON
|
||||
func (ForgePerson).Validate
|
||||
func GetItemByType
|
||||
func JSONUnmarshalerFn
|
||||
func NotEmpty
|
||||
|
|
|
@ -68,6 +68,10 @@ var migrations = []*Migration{
|
|||
NewMigration("Remove Gitea-specific columns from the repository and badge tables", RemoveGiteaSpecificColumnsFromRepositoryAndBadge),
|
||||
// v15 -> v16
|
||||
NewMigration("Create the `federation_host` table", CreateFederationHostTable),
|
||||
// v16 -> v17
|
||||
NewMigration("Create the `federated_user` table", CreateFederatedUserTable),
|
||||
// v17 -> v18
|
||||
NewMigration("Add `normalized_federated_uri` column to `user` table", AddNormalizedFederatedURIToUser),
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current Forgejo database version.
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgejo_migrations //nolint:revive
|
||||
|
||||
import "xorm.io/xorm"
|
||||
|
||||
type FederatedUser struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UserID int64 `xorm:"NOT NULL"`
|
||||
ExternalID string `xorm:"UNIQUE(federation_user_mapping) NOT NULL"`
|
||||
FederationHostID int64 `xorm:"UNIQUE(federation_user_mapping) NOT NULL"`
|
||||
}
|
||||
|
||||
func CreateFederatedUserTable(x *xorm.Engine) error {
|
||||
return x.Sync(new(FederatedUser))
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgejo_migrations //nolint:revive
|
||||
|
||||
import "xorm.io/xorm"
|
||||
|
||||
func AddNormalizedFederatedURIToUser(x *xorm.Engine) error {
|
||||
type User struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
NormalizedFederatedURI string
|
||||
}
|
||||
return x.Sync(&User{})
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/validation"
|
||||
)
|
||||
|
||||
type FederatedUser struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UserID int64 `xorm:"NOT NULL"`
|
||||
ExternalID string `xorm:"UNIQUE(federation_user_mapping) NOT NULL"`
|
||||
FederationHostID int64 `xorm:"UNIQUE(federation_user_mapping) NOT NULL"`
|
||||
}
|
||||
|
||||
func NewFederatedUser(userID int64, externalID string, federationHostID int64) (FederatedUser, error) {
|
||||
result := FederatedUser{
|
||||
UserID: userID,
|
||||
ExternalID: externalID,
|
||||
FederationHostID: federationHostID,
|
||||
}
|
||||
if valid, err := validation.IsValid(result); !valid {
|
||||
return FederatedUser{}, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (user FederatedUser) Validate() []string {
|
||||
var result []string
|
||||
result = append(result, validation.ValidateNotEmpty(user.UserID, "UserID")...)
|
||||
result = append(result, validation.ValidateNotEmpty(user.ExternalID, "ExternalID")...)
|
||||
result = append(result, validation.ValidateNotEmpty(user.FederationHostID, "FederationHostID")...)
|
||||
return result
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/validation"
|
||||
)
|
||||
|
||||
func Test_FederatedUserValidation(t *testing.T) {
|
||||
sut := FederatedUser{
|
||||
UserID: 12,
|
||||
ExternalID: "12",
|
||||
FederationHostID: 1,
|
||||
}
|
||||
if res, err := validation.IsValid(sut); !res {
|
||||
t.Errorf("sut should be valid but was %q", err)
|
||||
}
|
||||
|
||||
sut = FederatedUser{
|
||||
ExternalID: "12",
|
||||
FederationHostID: 1,
|
||||
}
|
||||
if res, _ := validation.IsValid(sut); res {
|
||||
t.Errorf("sut should be invalid")
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
@ -131,6 +132,9 @@ type User struct {
|
|||
AvatarEmail string `xorm:"NOT NULL"`
|
||||
UseCustomAvatar bool
|
||||
|
||||
// For federation
|
||||
NormalizedFederatedURI string
|
||||
|
||||
// Counters
|
||||
NumFollowers int
|
||||
NumFollowing int `xorm:"NOT NULL DEFAULT 0"`
|
||||
|
@ -303,6 +307,11 @@ func (u *User) HTMLURL() string {
|
|||
return setting.AppURL + url.PathEscape(u.Name)
|
||||
}
|
||||
|
||||
// APActorID returns the IRI to the api endpoint of the user
|
||||
func (u *User) APActorID() string {
|
||||
return fmt.Sprintf("%vapi/v1/activitypub/user-id/%v", setting.AppURL, url.PathEscape(fmt.Sprintf("%v", u.ID)))
|
||||
}
|
||||
|
||||
// OrganisationLink returns the organization sub page link.
|
||||
func (u *User) OrganisationLink() string {
|
||||
return setting.AppSubURL + "/org/" + url.PathEscape(u.Name)
|
||||
|
@ -834,6 +843,17 @@ func ValidateUser(u *User, cols ...string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (u User) Validate() []string {
|
||||
var result []string
|
||||
if err := ValidateUser(&u); err != nil {
|
||||
result = append(result, err.Error())
|
||||
}
|
||||
if err := ValidateEmail(u.Email); err != nil {
|
||||
result = append(result, err.Error())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// UpdateUserCols update user according special columns
|
||||
func UpdateUserCols(ctx context.Context, u *User, cols ...string) error {
|
||||
if err := ValidateUser(u, cols...); err != nil {
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/validation"
|
||||
)
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(FederatedUser))
|
||||
}
|
||||
|
||||
func CreateFederatedUser(ctx context.Context, user *User, federatedUser *FederatedUser) error {
|
||||
if res, err := validation.IsValid(user); !res {
|
||||
return err
|
||||
}
|
||||
overwrite := CreateUserOverwriteOptions{
|
||||
IsActive: optional.Some(false),
|
||||
IsRestricted: optional.Some(false),
|
||||
}
|
||||
|
||||
// Begin transaction
|
||||
ctx, committer, err := db.TxContext((ctx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
if err := CreateUser(ctx, user, &overwrite); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
federatedUser.UserID = user.ID
|
||||
if res, err := validation.IsValid(federatedUser); !res {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.GetEngine(ctx).Insert(federatedUser)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Commit transaction
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
func FindFederatedUser(ctx context.Context, externalID string,
|
||||
federationHostID int64,
|
||||
) (*User, *FederatedUser, error) {
|
||||
federatedUser := new(FederatedUser)
|
||||
user := new(User)
|
||||
has, err := db.GetEngine(ctx).Where("external_id=? and federation_host_id=?", externalID, federationHostID).Get(federatedUser)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
} else if !has {
|
||||
return nil, nil, nil
|
||||
}
|
||||
has, err = db.GetEngine(ctx).ID(federatedUser.UserID).Get(user)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
} else if !has {
|
||||
return nil, nil, fmt.Errorf("User %v for federated user is missing", federatedUser.UserID)
|
||||
}
|
||||
|
||||
if res, err := validation.IsValid(*user); !res {
|
||||
return nil, nil, err
|
||||
}
|
||||
if res, err := validation.IsValid(*federatedUser); !res {
|
||||
return nil, nil, err
|
||||
}
|
||||
return user, federatedUser, nil
|
||||
}
|
||||
|
||||
func DeleteFederatedUser(ctx context.Context, userID int64) error {
|
||||
_, err := db.GetEngine(ctx).Delete(&FederatedUser{UserID: userID})
|
||||
return err
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user_test
|
||||
|
@ -107,6 +108,15 @@ func TestGetAllUsers(t *testing.T) {
|
|||
assert.False(t, found[user_model.UserTypeOrganization], users)
|
||||
}
|
||||
|
||||
func TestAPActorID(t *testing.T) {
|
||||
user := user_model.User{ID: 1}
|
||||
url := user.APActorID()
|
||||
expected := "https://try.gitea.io/api/v1/activitypub/user-id/1"
|
||||
if url != expected {
|
||||
t.Errorf("unexpected APActorID, expected: %q, actual: %q", expected, url)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchUsers(t *testing.T) {
|
||||
defer tests.AddFixtures("models/user/fixtures/")()
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
|
|
@ -74,9 +74,6 @@ func RepositoryInbox(ctx *context.APIContext) {
|
|||
form := web.GetForm(ctx)
|
||||
httpStatus, title, err := federation.ProcessLikeActivity(ctx, form, repository.ID)
|
||||
if err != nil {
|
||||
log.Error("Status: %v", httpStatus)
|
||||
log.Error("Title: %v", title)
|
||||
log.Error("Error: %v", err)
|
||||
ctx.Error(httpStatus, title, err)
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
|
|
|
@ -7,13 +7,19 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/forgefed"
|
||||
"code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/activitypub"
|
||||
"code.gitea.io/gitea/modules/auth/password"
|
||||
fm "code.gitea.io/gitea/modules/forgefed"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/validation"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ProcessLikeActivity receives a ForgeLike activity and does the following:
|
||||
|
@ -40,6 +46,37 @@ func ProcessLikeActivity(ctx context.Context, form any, repositoryID int64) (int
|
|||
if !activity.IsNewer(federationHost.LatestActivity) {
|
||||
return http.StatusNotAcceptable, "Activity out of order.", fmt.Errorf("Activity already processed")
|
||||
}
|
||||
actorID, err := fm.NewPersonID(actorURI, string(federationHost.NodeInfo.SoftwareName))
|
||||
if err != nil {
|
||||
return http.StatusNotAcceptable, "Invalid PersonID", err
|
||||
}
|
||||
log.Info("Actor accepted:%v", actorID)
|
||||
|
||||
// parse objectID (repository)
|
||||
objectID, err := fm.NewRepositoryID(activity.Object.GetID().String(), string(forgefed.ForgejoSourceType))
|
||||
if err != nil {
|
||||
return http.StatusNotAcceptable, "Invalid objectId", err
|
||||
}
|
||||
if objectID.ID != fmt.Sprint(repositoryID) {
|
||||
return http.StatusNotAcceptable, "Invalid objectId", err
|
||||
}
|
||||
log.Info("Object accepted:%v", objectID)
|
||||
|
||||
// Check if user already exists
|
||||
user, _, err := user.FindFederatedUser(ctx, actorID.ID, federationHost.ID)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, "Searching for user failed", err
|
||||
}
|
||||
if user != nil {
|
||||
log.Info("Found local federatedUser: %v", user)
|
||||
} else {
|
||||
user, _, err = CreateUserFromAP(ctx, actorID, federationHost.ID)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, "Error creating federatedUser", err
|
||||
}
|
||||
log.Info("Created federatedUser from ap: %v", user)
|
||||
}
|
||||
log.Info("Got user:%v", user.Name)
|
||||
|
||||
return 0, "", nil
|
||||
}
|
||||
|
@ -96,3 +133,67 @@ func GetFederationHostForURI(ctx context.Context, actorURI string) (*forgefed.Fe
|
|||
}
|
||||
return federationHost, nil
|
||||
}
|
||||
|
||||
func CreateUserFromAP(ctx context.Context, personID fm.PersonID, federationHostID int64) (*user.User, *user.FederatedUser, error) {
|
||||
// ToDo: Do we get a publicKeyId from server, repo or owner or repo?
|
||||
actionsUser := user.NewActionsUser()
|
||||
client, err := activitypub.NewClient(ctx, actionsUser, "no idea where to get key material.")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
body, err := client.GetBody(personID.AsURI())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
person := fm.ForgePerson{}
|
||||
err = person.UnmarshalJSON(body)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if res, err := validation.IsValid(person); !res {
|
||||
return nil, nil, err
|
||||
}
|
||||
log.Info("Fetched valid person:%q", person)
|
||||
|
||||
localFqdn, err := url.ParseRequestURI(setting.AppURL)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
email := fmt.Sprintf("f%v@%v", uuid.New().String(), localFqdn.Hostname())
|
||||
loginName := personID.AsLoginName()
|
||||
name := fmt.Sprintf("%v%v", person.PreferredUsername.String(), personID.HostSuffix())
|
||||
fullName := person.Name.String()
|
||||
if len(person.Name) == 0 {
|
||||
fullName = name
|
||||
}
|
||||
password, err := password.Generate(32)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
newUser := user.User{
|
||||
LowerName: strings.ToLower(name),
|
||||
Name: name,
|
||||
FullName: fullName,
|
||||
Email: email,
|
||||
EmailNotificationsPreference: "disabled",
|
||||
Passwd: password,
|
||||
MustChangePassword: false,
|
||||
LoginName: loginName,
|
||||
Type: user.UserTypeRemoteUser,
|
||||
IsAdmin: false,
|
||||
NormalizedFederatedURI: personID.AsURI(),
|
||||
}
|
||||
federatedUser := user.FederatedUser{
|
||||
ExternalID: personID.ID,
|
||||
FederationHostID: federationHostID,
|
||||
}
|
||||
err = user.CreateFederatedUser(ctx, &newUser, &federatedUser)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
log.Info("Created federatedUser:%q", federatedUser)
|
||||
|
||||
return &newUser, &federatedUser, nil
|
||||
}
|
||||
|
|
|
@ -91,15 +91,15 @@ func TestActivityPubRepositoryInboxValid(t *testing.T) {
|
|||
`"openRegistrations":true,"usage":{"users":{"total":14,"activeHalfyear":2}},"metadata":{}}`)
|
||||
fmt.Fprint(res, responseBody)
|
||||
})
|
||||
federatedRoutes.HandleFunc("/api/v1/activitypub/user-id/2",
|
||||
federatedRoutes.HandleFunc("/api/v1/activitypub/user-id/15",
|
||||
func(res http.ResponseWriter, req *http.Request) {
|
||||
// curl -H "Accept: application/json" https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/2
|
||||
responseBody := fmt.Sprintf(`{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1"],` +
|
||||
`"id":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/2","type":"Person",` +
|
||||
`"id":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/15","type":"Person",` +
|
||||
`"icon":{"type":"Image","mediaType":"image/png","url":"https://federated-repo.prod.meissa.de/avatars/1bb05d9a5f6675ed0272af9ea193063c"},` +
|
||||
`"url":"https://federated-repo.prod.meissa.de/stargoose1","inbox":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/2/inbox",` +
|
||||
`"outbox":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/2/outbox","preferredUsername":"stargoose1",` +
|
||||
`"publicKey":{"id":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/2#main-key","owner":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/2",` +
|
||||
`"url":"https://federated-repo.prod.meissa.de/stargoose1","inbox":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/15/inbox",` +
|
||||
`"outbox":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/15/outbox","preferredUsername":"stargoose1",` +
|
||||
`"publicKey":{"id":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/15#main-key","owner":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/15",` +
|
||||
`"publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA18H5s7N6ItZUAh9tneII\nIuZdTTa3cZlLa/9ejWAHTkcp3WLW+/zbsumlMrWYfBy2/yTm56qasWt38iY4D6ul\n` +
|
||||
`CPiwhAqX3REvVq8tM79a2CEqZn9ka6vuXoDgBg/sBf/BUWqf7orkjUXwk/U0Egjf\nk5jcurF4vqf1u+rlAHH37dvSBaDjNj6Qnj4OP12bjfaY/yvs7+jue/eNXFHjzN4E\n` +
|
||||
`T2H4B/yeKTJ4UuAwTlLaNbZJul2baLlHelJPAsxiYaziVuV5P+IGWckY6RSerRaZ\nAkc4mmGGtjAyfN9aewe+lNVfwS7ElFx546PlLgdQgjmeSwLX8FWxbPE5A/PmaXCs\n` +
|
||||
|
@ -107,6 +107,22 @@ func TestActivityPubRepositoryInboxValid(t *testing.T) {
|
|||
`LXX5AQ1xQNtlssnVoUBqBrvZsX2jUUKUocvZqMGuE4hfAgMBAAE=\n-----END PUBLIC KEY-----\n"}}`)
|
||||
fmt.Fprint(res, responseBody)
|
||||
})
|
||||
federatedRoutes.HandleFunc("/api/v1/activitypub/user-id/30",
|
||||
func(res http.ResponseWriter, req *http.Request) {
|
||||
// curl -H "Accept: application/json" https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/3
|
||||
responseBody := fmt.Sprintf(`{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1"],` +
|
||||
`"id":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/30","type":"Person",` +
|
||||
`"icon":{"type":"Image","mediaType":"image/png","url":"https://federated-repo.prod.meissa.de/avatars/9c03f03d1c1f13f21976a22489326fe1"},` +
|
||||
`"url":"https://federated-repo.prod.meissa.de/stargoose2","inbox":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/30/inbox",` +
|
||||
`"outbox":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/30/outbox","preferredUsername":"stargoose2",` +
|
||||
`"publicKey":{"id":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/30#main-key","owner":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/30",` +
|
||||
`"publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyv5NytsfqpWXSrwuk8a3\n0W1zE13QJioXb/e3opgN2CfKZkdm3hb+4+mGKoU/rCqegnL9/AO0Aw+R8fCHXx44\n` +
|
||||
`iNkdVpdY8Dzq+tQ9IetPWbyVIBvSzGgvpqfS05JuVPsy8cBX9wByODjr5kq7k1/v\nY1G7E3uh0a/XJc+mZutwGC3gPgR93NSrqsvTPN4wdhCCu9uj02S8OBoKuSYaPkU+\n` +
|
||||
`tZ4CEDpnclAOw/eNiH4x2irMvVtruEgtlTA5K2I4YJrmtGLidus47FCyc8/zEKUh\nAeiD8KWDvqsQgOhUwcQgRxAnYVCoMD9cnE+WFFRHTuQecNlmdNFs3Cr0yKcWjDde\n` +
|
||||
`trvnehW7LfPveGb0tHRHPuVAJpncTOidUR5h/7pqMyvKHzuAHWomm9rEaGUxd/7a\nL1CFjAf39+QIEgu0Anj8mIc7CTiz+DQhDz+0jBOsQ0iDXc5GeBz7X9Xv4Jp966nq\n` +
|
||||
`MUR0GQGXvfZQN9IqMO+WoUVy10Ddhns1EWGlA0x4fecnAgMBAAE=\n-----END PUBLIC KEY-----\n"}}`)
|
||||
fmt.Fprint(res, responseBody)
|
||||
})
|
||||
federatedRoutes.HandleFunc("/",
|
||||
func(res http.ResponseWriter, req *http.Request) {
|
||||
t.Errorf("Unhandled request: %q", req.URL.EscapedPath())
|
||||
|
@ -129,20 +145,64 @@ func TestActivityPubRepositoryInboxValid(t *testing.T) {
|
|||
"%s/api/v1/activitypub/repository-id/%v/inbox",
|
||||
srv.URL, repositoryID)
|
||||
|
||||
activity := []byte(fmt.Sprintf(
|
||||
timeNow := time.Now().UTC()
|
||||
|
||||
activity1 := []byte(fmt.Sprintf(
|
||||
`{"type":"Like",`+
|
||||
`"startTime":"%s",`+
|
||||
`"actor":"%s/api/v1/activitypub/user-id/2",`+
|
||||
`"actor":"%s/api/v1/activitypub/user-id/15",`+
|
||||
`"object":"%s/api/v1/activitypub/repository-id/%v"}`,
|
||||
time.Now().UTC().Format(time.RFC3339),
|
||||
timeNow.Format(time.RFC3339),
|
||||
federatedSrv.URL, srv.URL, repositoryID))
|
||||
t.Logf("activity: %s", activity)
|
||||
resp, err := c.Post(activity, repoInboxURL)
|
||||
t.Logf("activity: %s", activity1)
|
||||
resp, err := c.Post(activity1, repoInboxURL)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusNoContent, resp.StatusCode)
|
||||
|
||||
unittest.AssertExistsAndLoadBean(t, &forgefed.FederationHost{HostFqdn: "127.0.0.1"})
|
||||
federationHost := unittest.AssertExistsAndLoadBean(t, &forgefed.FederationHost{HostFqdn: "127.0.0.1"})
|
||||
federatedUser := unittest.AssertExistsAndLoadBean(t, &user.FederatedUser{ExternalID: "15", FederationHostID: federationHost.ID})
|
||||
unittest.AssertExistsAndLoadBean(t, &user.User{ID: federatedUser.UserID})
|
||||
|
||||
// A like activity by a different user of the same federated host.
|
||||
activity2 := []byte(fmt.Sprintf(
|
||||
`{"type":"Like",`+
|
||||
`"startTime":"%s",`+
|
||||
`"actor":"%s/api/v1/activitypub/user-id/30",`+
|
||||
`"object":"%s/api/v1/activitypub/repository-id/%v"}`,
|
||||
// Make sure this activity happens later then the one before
|
||||
timeNow.Add(time.Second).Format(time.RFC3339),
|
||||
federatedSrv.URL, srv.URL, repositoryID))
|
||||
t.Logf("activity: %s", activity2)
|
||||
resp, err = c.Post(activity2, repoInboxURL)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusNoContent, resp.StatusCode)
|
||||
|
||||
federatedUser = unittest.AssertExistsAndLoadBean(t, &user.FederatedUser{ExternalID: "30", FederationHostID: federationHost.ID})
|
||||
unittest.AssertExistsAndLoadBean(t, &user.User{ID: federatedUser.UserID})
|
||||
|
||||
// The same user sends another like activity
|
||||
otherRepositoryID := 3
|
||||
otherRepoInboxURL := fmt.Sprintf(
|
||||
"%s/api/v1/activitypub/repository-id/%v/inbox",
|
||||
srv.URL, otherRepositoryID)
|
||||
activity3 := []byte(fmt.Sprintf(
|
||||
`{"type":"Like",`+
|
||||
`"startTime":"%s",`+
|
||||
`"actor":"%s/api/v1/activitypub/user-id/30",`+
|
||||
`"object":"%s/api/v1/activitypub/repository-id/%v"}`,
|
||||
// Make sure this activity happens later then the ones before
|
||||
timeNow.Add(time.Second*2).Format(time.RFC3339),
|
||||
federatedSrv.URL, srv.URL, otherRepositoryID))
|
||||
t.Logf("activity: %s", activity3)
|
||||
resp, err = c.Post(activity3, otherRepoInboxURL)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusNoContent, resp.StatusCode)
|
||||
|
||||
federatedUser = unittest.AssertExistsAndLoadBean(t, &user.FederatedUser{ExternalID: "30", FederationHostID: federationHost.ID})
|
||||
unittest.AssertExistsAndLoadBean(t, &user.User{ID: federatedUser.UserID})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue