Merge pull request '[BUG] Don't remove builtin OAuth2 applications' (#3067) from gusted/forgejo-db-consistency-oauth2 into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3067
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
This commit is contained in:
Earl Warren 2024-04-06 07:02:07 +00:00
commit 8595e251a2
4 changed files with 93 additions and 3 deletions

View file

@ -0,0 +1,25 @@
-
id: 1000
uid: 0
name: "Git Credential Manager"
client_id: "e90ee53c-94e2-48ac-9358-a874fb9e0662"
redirect_uris: '["http://127.0.0.1", "https://127.0.0.1"]'
created_unix: 1712358091
updated_unix: 1712358091
-
id: 1001
uid: 0
name: "git-credential-oauth"
client_id: "a4792ccc-144e-407e-86c9-5e7d8d9c3269"
redirect_uris: '["http://127.0.0.1", "https://127.0.0.1"]'
created_unix: 1712358091
updated_unix: 1712358091
-
id: 1002
uid: 1234567890
name: "Should be removed"
client_id: "deadc0de-badd-dd11-fee1-deaddecafbad"
redirect_uris: '["http://127.0.0.1", "https://127.0.0.1"]'
created_unix: 1712358091
updated_unix: 1712358091

View file

@ -74,6 +74,13 @@ func BuiltinApplications() map[string]*BuiltinOAuth2Application {
return m return m
} }
func BuiltinApplicationsClientIDs() (clientIDs []string) {
for clientID := range BuiltinApplications() {
clientIDs = append(clientIDs, clientID)
}
return clientIDs
}
func Init(ctx context.Context) error { func Init(ctx context.Context) error {
builtinApps := BuiltinApplications() builtinApps := BuiltinApplications()
var builtinAllClientIDs []string var builtinAllClientIDs []string
@ -637,3 +644,27 @@ func DeleteOAuth2RelictsByUserID(ctx context.Context, userID int64) error {
return nil return nil
} }
// CountOrphanedOAuth2Applications returns the amount of orphaned OAuth2 applications.
func CountOrphanedOAuth2Applications(ctx context.Context) (int64, error) {
return db.GetEngine(ctx).
Table("`oauth2_application`").
Join("LEFT", "`user`", "`oauth2_application`.`uid` = `user`.`id`").
Where(builder.IsNull{"`user`.id"}).
Where(builder.NotIn("`oauth2_application`.`client_id`", BuiltinApplicationsClientIDs())).
Select("COUNT(`oauth2_application`.`id`)").
Count()
}
// DeleteOrphanedOAuth2Applications deletes orphaned OAuth2 applications.
func DeleteOrphanedOAuth2Applications(ctx context.Context) (int64, error) {
subQuery := builder.Select("`oauth2_application`.id").
From("`oauth2_application`").
Join("LEFT", "`user`", "`oauth2_application`.`uid` = `user`.`id`").
Where(builder.IsNull{"`user`.id"}).
Where(builder.NotIn("`oauth2_application`.`client_id`", BuiltinApplicationsClientIDs()))
b := builder.Delete(builder.In("id", subQuery)).From("`oauth2_application`")
_, err := db.GetEngine(ctx).Exec(b)
return -1, err
}

View file

@ -4,11 +4,13 @@
package auth_test package auth_test
import ( import (
"path/filepath"
"testing" "testing"
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -265,3 +267,31 @@ func TestOAuth2AuthorizationCode_Invalidate(t *testing.T) {
func TestOAuth2AuthorizationCode_TableName(t *testing.T) { func TestOAuth2AuthorizationCode_TableName(t *testing.T) {
assert.Equal(t, "oauth2_authorization_code", new(auth_model.OAuth2AuthorizationCode).TableName()) assert.Equal(t, "oauth2_authorization_code", new(auth_model.OAuth2AuthorizationCode).TableName())
} }
func TestBuiltinApplicationsClientIDs(t *testing.T) {
assert.EqualValues(t, []string{"a4792ccc-144e-407e-86c9-5e7d8d9c3269", "e90ee53c-94e2-48ac-9358-a874fb9e0662", "d57cb8c4-630c-4168-8324-ec79935e18d4"}, auth_model.BuiltinApplicationsClientIDs())
}
func TestOrphanedOAuth2Applications(t *testing.T) {
defer unittest.OverrideFixtures(
unittest.FixturesOptions{
Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"),
Base: setting.AppWorkPath,
Dirs: []string{"models/auth/TestOrphanedOAuth2Applications/"},
},
)()
assert.NoError(t, unittest.PrepareTestDatabase())
count, err := auth_model.CountOrphanedOAuth2Applications(db.DefaultContext)
assert.NoError(t, err)
assert.EqualValues(t, 1, count)
unittest.AssertExistsIf(t, true, &auth_model.OAuth2Application{ID: 1002})
_, err = auth_model.DeleteOrphanedOAuth2Applications(db.DefaultContext)
assert.NoError(t, err)
count, err = auth_model.CountOrphanedOAuth2Applications(db.DefaultContext)
assert.NoError(t, err)
assert.EqualValues(t, 0, count)
unittest.AssertExistsIf(t, false, &auth_model.OAuth2Application{ID: 1002})
}

View file

@ -8,6 +8,7 @@ import (
actions_model "code.gitea.io/gitea/models/actions" actions_model "code.gitea.io/gitea/models/actions"
activities_model "code.gitea.io/gitea/models/activities" activities_model "code.gitea.io/gitea/models/activities"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/migrations" "code.gitea.io/gitea/models/migrations"
@ -164,6 +165,12 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er
Fixer: repo_model.DeleteOrphanedTopics, Fixer: repo_model.DeleteOrphanedTopics,
FixedMessage: "Removed", FixedMessage: "Removed",
}, },
{
Name: "Orphaned OAuth2Application without existing User",
Counter: auth_model.CountOrphanedOAuth2Applications,
Fixer: auth_model.DeleteOrphanedOAuth2Applications,
FixedMessage: "Removed",
},
} }
// TODO: function to recalc all counters // TODO: function to recalc all counters
@ -208,9 +215,6 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er
// find OAuth2Grant without existing user // find OAuth2Grant without existing user
genericOrphanCheck("Orphaned OAuth2Grant without existing User", genericOrphanCheck("Orphaned OAuth2Grant without existing User",
"oauth2_grant", "user", "oauth2_grant.user_id=`user`.id"), "oauth2_grant", "user", "oauth2_grant.user_id=`user`.id"),
// find OAuth2Application without existing user
genericOrphanCheck("Orphaned OAuth2Application without existing User",
"oauth2_application", "user", "oauth2_application.uid=`user`.id"),
// find OAuth2AuthorizationCode without existing OAuth2Grant // find OAuth2AuthorizationCode without existing OAuth2Grant
genericOrphanCheck("Orphaned OAuth2AuthorizationCode without existing OAuth2Grant", genericOrphanCheck("Orphaned OAuth2AuthorizationCode without existing OAuth2Grant",
"oauth2_authorization_code", "oauth2_grant", "oauth2_authorization_code.grant_id=oauth2_grant.id"), "oauth2_authorization_code", "oauth2_grant", "oauth2_authorization_code.grant_id=oauth2_grant.id"),