From e2277d07ca5112a797f8c86f825060027ff1c2da Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Mon, 4 Mar 2024 16:57:39 +0800
Subject: [PATCH] Move some asymkey functions to service layer (#28894)

After the moving, all models will not depend on `util.Rename` so that I
can do next step refactoring.
---
 cmd/admin_regenerate.go                       |  4 +-
 models/asymkey/ssh_key_authorized_keys.go     | 66 ++--------------
 models/asymkey/ssh_key_principals.go          | 40 ----------
 routers/init.go                               |  4 +-
 routers/web/user/setting/keys.go              |  2 +-
 services/asymkey/deploy_key.go                |  3 +-
 services/asymkey/ssh_key.go                   |  4 +-
 services/asymkey/ssh_key_authorized_keys.go   | 79 +++++++++++++++++++
 .../asymkey/ssh_key_authorized_principals.go  | 30 +++----
 services/asymkey/ssh_key_principals.go        | 54 +++++++++++++
 .../auth/source/ldap/source_authenticate.go   |  5 +-
 services/auth/source/ldap/source_sync.go      |  5 +-
 services/cron/tasks_extended.go               |  6 +-
 services/doctor/authorizedkeys.go             |  5 +-
 services/repository/delete.go                 |  3 +-
 services/user/user.go                         |  6 +-
 16 files changed, 176 insertions(+), 140 deletions(-)
 create mode 100644 services/asymkey/ssh_key_authorized_keys.go
 rename {models => services}/asymkey/ssh_key_authorized_principals.go (73%)
 create mode 100644 services/asymkey/ssh_key_principals.go

diff --git a/cmd/admin_regenerate.go b/cmd/admin_regenerate.go
index 0db505ff9c..ab769f6d0c 100644
--- a/cmd/admin_regenerate.go
+++ b/cmd/admin_regenerate.go
@@ -4,8 +4,8 @@
 package cmd
 
 import (
-	asymkey_model "code.gitea.io/gitea/models/asymkey"
 	"code.gitea.io/gitea/modules/graceful"
+	asymkey_service "code.gitea.io/gitea/services/asymkey"
 	repo_service "code.gitea.io/gitea/services/repository"
 
 	"github.com/urfave/cli/v2"
@@ -42,5 +42,5 @@ func runRegenerateKeys(_ *cli.Context) error {
 	if err := initDB(ctx); err != nil {
 		return err
 	}
-	return asymkey_model.RewriteAllPublicKeys(ctx)
+	return asymkey_service.RewriteAllPublicKeys(ctx)
 }
diff --git a/models/asymkey/ssh_key_authorized_keys.go b/models/asymkey/ssh_key_authorized_keys.go
index 267ab252c8..9279db2020 100644
--- a/models/asymkey/ssh_key_authorized_keys.go
+++ b/models/asymkey/ssh_key_authorized_keys.go
@@ -12,7 +12,6 @@ import (
 	"path/filepath"
 	"strings"
 	"sync"
-	"time"
 
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/modules/log"
@@ -44,6 +43,12 @@ const (
 
 var sshOpLocker sync.Mutex
 
+func WithSSHOpLocker(f func() error) error {
+	sshOpLocker.Lock()
+	defer sshOpLocker.Unlock()
+	return f()
+}
+
 // AuthorizedStringForKey creates the authorized keys string appropriate for the provided key
 func AuthorizedStringForKey(key *PublicKey) string {
 	sb := &strings.Builder{}
@@ -114,65 +119,6 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
 	return nil
 }
 
-// RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again.
-// Note: db.GetEngine(ctx).Iterate does not get latest data after insert/delete, so we have to call this function
-// outside any session scope independently.
-func RewriteAllPublicKeys(ctx context.Context) error {
-	// Don't rewrite key if internal server
-	if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile {
-		return nil
-	}
-
-	sshOpLocker.Lock()
-	defer sshOpLocker.Unlock()
-
-	if setting.SSH.RootPath != "" {
-		// First of ensure that the RootPath is present, and if not make it with 0700 permissions
-		// This of course doesn't guarantee that this is the right directory for authorized_keys
-		// but at least if it's supposed to be this directory and it doesn't exist and we're the
-		// right user it will at least be created properly.
-		err := os.MkdirAll(setting.SSH.RootPath, 0o700)
-		if err != nil {
-			log.Error("Unable to MkdirAll(%s): %v", setting.SSH.RootPath, err)
-			return err
-		}
-	}
-
-	fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
-	tmpPath := fPath + ".tmp"
-	t, err := os.OpenFile(tmpPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
-	if err != nil {
-		return err
-	}
-	defer func() {
-		t.Close()
-		if err := util.Remove(tmpPath); err != nil {
-			log.Warn("Unable to remove temporary authorized keys file: %s: Error: %v", tmpPath, err)
-		}
-	}()
-
-	if setting.SSH.AuthorizedKeysBackup {
-		isExist, err := util.IsExist(fPath)
-		if err != nil {
-			log.Error("Unable to check if %s exists. Error: %v", fPath, err)
-			return err
-		}
-		if isExist {
-			bakPath := fmt.Sprintf("%s_%d.gitea_bak", fPath, time.Now().Unix())
-			if err = util.CopyFile(fPath, bakPath); err != nil {
-				return err
-			}
-		}
-	}
-
-	if err := RegeneratePublicKeys(ctx, t); err != nil {
-		return err
-	}
-
-	t.Close()
-	return util.Rename(tmpPath, fPath)
-}
-
 // RegeneratePublicKeys regenerates the authorized_keys file
 func RegeneratePublicKeys(ctx context.Context, t io.StringWriter) error {
 	if err := db.GetEngine(ctx).Where("type != ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean any) (err error) {
diff --git a/models/asymkey/ssh_key_principals.go b/models/asymkey/ssh_key_principals.go
index 4e7dee2c91..e8b97d306e 100644
--- a/models/asymkey/ssh_key_principals.go
+++ b/models/asymkey/ssh_key_principals.go
@@ -9,51 +9,11 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
-	"code.gitea.io/gitea/models/perm"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/util"
 )
 
-// AddPrincipalKey adds new principal to database and authorized_principals file.
-func AddPrincipalKey(ctx context.Context, ownerID int64, content string, authSourceID int64) (*PublicKey, error) {
-	dbCtx, committer, err := db.TxContext(ctx)
-	if err != nil {
-		return nil, err
-	}
-	defer committer.Close()
-
-	// Principals cannot be duplicated.
-	has, err := db.GetEngine(dbCtx).
-		Where("content = ? AND type = ?", content, KeyTypePrincipal).
-		Get(new(PublicKey))
-	if err != nil {
-		return nil, err
-	} else if has {
-		return nil, ErrKeyAlreadyExist{0, "", content}
-	}
-
-	key := &PublicKey{
-		OwnerID:       ownerID,
-		Name:          content,
-		Content:       content,
-		Mode:          perm.AccessModeWrite,
-		Type:          KeyTypePrincipal,
-		LoginSourceID: authSourceID,
-	}
-	if err = db.Insert(dbCtx, key); err != nil {
-		return nil, fmt.Errorf("addKey: %w", err)
-	}
-
-	if err = committer.Commit(); err != nil {
-		return nil, err
-	}
-
-	committer.Close()
-
-	return key, RewriteAllPrincipalKeys(ctx)
-}
-
 // CheckPrincipalKeyString strips spaces and returns an error if the given principal contains newlines
 func CheckPrincipalKeyString(ctx context.Context, user *user_model.User, content string) (_ string, err error) {
 	if setting.SSH.Disabled {
diff --git a/routers/init.go b/routers/init.go
index 1dedbebeb5..aaf95920c2 100644
--- a/routers/init.go
+++ b/routers/init.go
@@ -9,7 +9,6 @@ import (
 	"runtime"
 
 	"code.gitea.io/gitea/models"
-	asymkey_model "code.gitea.io/gitea/models/asymkey"
 	authmodel "code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/modules/cache"
 	"code.gitea.io/gitea/modules/eventsource"
@@ -33,6 +32,7 @@ import (
 	"code.gitea.io/gitea/routers/private"
 	web_routers "code.gitea.io/gitea/routers/web"
 	actions_service "code.gitea.io/gitea/services/actions"
+	asymkey_service "code.gitea.io/gitea/services/asymkey"
 	"code.gitea.io/gitea/services/auth"
 	"code.gitea.io/gitea/services/auth/source/oauth2"
 	"code.gitea.io/gitea/services/automerge"
@@ -94,7 +94,7 @@ func syncAppConfForGit(ctx context.Context) error {
 		mustInitCtx(ctx, repo_service.SyncRepositoryHooks)
 
 		log.Info("re-write ssh public keys ...")
-		mustInitCtx(ctx, asymkey_model.RewriteAllPublicKeys)
+		mustInitCtx(ctx, asymkey_service.RewriteAllPublicKeys)
 
 		return system.AppState.Set(ctx, runtimeState)
 	}
diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go
index d2b60fc809..056fcc0ace 100644
--- a/routers/web/user/setting/keys.go
+++ b/routers/web/user/setting/keys.go
@@ -62,7 +62,7 @@ func KeysPost(ctx *context.Context) {
 			ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
 			return
 		}
-		if _, err = asymkey_model.AddPrincipalKey(ctx, ctx.Doer.ID, content, 0); err != nil {
+		if _, err = asymkey_service.AddPrincipalKey(ctx, ctx.Doer.ID, content, 0); err != nil {
 			ctx.Data["HasPrincipalError"] = true
 			switch {
 			case asymkey_model.IsErrKeyAlreadyExist(err), asymkey_model.IsErrKeyNameAlreadyUsed(err):
diff --git a/services/asymkey/deploy_key.go b/services/asymkey/deploy_key.go
index e127cbfc6e..324688c534 100644
--- a/services/asymkey/deploy_key.go
+++ b/services/asymkey/deploy_key.go
@@ -7,7 +7,6 @@ import (
 	"context"
 
 	"code.gitea.io/gitea/models"
-	asymkey_model "code.gitea.io/gitea/models/asymkey"
 	"code.gitea.io/gitea/models/db"
 	user_model "code.gitea.io/gitea/models/user"
 )
@@ -27,5 +26,5 @@ func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error
 		return err
 	}
 
-	return asymkey_model.RewriteAllPublicKeys(ctx)
+	return RewriteAllPublicKeys(ctx)
 }
diff --git a/services/asymkey/ssh_key.go b/services/asymkey/ssh_key.go
index 83d7edafa3..da57059d4b 100644
--- a/services/asymkey/ssh_key.go
+++ b/services/asymkey/ssh_key.go
@@ -43,8 +43,8 @@ func DeletePublicKey(ctx context.Context, doer *user_model.User, id int64) (err
 	committer.Close()
 
 	if key.Type == asymkey_model.KeyTypePrincipal {
-		return asymkey_model.RewriteAllPrincipalKeys(ctx)
+		return RewriteAllPrincipalKeys(ctx)
 	}
 
-	return asymkey_model.RewriteAllPublicKeys(ctx)
+	return RewriteAllPublicKeys(ctx)
 }
diff --git a/services/asymkey/ssh_key_authorized_keys.go b/services/asymkey/ssh_key_authorized_keys.go
new file mode 100644
index 0000000000..5caa5bbfb6
--- /dev/null
+++ b/services/asymkey/ssh_key_authorized_keys.go
@@ -0,0 +1,79 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package asymkey
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+	"time"
+
+	asymkey_model "code.gitea.io/gitea/models/asymkey"
+	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/util"
+)
+
+// RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again.
+// Note: db.GetEngine(ctx).Iterate does not get latest data after insert/delete, so we have to call this function
+// outside any session scope independently.
+func RewriteAllPublicKeys(ctx context.Context) error {
+	// Don't rewrite key if internal server
+	if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile {
+		return nil
+	}
+
+	return asymkey_model.WithSSHOpLocker(func() error {
+		return rewriteAllPublicKeys(ctx)
+	})
+}
+
+func rewriteAllPublicKeys(ctx context.Context) error {
+	if setting.SSH.RootPath != "" {
+		// First of ensure that the RootPath is present, and if not make it with 0700 permissions
+		// This of course doesn't guarantee that this is the right directory for authorized_keys
+		// but at least if it's supposed to be this directory and it doesn't exist and we're the
+		// right user it will at least be created properly.
+		err := os.MkdirAll(setting.SSH.RootPath, 0o700)
+		if err != nil {
+			log.Error("Unable to MkdirAll(%s): %v", setting.SSH.RootPath, err)
+			return err
+		}
+	}
+
+	fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
+	tmpPath := fPath + ".tmp"
+	t, err := os.OpenFile(tmpPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		t.Close()
+		if err := util.Remove(tmpPath); err != nil {
+			log.Warn("Unable to remove temporary authorized keys file: %s: Error: %v", tmpPath, err)
+		}
+	}()
+
+	if setting.SSH.AuthorizedKeysBackup {
+		isExist, err := util.IsExist(fPath)
+		if err != nil {
+			log.Error("Unable to check if %s exists. Error: %v", fPath, err)
+			return err
+		}
+		if isExist {
+			bakPath := fmt.Sprintf("%s_%d.gitea_bak", fPath, time.Now().Unix())
+			if err = util.CopyFile(fPath, bakPath); err != nil {
+				return err
+			}
+		}
+	}
+
+	if err := asymkey_model.RegeneratePublicKeys(ctx, t); err != nil {
+		return err
+	}
+
+	t.Close()
+	return util.Rename(tmpPath, fPath)
+}
diff --git a/models/asymkey/ssh_key_authorized_principals.go b/services/asymkey/ssh_key_authorized_principals.go
similarity index 73%
rename from models/asymkey/ssh_key_authorized_principals.go
rename to services/asymkey/ssh_key_authorized_principals.go
index 107d70c766..9154db7dbb 100644
--- a/models/asymkey/ssh_key_authorized_principals.go
+++ b/services/asymkey/ssh_key_authorized_principals.go
@@ -13,31 +13,22 @@ import (
 	"strings"
 	"time"
 
+	asymkey_model "code.gitea.io/gitea/models/asymkey"
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/util"
 )
 
-//  _____          __  .__                 .__                  .___
-// /  _  \  __ ___/  |_|  |__   ___________|__|_______ ____   __| _/
-// /  /_\  \|  |  \   __\  |  \ /  _ \_  __ \  \___   // __ \ / __ |
-// /    |    \  |  /|  | |   Y  (  <_> )  | \/  |/    /\  ___// /_/ |
-// \____|__  /____/ |__| |___|  /\____/|__|  |__/_____ \\___  >____ |
-//         \/                 \/                      \/    \/     \/
-// __________       .__              .__             .__
-// \______   _______|__| ____   ____ |_____________  |  |   ______
-//  |     ___\_  __ |  |/    \_/ ___\|  \____ \__  \ |  |  /  ___/
-//  |    |    |  | \|  |   |  \  \___|  |  |_> / __ \|  |__\___ \
-//  |____|    |__|  |__|___|  /\___  |__|   __(____  |____/____  >
-//                          \/     \/   |__|       \/          \/
-//
 // This file contains functions for creating authorized_principals files
 //
 // There is a dependence on the database within RewriteAllPrincipalKeys & RegeneratePrincipalKeys
 // The sshOpLocker is used from ssh_key_authorized_keys.go
 
-const authorizedPrincipalsFile = "authorized_principals"
+const (
+	authorizedPrincipalsFile = "authorized_principals"
+	tplCommentPrefix         = `# gitea public key`
+)
 
 // RewriteAllPrincipalKeys removes any authorized principal and rewrite all keys from database again.
 // Note: db.GetEngine(ctx).Iterate does not get latest data after insert/delete, so we have to call this function
@@ -48,9 +39,12 @@ func RewriteAllPrincipalKeys(ctx context.Context) error {
 		return nil
 	}
 
-	sshOpLocker.Lock()
-	defer sshOpLocker.Unlock()
+	return asymkey_model.WithSSHOpLocker(func() error {
+		return rewriteAllPrincipalKeys(ctx)
+	})
+}
 
+func rewriteAllPrincipalKeys(ctx context.Context) error {
 	if setting.SSH.RootPath != "" {
 		// First of ensure that the RootPath is present, and if not make it with 0700 permissions
 		// This of course doesn't guarantee that this is the right directory for authorized_keys
@@ -97,8 +91,8 @@ func RewriteAllPrincipalKeys(ctx context.Context) error {
 }
 
 func regeneratePrincipalKeys(ctx context.Context, t io.StringWriter) error {
-	if err := db.GetEngine(ctx).Where("type = ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean any) (err error) {
-		_, err = t.WriteString((bean.(*PublicKey)).AuthorizedString())
+	if err := db.GetEngine(ctx).Where("type = ?", asymkey_model.KeyTypePrincipal).Iterate(new(asymkey_model.PublicKey), func(idx int, bean any) (err error) {
+		_, err = t.WriteString((bean.(*asymkey_model.PublicKey)).AuthorizedString())
 		return err
 	}); err != nil {
 		return err
diff --git a/services/asymkey/ssh_key_principals.go b/services/asymkey/ssh_key_principals.go
new file mode 100644
index 0000000000..5ed5cfa782
--- /dev/null
+++ b/services/asymkey/ssh_key_principals.go
@@ -0,0 +1,54 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package asymkey
+
+import (
+	"context"
+	"fmt"
+
+	asymkey_model "code.gitea.io/gitea/models/asymkey"
+	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/models/perm"
+)
+
+// AddPrincipalKey adds new principal to database and authorized_principals file.
+func AddPrincipalKey(ctx context.Context, ownerID int64, content string, authSourceID int64) (*asymkey_model.PublicKey, error) {
+	dbCtx, committer, err := db.TxContext(ctx)
+	if err != nil {
+		return nil, err
+	}
+	defer committer.Close()
+
+	// Principals cannot be duplicated.
+	has, err := db.GetEngine(dbCtx).
+		Where("content = ? AND type = ?", content, asymkey_model.KeyTypePrincipal).
+		Get(new(asymkey_model.PublicKey))
+	if err != nil {
+		return nil, err
+	} else if has {
+		return nil, asymkey_model.ErrKeyAlreadyExist{
+			Content: content,
+		}
+	}
+
+	key := &asymkey_model.PublicKey{
+		OwnerID:       ownerID,
+		Name:          content,
+		Content:       content,
+		Mode:          perm.AccessModeWrite,
+		Type:          asymkey_model.KeyTypePrincipal,
+		LoginSourceID: authSourceID,
+	}
+	if err = db.Insert(dbCtx, key); err != nil {
+		return nil, fmt.Errorf("addKey: %w", err)
+	}
+
+	if err = committer.Commit(); err != nil {
+		return nil, err
+	}
+
+	committer.Close()
+
+	return key, RewriteAllPrincipalKeys(ctx)
+}
diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go
index 68ecd16342..6ebd3ea50a 100644
--- a/services/auth/source/ldap/source_authenticate.go
+++ b/services/auth/source/ldap/source_authenticate.go
@@ -13,6 +13,7 @@ import (
 	user_model "code.gitea.io/gitea/models/user"
 	auth_module "code.gitea.io/gitea/modules/auth"
 	"code.gitea.io/gitea/modules/optional"
+	asymkey_service "code.gitea.io/gitea/services/asymkey"
 	source_service "code.gitea.io/gitea/services/auth/source"
 	user_service "code.gitea.io/gitea/services/user"
 )
@@ -68,7 +69,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
 
 	if user != nil {
 		if isAttributeSSHPublicKeySet && asymkey_model.SynchronizePublicKeys(ctx, user, source.authSource, sr.SSHPublicKey) {
-			if err := asymkey_model.RewriteAllPublicKeys(ctx); err != nil {
+			if err := asymkey_service.RewriteAllPublicKeys(ctx); err != nil {
 				return user, err
 			}
 		}
@@ -94,7 +95,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
 		}
 
 		if isAttributeSSHPublicKeySet && asymkey_model.AddPublicKeysBySource(ctx, user, source.authSource, sr.SSHPublicKey) {
-			if err := asymkey_model.RewriteAllPublicKeys(ctx); err != nil {
+			if err := asymkey_service.RewriteAllPublicKeys(ctx); err != nil {
 				return user, err
 			}
 		}
diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go
index 62f052d68c..0c9491cd09 100644
--- a/services/auth/source/ldap/source_sync.go
+++ b/services/auth/source/ldap/source_sync.go
@@ -16,6 +16,7 @@ import (
 	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/optional"
+	asymkey_service "code.gitea.io/gitea/services/asymkey"
 	source_service "code.gitea.io/gitea/services/auth/source"
 	user_service "code.gitea.io/gitea/services/user"
 )
@@ -77,7 +78,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
 			log.Warn("SyncExternalUsers: Cancelled at update of %s before completed update of users", source.authSource.Name)
 			// Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed
 			if sshKeysNeedUpdate {
-				err = asymkey_model.RewriteAllPublicKeys(ctx)
+				err = asymkey_service.RewriteAllPublicKeys(ctx)
 				if err != nil {
 					log.Error("RewriteAllPublicKeys: %v", err)
 				}
@@ -195,7 +196,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
 
 	// Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed
 	if sshKeysNeedUpdate {
-		err = asymkey_model.RewriteAllPublicKeys(ctx)
+		err = asymkey_service.RewriteAllPublicKeys(ctx)
 		if err != nil {
 			log.Error("RewriteAllPublicKeys: %v", err)
 		}
diff --git a/services/cron/tasks_extended.go b/services/cron/tasks_extended.go
index 1dd5d70a38..0018c5facc 100644
--- a/services/cron/tasks_extended.go
+++ b/services/cron/tasks_extended.go
@@ -8,13 +8,13 @@ import (
 	"time"
 
 	activities_model "code.gitea.io/gitea/models/activities"
-	asymkey_model "code.gitea.io/gitea/models/asymkey"
 	"code.gitea.io/gitea/models/system"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/git"
 	issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/updatechecker"
+	asymkey_service "code.gitea.io/gitea/services/asymkey"
 	repo_service "code.gitea.io/gitea/services/repository"
 	archiver_service "code.gitea.io/gitea/services/repository/archiver"
 	user_service "code.gitea.io/gitea/services/user"
@@ -71,7 +71,7 @@ func registerRewriteAllPublicKeys() {
 		RunAtStart: false,
 		Schedule:   "@every 72h",
 	}, func(ctx context.Context, _ *user_model.User, _ Config) error {
-		return asymkey_model.RewriteAllPublicKeys(ctx)
+		return asymkey_service.RewriteAllPublicKeys(ctx)
 	})
 }
 
@@ -81,7 +81,7 @@ func registerRewriteAllPrincipalKeys() {
 		RunAtStart: false,
 		Schedule:   "@every 72h",
 	}, func(ctx context.Context, _ *user_model.User, _ Config) error {
-		return asymkey_model.RewriteAllPrincipalKeys(ctx)
+		return asymkey_service.RewriteAllPrincipalKeys(ctx)
 	})
 }
 
diff --git a/services/doctor/authorizedkeys.go b/services/doctor/authorizedkeys.go
index 050a4e7974..d5a96605b9 100644
--- a/services/doctor/authorizedkeys.go
+++ b/services/doctor/authorizedkeys.go
@@ -16,6 +16,7 @@ import (
 	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
+	asymkey_service "code.gitea.io/gitea/services/asymkey"
 )
 
 const tplCommentPrefix = `# gitea public key`
@@ -33,7 +34,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e
 			return fmt.Errorf("Unable to open authorized_keys file. ERROR: %w", err)
 		}
 		logger.Warn("Unable to open authorized_keys. (ERROR: %v). Attempting to rewrite...", err)
-		if err = asymkey_model.RewriteAllPublicKeys(ctx); err != nil {
+		if err = asymkey_service.RewriteAllPublicKeys(ctx); err != nil {
 			logger.Critical("Unable to rewrite authorized_keys file. ERROR: %v", err)
 			return fmt.Errorf("Unable to rewrite authorized_keys file. ERROR: %w", err)
 		}
@@ -76,7 +77,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e
 			return fmt.Errorf(`authorized_keys is out of date and should be regenerated with "gitea admin regenerate keys" or "gitea doctor --run authorized-keys --fix"`)
 		}
 		logger.Warn("authorized_keys is out of date. Attempting rewrite...")
-		err = asymkey_model.RewriteAllPublicKeys(ctx)
+		err = asymkey_service.RewriteAllPublicKeys(ctx)
 		if err != nil {
 			logger.Critical("Unable to rewrite authorized_keys file. ERROR: %v", err)
 			return fmt.Errorf("Unable to rewrite authorized_keys file. ERROR: %w", err)
diff --git a/services/repository/delete.go b/services/repository/delete.go
index 1eeec27660..8d6729f31b 100644
--- a/services/repository/delete.go
+++ b/services/repository/delete.go
@@ -27,6 +27,7 @@ import (
 	"code.gitea.io/gitea/modules/lfs"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/storage"
+	asymkey_service "code.gitea.io/gitea/services/asymkey"
 
 	"xorm.io/builder"
 )
@@ -277,7 +278,7 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID
 	committer.Close()
 
 	if needRewriteKeysFile {
-		if err := asymkey_model.RewriteAllPublicKeys(ctx); err != nil {
+		if err := asymkey_service.RewriteAllPublicKeys(ctx); err != nil {
 			log.Error("RewriteAllPublicKeys failed: %v", err)
 		}
 	}
diff --git a/services/user/user.go b/services/user/user.go
index 6604dba4d6..4fcb81581d 100644
--- a/services/user/user.go
+++ b/services/user/user.go
@@ -11,7 +11,6 @@ import (
 	"time"
 
 	"code.gitea.io/gitea/models"
-	asymkey_model "code.gitea.io/gitea/models/asymkey"
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/organization"
 	packages_model "code.gitea.io/gitea/models/packages"
@@ -24,6 +23,7 @@ import (
 	"code.gitea.io/gitea/modules/storage"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/services/agit"
+	asymkey_service "code.gitea.io/gitea/services/asymkey"
 	org_service "code.gitea.io/gitea/services/org"
 	"code.gitea.io/gitea/services/packages"
 	container_service "code.gitea.io/gitea/services/packages/container"
@@ -252,10 +252,10 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error {
 	}
 	committer.Close()
 
-	if err = asymkey_model.RewriteAllPublicKeys(ctx); err != nil {
+	if err = asymkey_service.RewriteAllPublicKeys(ctx); err != nil {
 		return err
 	}
-	if err = asymkey_model.RewriteAllPrincipalKeys(ctx); err != nil {
+	if err = asymkey_service.RewriteAllPrincipalKeys(ctx); err != nil {
 		return err
 	}