From edf7f61b8325f98c26fad42e7201cb8de76e187a Mon Sep 17 00:00:00 2001
From: Michael Jerger <michael.jerger@meissa-gmbh.de>
Date: Wed, 7 Feb 2024 17:11:43 +0100
Subject: [PATCH] mv federated_user_creation to user package

---
 models/user/user_repository.go           |  2 +-
 models/user/user_service.go              | 30 ++++-----
 routers/api/v1/activitypub/repository.go | 77 ++++++------------------
 3 files changed, 34 insertions(+), 75 deletions(-)

diff --git a/models/user/user_repository.go b/models/user/user_repository.go
index 43e7db32a3..e6d625aae5 100644
--- a/models/user/user_repository.go
+++ b/models/user/user_repository.go
@@ -15,7 +15,7 @@ func init() {
 	db.RegisterModel(new(FederatedUser))
 }
 
-func CreateFederationUser(ctx context.Context, user FederatedUser) error {
+func CreateFederationUser(ctx context.Context, user *FederatedUser) error {
 	if res, err := validation.IsValid(user); !res {
 		return fmt.Errorf("FederatedUser is not valid: %v", err)
 	}
diff --git a/models/user/user_service.go b/models/user/user_service.go
index 0b010e7f8c..01df922806 100644
--- a/models/user/user_service.go
+++ b/models/user/user_service.go
@@ -4,30 +4,25 @@
 package user
 
 import (
+	"context"
 	"fmt"
 	"net/url"
 	"strings"
 
 	"code.gitea.io/gitea/models/forgefed"
-	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/util"
-	"code.gitea.io/gitea/modules/validation"
 	"github.com/google/uuid"
 	pwd_gen "github.com/sethvargo/go-password/password"
 )
 
-func CreateFederatedUserFromAP(ctx *context.APIContext, person forgefed.ForgePerson, personID forgefed.PersonID,
-	federationHostID int64) (*User, error) {
-	if res, err := validation.IsValid(person); !res {
-		return nil, err
-	}
-	log.Info("RepositoryInbox: validated person: %q", person)
+func CreateFederatedUserFromAP(ctx context.Context, person forgefed.ForgePerson,
+	personID forgefed.PersonID, federationHostID int64) (*User, *FederatedUser, error) {
 
 	localFqdn, err := url.ParseRequestURI(setting.AppURL)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
 	email := fmt.Sprintf("f%v@%v", uuid.New().String(), localFqdn.Hostname())
@@ -41,10 +36,10 @@ func CreateFederatedUserFromAP(ctx *context.APIContext, person forgefed.ForgePer
 
 	password, err := pwd_gen.Generate(32, 10, 10, false, true)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
-	user := &User{
+	user := User{
 		LowerName:                    strings.ToLower(person.PreferredUsername.String()),
 		Name:                         name,
 		FullName:                     fullName,
@@ -62,19 +57,20 @@ func CreateFederatedUserFromAP(ctx *context.APIContext, person forgefed.ForgePer
 		IsRestricted: util.OptionalBoolFalse,
 	}
 
-	if err := CreateUser(ctx, user, overwrite); err != nil {
-		return nil, err
+	// TODO: Transaction around
+	if err := CreateUser(ctx, &user, overwrite); err != nil {
+		return nil, nil, err
 	}
 
 	federatedUser, err := NewFederatedUser(user.ID, personID.ID, federationHostID)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
-	err = CreateFederationUser(ctx, federatedUser)
+	err = CreateFederationUser(ctx, &federatedUser)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
-	return user, nil
+	return &user, &federatedUser, nil
 }
diff --git a/routers/api/v1/activitypub/repository.go b/routers/api/v1/activitypub/repository.go
index 2f61283bc3..e7052625f5 100644
--- a/routers/api/v1/activitypub/repository.go
+++ b/routers/api/v1/activitypub/repository.go
@@ -6,7 +6,6 @@ package activitypub
 import (
 	"fmt"
 	"net/http"
-	"net/url"
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
@@ -20,10 +19,8 @@ import (
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/validation"
 	"code.gitea.io/gitea/modules/web"
-	"github.com/google/uuid"
 
 	ap "github.com/go-ap/activitypub"
-	pwd_gen "github.com/sethvargo/go-password/password"
 )
 
 // Repository function returns the Repository actor for a repo
@@ -97,28 +94,28 @@ func RepositoryInbox(ctx *context.APIContext) {
 			"RepositoryInbox: Validating ActorID", err)
 		return
 	}
-	federationInfo, err := forgefed.FindFederationHostByFqdn(ctx, rawActorID.Host)
+	federationHost, err := forgefed.FindFederationHostByFqdn(ctx, rawActorID.Host)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError,
 			"RepositoryInbox: Error while loading FederationInfo", err)
 		return
 	}
-	if federationInfo == nil {
-		result, err := createFederationInfo(ctx, rawActorID)
+	if federationHost == nil {
+		result, err := createFederationHost(ctx, rawActorID)
 		if err != nil {
 			ctx.Error(http.StatusNotAcceptable, "RepositoryInbox: Validate actorId", err)
 			return
 		}
-		federationInfo = &result
-		log.Info("RepositoryInbox: federationInfo validated: %v", federationInfo)
+		federationHost = &result
+		log.Info("RepositoryInbox: federationInfo validated: %v", federationHost)
 	}
-	if !activity.IsNewer(federationInfo.LatestActivity) {
+	if !activity.IsNewer(federationHost.LatestActivity) {
 		ctx.Error(http.StatusNotAcceptable, "RepositoryInbox: Validate Activity",
 			fmt.Errorf("Activity already processed"))
 		return
 	}
 
-	actorID, err := forgefed.NewPersonID(actorURI, string(federationInfo.NodeInfo.Source))
+	actorID, err := forgefed.NewPersonID(actorURI, string(federationHost.NodeInfo.Source))
 	if err != nil {
 		ctx.Error(http.StatusNotAcceptable, "RepositoryInbox: Validate actorId", err)
 		return
@@ -140,6 +137,8 @@ func RepositoryInbox(ctx *context.APIContext) {
 	log.Info("RepositoryInbox: remoteStargazer: %v", actorAsLoginID)
 
 	// Check if user already exists
+	// TODO: search for federation user instead
+	// users, _, err := SearchFederatedUser(actorID.ID, federationHost.ID)
 	users, err := SearchUsersByLoginName(actorAsLoginID)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "RepositoryInbox: Searching for user failed", err)
@@ -150,7 +149,7 @@ func RepositoryInbox(ctx *context.APIContext) {
 	switch len(users) {
 	case 0:
 		{
-			user, err = createUserFromAP(ctx, actorID)
+			user, err = createUserFromAP(ctx, actorID, federationHost.ID)
 			if err != nil {
 				ctx.Error(http.StatusInternalServerError,
 					"RepositoryInbox: Creating federated user failed", err)
@@ -180,8 +179,8 @@ func RepositoryInbox(ctx *context.APIContext) {
 			return
 		}
 	}
-	federationInfo.LatestActivity = activity.StartTime
-	err = forgefed.UpdateFederationHost(ctx, *federationInfo)
+	federationHost.LatestActivity = activity.StartTime
+	err = forgefed.UpdateFederationHost(ctx, *federationHost)
 	if err != nil {
 		ctx.Error(http.StatusNotAcceptable, "RepositoryInbox: error updateing federateionInfo", err)
 		return
@@ -212,7 +211,7 @@ func SearchUsersByLoginName(loginName string) ([]*user_model.User, error) {
 	return users, nil
 }
 
-func createFederationInfo(ctx *context.APIContext, actorID forgefed.ActorID) (forgefed.FederationHost, error) {
+func createFederationHost(ctx *context.APIContext, actorID forgefed.ActorID) (forgefed.FederationHost, error) {
 	actionsUser := user_model.NewActionsUser()
 	client, err := api.NewClient(ctx, actionsUser, "no idea where to get key material.")
 	if err != nil {
@@ -245,68 +244,32 @@ func createFederationInfo(ctx *context.APIContext, actorID forgefed.ActorID) (fo
 	return result, nil
 }
 
-func createUserFromAP(ctx *context.APIContext, personID forgefed.PersonID) (*user_model.User, error) {
+func createUserFromAP(ctx *context.APIContext, personID forgefed.PersonID, federationHostID int64) (*user_model.User, error) {
 	// ToDo: Do we get a publicKeyId from server, repo or owner or repo?
 	actionsUser := user_model.NewActionsUser()
 	client, err := api.NewClient(ctx, actionsUser, "no idea where to get key material.")
 	if err != nil {
-		return &user_model.User{}, err
+		return nil, err
 	}
 
 	body, err := client.GetBody(personID.AsURI())
 	if err != nil {
-		return &user_model.User{}, err
+		return nil, err
 	}
 
 	person := forgefed.ForgePerson{}
 	err = person.UnmarshalJSON(body)
 	if err != nil {
-		return &user_model.User{}, err
+		return nil, err
 	}
 	if res, err := validation.IsValid(person); !res {
-		return &user_model.User{}, err
+		return nil, err
 	}
 	log.Info("RepositoryInbox: validated person: %q", person)
 
-	localFqdn, err := url.ParseRequestURI(setting.AppURL)
+	user, _, err := user_model.CreateFederatedUserFromAP(ctx, person, personID, federationHostID)
 	if err != nil {
-		return &user_model.User{}, 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())
-	log.Info("RepositoryInbox: person.Name: %v", person.Name)
-	fullName := person.Name.String()
-	if len(person.Name) == 0 {
-		fullName = name
-	}
-
-	password, err := pwd_gen.Generate(32, 10, 10, false, true)
-	if err != nil {
-		return &user_model.User{}, err
-	}
-
-	user := &user_model.User{
-		LowerName:                    strings.ToLower(person.PreferredUsername.String()),
-		Name:                         name,
-		FullName:                     fullName,
-		Email:                        email,
-		EmailNotificationsPreference: "disabled",
-		Passwd:                       password,
-		MustChangePassword:           false,
-		LoginName:                    loginName,
-		Type:                         user_model.UserTypeRemoteUser,
-		IsAdmin:                      false,
-	}
-
-	overwrite := &user_model.CreateUserOverwriteOptions{
-		IsActive:     util.OptionalBoolFalse,
-		IsRestricted: util.OptionalBoolFalse,
-	}
-
-	if err := user_model.CreateUser(ctx, user, overwrite); err != nil {
-		return &user_model.User{}, err
+		return nil, err
 	}
 
 	return user, nil