summaryrefslogtreecommitdiffstats
path: root/services/auth/source/oauth2/source_sync.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel@debian.org>2024-10-18 20:33:49 +0200
committerDaniel Baumann <daniel@debian.org>2024-10-18 20:33:49 +0200
commitdd136858f1ea40ad3c94191d647487fa4f31926c (patch)
tree58fec94a7b2a12510c9664b21793f1ed560c6518 /services/auth/source/oauth2/source_sync.go
parentInitial commit. (diff)
downloadforgejo-debian.tar.xz
forgejo-debian.zip
Adding upstream version 9.0.0.upstream/9.0.0upstreamdebian
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to 'services/auth/source/oauth2/source_sync.go')
-rw-r--r--services/auth/source/oauth2/source_sync.go114
1 files changed, 114 insertions, 0 deletions
diff --git a/services/auth/source/oauth2/source_sync.go b/services/auth/source/oauth2/source_sync.go
new file mode 100644
index 0000000..5e30313
--- /dev/null
+++ b/services/auth/source/oauth2/source_sync.go
@@ -0,0 +1,114 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package oauth2
+
+import (
+ "context"
+ "time"
+
+ "code.gitea.io/gitea/models/auth"
+ "code.gitea.io/gitea/models/db"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/log"
+
+ "github.com/markbates/goth"
+ "golang.org/x/oauth2"
+)
+
+// Sync causes this OAuth2 source to synchronize its users with the db.
+func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
+ log.Trace("Doing: SyncExternalUsers[%s] %d", source.authSource.Name, source.authSource.ID)
+
+ if !updateExisting {
+ log.Info("SyncExternalUsers[%s] not running since updateExisting is false", source.authSource.Name)
+ return nil
+ }
+
+ provider, err := createProvider(source.authSource.Name, source)
+ if err != nil {
+ return err
+ }
+
+ if !provider.RefreshTokenAvailable() {
+ log.Trace("SyncExternalUsers[%s] provider doesn't support refresh tokens, can't synchronize", source.authSource.Name)
+ return nil
+ }
+
+ opts := user_model.FindExternalUserOptions{
+ HasRefreshToken: true,
+ Expired: true,
+ LoginSourceID: source.authSource.ID,
+ }
+
+ return user_model.IterateExternalLogin(ctx, opts, func(ctx context.Context, u *user_model.ExternalLoginUser) error {
+ return source.refresh(ctx, provider, u)
+ })
+}
+
+func (source *Source) refresh(ctx context.Context, provider goth.Provider, u *user_model.ExternalLoginUser) error {
+ log.Trace("Syncing login_source_id=%d external_id=%s expiration=%s", u.LoginSourceID, u.ExternalID, u.ExpiresAt)
+
+ shouldDisable := false
+
+ token, err := provider.RefreshToken(u.RefreshToken)
+ if err != nil {
+ if err, ok := err.(*oauth2.RetrieveError); ok && err.ErrorCode == "invalid_grant" {
+ // this signals that the token is not valid and the user should be disabled
+ shouldDisable = true
+ } else {
+ return err
+ }
+ }
+
+ user := &user_model.User{
+ LoginName: u.ExternalID,
+ LoginType: auth.OAuth2,
+ LoginSource: u.LoginSourceID,
+ }
+
+ hasUser, err := user_model.GetUser(ctx, user)
+ if err != nil {
+ return err
+ }
+
+ // If the grant is no longer valid, disable the user and
+ // delete local tokens. If the OAuth2 provider still
+ // recognizes them as a valid user, they will be able to login
+ // via their provider and reactivate their account.
+ if shouldDisable {
+ log.Info("SyncExternalUsers[%s] disabling user %d", source.authSource.Name, user.ID)
+
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ if hasUser {
+ user.IsActive = false
+ err := user_model.UpdateUserCols(ctx, user, "is_active")
+ if err != nil {
+ return err
+ }
+ }
+
+ // Delete stored tokens, since they are invalid. This
+ // also provents us from checking this in subsequent runs.
+ u.AccessToken = ""
+ u.RefreshToken = ""
+ u.ExpiresAt = time.Time{}
+
+ return user_model.UpdateExternalUserByExternalID(ctx, u)
+ })
+ }
+
+ // Otherwise, update the tokens
+ u.AccessToken = token.AccessToken
+ u.ExpiresAt = token.Expiry
+
+ // Some providers only update access tokens provide a new
+ // refresh token, so avoid updating it if it's empty
+ if token.RefreshToken != "" {
+ u.RefreshToken = token.RefreshToken
+ }
+
+ err = user_model.UpdateExternalUserByExternalID(ctx, u)
+
+ return err
+}