loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request 'fix: Revert "allow synchronizing user status from OAuth2 login providers (#31572)"' (#6249) from earl-warren/forgejo:wip-oauth2 into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6249

+48 -371
+1 -1
models/auth/source.go
··· 216 216 return ErrSourceAlreadyExist{source.Name} 217 217 } 218 218 // Synchronization is only available with LDAP for now 219 - if !source.IsLDAP() && !source.IsOAuth2() { 219 + if !source.IsLDAP() { 220 220 source.IsSyncEnabled = false 221 221 } 222 222
+3 -38
models/user/external_login_user.go
··· 160 160 return err 161 161 } 162 162 163 - // EnsureLinkExternalToUser link the external user to the user 164 - func EnsureLinkExternalToUser(ctx context.Context, external *ExternalLoginUser) error { 165 - has, err := db.Exist[ExternalLoginUser](ctx, builder.Eq{ 166 - "external_id": external.ExternalID, 167 - "login_source_id": external.LoginSourceID, 168 - }) 169 - if err != nil { 170 - return err 171 - } 172 - 173 - if has { 174 - _, err = db.GetEngine(ctx).Where("external_id=? AND login_source_id=?", external.ExternalID, external.LoginSourceID).AllCols().Update(external) 175 - return err 176 - } 177 - 178 - _, err = db.GetEngine(ctx).Insert(external) 179 - return err 180 - } 181 - 182 163 // FindExternalUserOptions represents an options to find external users 183 164 type FindExternalUserOptions struct { 184 165 db.ListOptions 185 - Provider string 186 - UserID int64 187 - LoginSourceID int64 188 - HasRefreshToken bool 189 - Expired bool 190 - OrderBy string 166 + Provider string 167 + UserID int64 168 + OrderBy string 191 169 } 192 170 193 171 func (opts FindExternalUserOptions) ToConds() builder.Cond { ··· 198 176 if opts.UserID > 0 { 199 177 cond = cond.And(builder.Eq{"user_id": opts.UserID}) 200 178 } 201 - if opts.Expired { 202 - cond = cond.And(builder.Lt{"expires_at": time.Now()}) 203 - } 204 - if opts.HasRefreshToken { 205 - cond = cond.And(builder.Neq{"refresh_token": ""}) 206 - } 207 - if opts.LoginSourceID != 0 { 208 - cond = cond.And(builder.Eq{"login_source_id": opts.LoginSourceID}) 209 - } 210 179 return cond 211 180 } 212 181 213 182 func (opts FindExternalUserOptions) ToOrders() string { 214 183 return opts.OrderBy 215 184 } 216 - 217 - func IterateExternalLogin(ctx context.Context, opts FindExternalUserOptions, f func(ctx context.Context, u *ExternalLoginUser) error) error { 218 - return db.Iterate(ctx, opts.ToConds(), f) 219 - }
+4 -2
routers/web/auth/auth.go
··· 599 599 notify_service.NewUserSignUp(ctx, u) 600 600 // update external user information 601 601 if gothUser != nil { 602 - if err := externalaccount.EnsureLinkExternalToUser(ctx, u, *gothUser); err != nil { 603 - log.Error("EnsureLinkExternalToUser failed: %v", err) 602 + if err := externalaccount.UpdateExternalUser(ctx, u, *gothUser); err != nil { 603 + if !errors.Is(err, util.ErrNotExist) { 604 + log.Error("UpdateExternalUser failed: %v", err) 605 + } 604 606 } 605 607 } 606 608
+33 -31
routers/web/auth/oauth.go
··· 1205 1205 1206 1206 groups := getClaimedGroups(oauth2Source, &gothUser) 1207 1207 1208 - opts := &user_service.UpdateOptions{} 1209 - 1210 - // Reactivate user if they are deactivated 1211 - if !u.IsActive { 1212 - opts.IsActive = optional.Some(true) 1213 - } 1214 - 1215 - // Update GroupClaims 1216 - opts.IsAdmin, opts.IsRestricted = getUserAdminAndRestrictedFromGroupClaims(oauth2Source, &gothUser) 1217 - 1218 - if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval { 1219 - if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, oauth2Source.GroupTeamMapRemoval); err != nil { 1220 - ctx.ServerError("SyncGroupsToTeams", err) 1221 - return 1222 - } 1223 - } 1224 - 1225 - if err := externalaccount.EnsureLinkExternalToUser(ctx, u, gothUser); err != nil { 1226 - ctx.ServerError("EnsureLinkExternalToUser", err) 1227 - return 1228 - } 1229 - 1230 1208 // If this user is enrolled in 2FA and this source doesn't override it, 1231 1209 // we can't sign the user in just yet. Instead, redirect them to the 2FA authentication page. 1232 1210 if !needs2FA { 1233 - // Register last login 1234 - opts.SetLastLogin = true 1235 - 1236 - if err := user_service.UpdateUser(ctx, u, opts); err != nil { 1237 - ctx.ServerError("UpdateUser", err) 1238 - return 1239 - } 1240 - 1241 1211 if err := updateSession(ctx, nil, map[string]any{ 1242 1212 "uid": u.ID, 1243 1213 }); err != nil { ··· 1248 1218 // Clear whatever CSRF cookie has right now, force to generate a new one 1249 1219 ctx.Csrf.DeleteCookie(ctx) 1250 1220 1221 + opts := &user_service.UpdateOptions{ 1222 + SetLastLogin: true, 1223 + } 1224 + opts.IsAdmin, opts.IsRestricted = getUserAdminAndRestrictedFromGroupClaims(oauth2Source, &gothUser) 1225 + if err := user_service.UpdateUser(ctx, u, opts); err != nil { 1226 + ctx.ServerError("UpdateUser", err) 1227 + return 1228 + } 1229 + 1230 + if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval { 1231 + if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, oauth2Source.GroupTeamMapRemoval); err != nil { 1232 + ctx.ServerError("SyncGroupsToTeams", err) 1233 + return 1234 + } 1235 + } 1236 + 1237 + // update external user information 1238 + if err := externalaccount.UpdateExternalUser(ctx, u, gothUser); err != nil { 1239 + if !errors.Is(err, util.ErrNotExist) { 1240 + log.Error("UpdateExternalUser failed: %v", err) 1241 + } 1242 + } 1243 + 1251 1244 if err := resetLocale(ctx, u); err != nil { 1252 1245 ctx.ServerError("resetLocale", err) 1253 1246 return ··· 1263 1256 return 1264 1257 } 1265 1258 1266 - if opts.IsActive.Has() || opts.IsAdmin.Has() || opts.IsRestricted.Has() { 1259 + opts := &user_service.UpdateOptions{} 1260 + opts.IsAdmin, opts.IsRestricted = getUserAdminAndRestrictedFromGroupClaims(oauth2Source, &gothUser) 1261 + if opts.IsAdmin.Has() || opts.IsRestricted.Has() { 1267 1262 if err := user_service.UpdateUser(ctx, u, opts); err != nil { 1268 1263 ctx.ServerError("UpdateUser", err) 1264 + return 1265 + } 1266 + } 1267 + 1268 + if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval { 1269 + if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, oauth2Source.GroupTeamMapRemoval); err != nil { 1270 + ctx.ServerError("SyncGroupsToTeams", err) 1269 1271 return 1270 1272 } 1271 1273 }
-14
services/auth/source/oauth2/main_test.go
··· 1 - // Copyright 2024 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package oauth2 5 - 6 - import ( 7 - "testing" 8 - 9 - "code.gitea.io/gitea/models/unittest" 10 - ) 11 - 12 - func TestMain(m *testing.M) { 13 - unittest.MainTest(m, &unittest.TestOptions{}) 14 - }
-62
services/auth/source/oauth2/providers_test.go
··· 1 - // Copyright 2024 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package oauth2 5 - 6 - import ( 7 - "time" 8 - 9 - "github.com/markbates/goth" 10 - "golang.org/x/oauth2" 11 - ) 12 - 13 - type fakeProvider struct{} 14 - 15 - func (p *fakeProvider) Name() string { 16 - return "fake" 17 - } 18 - 19 - func (p *fakeProvider) SetName(name string) {} 20 - 21 - func (p *fakeProvider) BeginAuth(state string) (goth.Session, error) { 22 - return nil, nil 23 - } 24 - 25 - func (p *fakeProvider) UnmarshalSession(string) (goth.Session, error) { 26 - return nil, nil 27 - } 28 - 29 - func (p *fakeProvider) FetchUser(goth.Session) (goth.User, error) { 30 - return goth.User{}, nil 31 - } 32 - 33 - func (p *fakeProvider) Debug(bool) { 34 - } 35 - 36 - func (p *fakeProvider) RefreshToken(refreshToken string) (*oauth2.Token, error) { 37 - switch refreshToken { 38 - case "expired": 39 - return nil, &oauth2.RetrieveError{ 40 - ErrorCode: "invalid_grant", 41 - } 42 - default: 43 - return &oauth2.Token{ 44 - AccessToken: "token", 45 - TokenType: "Bearer", 46 - RefreshToken: "refresh", 47 - Expiry: time.Now().Add(time.Hour), 48 - }, nil 49 - } 50 - } 51 - 52 - func (p *fakeProvider) RefreshTokenAvailable() bool { 53 - return true 54 - } 55 - 56 - func init() { 57 - RegisterGothProvider( 58 - NewSimpleProvider("fake", "Fake", []string{"account"}, 59 - func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider { 60 - return &fakeProvider{} 61 - })) 62 - }
+1 -1
services/auth/source/oauth2/source.go
··· 36 36 return json.UnmarshalHandleDoubleEncode(bs, &source) 37 37 } 38 38 39 - // ToDB exports an OAuth2Config to a serialized format. 39 + // ToDB exports an SMTPConfig to a serialized format. 40 40 func (source *Source) ToDB() ([]byte, error) { 41 41 return json.Marshal(source) 42 42 }
-114
services/auth/source/oauth2/source_sync.go
··· 1 - // Copyright 2024 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package oauth2 5 - 6 - import ( 7 - "context" 8 - "time" 9 - 10 - "code.gitea.io/gitea/models/auth" 11 - "code.gitea.io/gitea/models/db" 12 - user_model "code.gitea.io/gitea/models/user" 13 - "code.gitea.io/gitea/modules/log" 14 - 15 - "github.com/markbates/goth" 16 - "golang.org/x/oauth2" 17 - ) 18 - 19 - // Sync causes this OAuth2 source to synchronize its users with the db. 20 - func (source *Source) Sync(ctx context.Context, updateExisting bool) error { 21 - log.Trace("Doing: SyncExternalUsers[%s] %d", source.authSource.Name, source.authSource.ID) 22 - 23 - if !updateExisting { 24 - log.Info("SyncExternalUsers[%s] not running since updateExisting is false", source.authSource.Name) 25 - return nil 26 - } 27 - 28 - provider, err := createProvider(source.authSource.Name, source) 29 - if err != nil { 30 - return err 31 - } 32 - 33 - if !provider.RefreshTokenAvailable() { 34 - log.Trace("SyncExternalUsers[%s] provider doesn't support refresh tokens, can't synchronize", source.authSource.Name) 35 - return nil 36 - } 37 - 38 - opts := user_model.FindExternalUserOptions{ 39 - HasRefreshToken: true, 40 - Expired: true, 41 - LoginSourceID: source.authSource.ID, 42 - } 43 - 44 - return user_model.IterateExternalLogin(ctx, opts, func(ctx context.Context, u *user_model.ExternalLoginUser) error { 45 - return source.refresh(ctx, provider, u) 46 - }) 47 - } 48 - 49 - func (source *Source) refresh(ctx context.Context, provider goth.Provider, u *user_model.ExternalLoginUser) error { 50 - log.Trace("Syncing login_source_id=%d external_id=%s expiration=%s", u.LoginSourceID, u.ExternalID, u.ExpiresAt) 51 - 52 - shouldDisable := false 53 - 54 - token, err := provider.RefreshToken(u.RefreshToken) 55 - if err != nil { 56 - if err, ok := err.(*oauth2.RetrieveError); ok && err.ErrorCode == "invalid_grant" { 57 - // this signals that the token is not valid and the user should be disabled 58 - shouldDisable = true 59 - } else { 60 - return err 61 - } 62 - } 63 - 64 - user := &user_model.User{ 65 - LoginName: u.ExternalID, 66 - LoginType: auth.OAuth2, 67 - LoginSource: u.LoginSourceID, 68 - } 69 - 70 - hasUser, err := user_model.GetUser(ctx, user) 71 - if err != nil { 72 - return err 73 - } 74 - 75 - // If the grant is no longer valid, disable the user and 76 - // delete local tokens. If the OAuth2 provider still 77 - // recognizes them as a valid user, they will be able to login 78 - // via their provider and reactivate their account. 79 - if shouldDisable { 80 - log.Info("SyncExternalUsers[%s] disabling user %d", source.authSource.Name, user.ID) 81 - 82 - return db.WithTx(ctx, func(ctx context.Context) error { 83 - if hasUser { 84 - user.IsActive = false 85 - err := user_model.UpdateUserCols(ctx, user, "is_active") 86 - if err != nil { 87 - return err 88 - } 89 - } 90 - 91 - // Delete stored tokens, since they are invalid. This 92 - // also provents us from checking this in subsequent runs. 93 - u.AccessToken = "" 94 - u.RefreshToken = "" 95 - u.ExpiresAt = time.Time{} 96 - 97 - return user_model.UpdateExternalUserByExternalID(ctx, u) 98 - }) 99 - } 100 - 101 - // Otherwise, update the tokens 102 - u.AccessToken = token.AccessToken 103 - u.ExpiresAt = token.Expiry 104 - 105 - // Some providers only update access tokens provide a new 106 - // refresh token, so avoid updating it if it's empty 107 - if token.RefreshToken != "" { 108 - u.RefreshToken = token.RefreshToken 109 - } 110 - 111 - err = user_model.UpdateExternalUserByExternalID(ctx, u) 112 - 113 - return err 114 - }
-101
services/auth/source/oauth2/source_sync_test.go
··· 1 - // Copyright 2024 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package oauth2 5 - 6 - import ( 7 - "context" 8 - "testing" 9 - 10 - "code.gitea.io/gitea/models/auth" 11 - "code.gitea.io/gitea/models/unittest" 12 - user_model "code.gitea.io/gitea/models/user" 13 - 14 - "github.com/stretchr/testify/assert" 15 - "github.com/stretchr/testify/require" 16 - ) 17 - 18 - func TestSource(t *testing.T) { 19 - require.NoError(t, unittest.PrepareTestDatabase()) 20 - 21 - source := &Source{ 22 - Provider: "fake", 23 - authSource: &auth.Source{ 24 - ID: 12, 25 - Type: auth.OAuth2, 26 - Name: "fake", 27 - IsActive: true, 28 - IsSyncEnabled: true, 29 - }, 30 - } 31 - 32 - user := &user_model.User{ 33 - LoginName: "external", 34 - LoginType: auth.OAuth2, 35 - LoginSource: source.authSource.ID, 36 - Name: "test", 37 - Email: "external@example.com", 38 - } 39 - 40 - err := user_model.CreateUser(context.Background(), user, &user_model.CreateUserOverwriteOptions{}) 41 - require.NoError(t, err) 42 - 43 - e := &user_model.ExternalLoginUser{ 44 - ExternalID: "external", 45 - UserID: user.ID, 46 - LoginSourceID: user.LoginSource, 47 - RefreshToken: "valid", 48 - } 49 - err = user_model.LinkExternalToUser(context.Background(), user, e) 50 - require.NoError(t, err) 51 - 52 - provider, err := createProvider(source.authSource.Name, source) 53 - require.NoError(t, err) 54 - 55 - t.Run("refresh", func(t *testing.T) { 56 - t.Run("valid", func(t *testing.T) { 57 - err := source.refresh(context.Background(), provider, e) 58 - require.NoError(t, err) 59 - 60 - e := &user_model.ExternalLoginUser{ 61 - ExternalID: e.ExternalID, 62 - LoginSourceID: e.LoginSourceID, 63 - } 64 - 65 - ok, err := user_model.GetExternalLogin(context.Background(), e) 66 - require.NoError(t, err) 67 - assert.True(t, ok) 68 - assert.Equal(t, "refresh", e.RefreshToken) 69 - assert.Equal(t, "token", e.AccessToken) 70 - 71 - u, err := user_model.GetUserByID(context.Background(), user.ID) 72 - require.NoError(t, err) 73 - assert.True(t, u.IsActive) 74 - }) 75 - 76 - t.Run("expired", func(t *testing.T) { 77 - err := source.refresh(context.Background(), provider, &user_model.ExternalLoginUser{ 78 - ExternalID: "external", 79 - UserID: user.ID, 80 - LoginSourceID: user.LoginSource, 81 - RefreshToken: "expired", 82 - }) 83 - require.NoError(t, err) 84 - 85 - e := &user_model.ExternalLoginUser{ 86 - ExternalID: e.ExternalID, 87 - LoginSourceID: e.LoginSourceID, 88 - } 89 - 90 - ok, err := user_model.GetExternalLogin(context.Background(), e) 91 - require.NoError(t, err) 92 - assert.True(t, ok) 93 - assert.Equal(t, "", e.RefreshToken) 94 - assert.Equal(t, "", e.AccessToken) 95 - 96 - u, err := user_model.GetUserByID(context.Background(), user.ID) 97 - require.NoError(t, err) 98 - assert.False(t, u.IsActive) 99 - }) 100 - }) 101 - }
+3 -3
services/externalaccount/user.go
··· 71 71 return nil 72 72 } 73 73 74 - // EnsureLinkExternalToUser link the gothUser to the user 75 - func EnsureLinkExternalToUser(ctx context.Context, user *user_model.User, gothUser goth.User) error { 74 + // UpdateExternalUser updates external user's information 75 + func UpdateExternalUser(ctx context.Context, user *user_model.User, gothUser goth.User) error { 76 76 externalLoginUser, err := toExternalLoginUser(ctx, user, gothUser) 77 77 if err != nil { 78 78 return err 79 79 } 80 80 81 - return user_model.EnsureLinkExternalToUser(ctx, externalLoginUser) 81 + return user_model.UpdateExternalUserByExternalID(ctx, externalLoginUser) 82 82 } 83 83 84 84 // UpdateMigrationsByType updates all migrated repositories' posterid from gitServiceType to replace originalAuthorID to posterID
+1 -1
templates/admin/auth/edit.tmpl
··· 416 416 <p class="help">{{ctx.Locale.Tr "admin.auths.sspi_default_language_helper"}}</p> 417 417 </div> 418 418 {{end}} 419 - {{if (or .Source.IsLDAP .Source.IsOAuth2)}} 419 + {{if .Source.IsLDAP}} 420 420 <div class="inline field"> 421 421 <div class="ui checkbox"> 422 422 <label><strong>{{ctx.Locale.Tr "admin.auths.syncenabled"}}</strong></label>
+1 -1
templates/admin/auth/new.tmpl
··· 59 59 <input name="attributes_in_bind" type="checkbox" {{if .attributes_in_bind}}checked{{end}}> 60 60 </div> 61 61 </div> 62 - <div class="oauth2 ldap inline field {{if not (or (eq .type 2) (eq .type 6))}}tw-hidden{{end}}"> 62 + <div class="ldap inline field {{if not (eq .type 2)}}tw-hidden{{end}}"> 63 63 <div class="ui checkbox"> 64 64 <label><strong>{{ctx.Locale.Tr "admin.auths.syncenabled"}}</strong></label> 65 65 <input name="is_sync_enabled" type="checkbox" {{if .is_sync_enabled}}checked{{end}}>
+1 -2
tests/integration/auth_ldap_test.go
··· 245 245 } 246 246 defer tests.PrepareTestEnv(t)() 247 247 addAuthSourceLDAP(t, "", "", "", "") 248 - err := auth.SyncExternalUsers(context.Background(), true) 249 - require.NoError(t, err) 248 + auth.SyncExternalUsers(context.Background(), true) 250 249 251 250 // Check if users exists 252 251 for _, gitLDAPUser := range gitLDAPUsers {