diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index e3c81809f8..aff2a12855 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -386,7 +386,7 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error { return a.createAuthSource(ctx, authSource) } -// updateLdapBindDn updates a new LDAP (simple auth) authentication source. +// updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source. func (a *authService) updateLdapSimpleAuth(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 24a1ccc6f4..3f0e9c447d 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -925,6 +925,24 @@ LEVEL = Info ;; Valid site url schemes for user profiles ;VALID_SITE_URL_SCHEMES=http,https +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[service.explore] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Only allow signed in users to view the explore pages. +;REQUIRE_SIGNIN_VIEW = false +;; +;; Disable the users explore page. +;DISABLE_USERS_PAGE = false +;; +;; Disable the organizations explore page. +;DISABLE_ORGANIZATIONS_PAGE = false +;; +;; Disable the code explore page. +;DISABLE_CODE_PAGE = false +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/models/issues/issue.go b/models/issues/issue.go index f7379b7e0c..13991da767 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -875,7 +875,7 @@ func GetPinnedIssues(ctx context.Context, repoID int64, isPull bool) (IssueList, return issues, nil } -// IsNewPinnedAllowed returns if a new Issue or Pull request can be pinned +// IsNewPinAllowed returns if a new Issue or Pull request can be pinned func IsNewPinAllowed(ctx context.Context, repoID int64, isPull bool) (bool, error) { var maxPin int _, err := db.GetEngine(ctx).SQL("SELECT COUNT(pin_order) FROM issue WHERE repo_id = ? AND is_pull = ? AND pin_order > 0", repoID, isPull).Get(&maxPin) diff --git a/models/issues/pull.go b/models/issues/pull.go index 45e2e19434..e7663ea350 100644 --- a/models/issues/pull.go +++ b/models/issues/pull.go @@ -689,7 +689,7 @@ func GetPullRequestByIssueID(ctx context.Context, issueID int64) (*PullRequest, return pr, pr.LoadAttributes(ctx) } -// GetPullRequestsByBaseHeadInfo returns the pull request by given base and head +// GetPullRequestByBaseHeadInfo returns the pull request by given base and head func GetPullRequestByBaseHeadInfo(ctx context.Context, baseID, headID int64, base, head string) (*PullRequest, error) { pr := &PullRequest{} sess := db.GetEngine(ctx). diff --git a/modules/setting/service.go b/modules/setting/service.go index afaee18101..128b8c7069 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -91,8 +91,10 @@ var Service = struct { // Explore page settings Explore struct { - RequireSigninView bool `ini:"REQUIRE_SIGNIN_VIEW"` - DisableUsersPage bool `ini:"DISABLE_USERS_PAGE"` + RequireSigninView bool `ini:"REQUIRE_SIGNIN_VIEW"` + DisableUsersPage bool `ini:"DISABLE_USERS_PAGE"` + DisableOrganizationsPage bool `ini:"DISABLE_ORGANIZATIONS_PAGE"` + DisableCodePage bool `ini:"DISABLE_CODE_PAGE"` } `ini:"service.explore"` }{ AllowedUserVisibilityModesSlice: []bool{true, true, true}, diff --git a/release-notes/5714.md b/release-notes/5714.md new file mode 100644 index 0000000000..968ad8bb51 --- /dev/null +++ b/release-notes/5714.md @@ -0,0 +1,3 @@ +fix: [commit](https://codeberg.org/forgejo/forgejo/commit/d13a4ab5632d6a9697bd0907f9c69ed57d949340) Fixed a bug related to disabling two-factor authentication +chore: [commit](https://codeberg.org/forgejo/forgejo/commit/ab26d880932dbc116c43ea277029984c7a6d4e94) Emit a log message when failing to delete an inactive user +feat: [commit](https://codeberg.org/forgejo/forgejo/commit/ab660c5944d59cdb4ecc071401445ac9f53cee45) Add `DISABLE_ORGANIZATIONS_PAGE` and `DISABLE_CODE_PAGE` settings for explore pages diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 6e4a97b885..b8f49a5bb6 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -395,12 +395,20 @@ func reqToken() func(ctx *context.APIContext) { func reqExploreSignIn() func(ctx *context.APIContext) { return func(ctx *context.APIContext) { - if setting.Service.Explore.RequireSigninView && !ctx.IsSigned { + if (setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView) && !ctx.IsSigned { ctx.Error(http.StatusUnauthorized, "reqExploreSignIn", "you must be signed in to search for users") } } } +func reqUsersExploreEnabled() func(ctx *context.APIContext) { + return func(ctx *context.APIContext) { + if setting.Service.Explore.DisableUsersPage { + ctx.NotFound() + } + } +} + func reqBasicOrRevProxyAuth() func(ctx *context.APIContext) { return func(ctx *context.APIContext) { if ctx.IsSigned && setting.Service.EnableReverseProxyAuthAPI && ctx.Data["AuthedMethod"].(string) == auth.ReverseProxyMethodName { @@ -887,7 +895,7 @@ func Routes() *web.Route { // Users (requires user scope) m.Group("/users", func() { - m.Get("/search", reqExploreSignIn(), user.Search) + m.Get("/search", reqExploreSignIn(), reqUsersExploreEnabled(), user.Search) m.Group("/{username}", func() { m.Get("", reqExploreSignIn(), user.GetInfo) diff --git a/routers/web/explore/code.go b/routers/web/explore/code.go index 437282cbb1..7992517ad4 100644 --- a/routers/web/explore/code.go +++ b/routers/web/explore/code.go @@ -21,12 +21,13 @@ const ( // Code render explore code page func Code(ctx *context.Context) { - if !setting.Indexer.RepoIndexerEnabled { + if !setting.Indexer.RepoIndexerEnabled || setting.Service.Explore.DisableCodePage { ctx.Redirect(setting.AppSubURL + "/explore") return } - ctx.Data["UsersIsDisabled"] = setting.Service.Explore.DisableUsersPage + ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage + ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["Title"] = ctx.Tr("explore") ctx.Data["PageIsExplore"] = true diff --git a/routers/web/explore/org.go b/routers/web/explore/org.go index f8fd6ec38e..7178630b64 100644 --- a/routers/web/explore/org.go +++ b/routers/web/explore/org.go @@ -14,7 +14,13 @@ import ( // Organizations render explore organizations page func Organizations(ctx *context.Context) { - ctx.Data["UsersIsDisabled"] = setting.Service.Explore.DisableUsersPage + if setting.Service.Explore.DisableOrganizationsPage { + ctx.Redirect(setting.AppSubURL + "/explore") + return + } + + ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage + ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage ctx.Data["Title"] = ctx.Tr("explore") ctx.Data["PageIsExplore"] = true ctx.Data["PageIsExploreOrganizations"] = true diff --git a/routers/web/explore/repo.go b/routers/web/explore/repo.go index 116b983b3a..798fdf5654 100644 --- a/routers/web/explore/repo.go +++ b/routers/web/explore/repo.go @@ -165,7 +165,9 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { // Repos render explore repositories page func Repos(ctx *context.Context) { - ctx.Data["UsersIsDisabled"] = setting.Service.Explore.DisableUsersPage + ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage + ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage + ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage ctx.Data["Title"] = ctx.Tr("explore") ctx.Data["PageIsExplore"] = true ctx.Data["PageIsExploreRepositories"] = true diff --git a/routers/web/explore/user.go b/routers/web/explore/user.go index b79a79fb2c..15c60f546f 100644 --- a/routers/web/explore/user.go +++ b/routers/web/explore/user.go @@ -131,9 +131,11 @@ func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions, // Users render explore users page func Users(ctx *context.Context) { if setting.Service.Explore.DisableUsersPage { - ctx.Redirect(setting.AppSubURL + "/explore/repos") + ctx.Redirect(setting.AppSubURL + "/explore") return } + ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage + ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage ctx.Data["Title"] = ctx.Tr("explore") ctx.Data["PageIsExplore"] = true ctx.Data["PageIsExploreUsers"] = true diff --git a/routers/web/user/search.go b/routers/web/user/search.go index fb7729bbe1..be5eee90a9 100644 --- a/routers/web/user/search.go +++ b/routers/web/user/search.go @@ -8,37 +8,24 @@ import ( "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/optional" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/convert" ) -// Search search users -func Search(ctx *context.Context) { - listOptions := db.ListOptions{ - Page: ctx.FormInt("page"), - PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), - } - - users, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{ +// SearchCandidates searches candidate users for dropdown list +func SearchCandidates(ctx *context.Context) { + users, _, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{ Actor: ctx.Doer, Keyword: ctx.FormTrim("q"), - UID: ctx.FormInt64("uid"), Type: user_model.UserTypeIndividual, - IsActive: ctx.FormOptionalBool("active"), - ListOptions: listOptions, + IsActive: optional.Some(true), + ListOptions: db.ListOptions{PageSize: setting.UI.MembersPagingNum}, }) if err != nil { - ctx.JSON(http.StatusInternalServerError, map[string]any{ - "ok": false, - "error": err.Error(), - }) + ctx.ServerError("Unable to search users", err) return } - - ctx.SetTotalCountHeader(maxResults) - - ctx.JSON(http.StatusOK, map[string]any{ - "ok": true, - "data": convert.ToUsers(ctx, ctx.Doer, users), - }) + ctx.JSON(http.StatusOK, map[string]any{"data": convert.ToUsers(ctx, ctx.Doer, users)}) } diff --git a/routers/web/user/setting/security/2fa.go b/routers/web/user/setting/security/2fa.go index a145867ea4..7c85c0e4b7 100644 --- a/routers/web/user/setting/security/2fa.go +++ b/routers/web/user/setting/security/2fa.go @@ -34,8 +34,9 @@ func RegenerateScratchTwoFactor(ctx *context.Context) { if auth.IsErrTwoFactorNotEnrolled(err) { ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled")) ctx.Redirect(setting.AppSubURL + "/user/settings/security") + } else { + ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err) } - ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err) return } @@ -64,8 +65,9 @@ func DisableTwoFactor(ctx *context.Context) { if auth.IsErrTwoFactorNotEnrolled(err) { ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled")) ctx.Redirect(setting.AppSubURL + "/user/settings/security") + } else { + ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err) } - ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err) return } @@ -74,8 +76,9 @@ func DisableTwoFactor(ctx *context.Context) { // There is a potential DB race here - we must have been disabled by another request in the intervening period ctx.Flash.Success(ctx.Tr("settings.twofa_disabled")) ctx.Redirect(setting.AppSubURL + "/user/settings/security") + } else { + ctx.ServerError("SettingsTwoFactor: Failed to DeleteTwoFactorByID", err) } - ctx.ServerError("SettingsTwoFactor: Failed to DeleteTwoFactorByID", err) return } diff --git a/routers/web/web.go b/routers/web/web.go index c268f7224d..34880bdda1 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -642,7 +642,7 @@ func registerRoutes(m *web.Route) { m.Post("/logout", auth.SignOut) m.Get("/task/{task}", reqSignIn, user.TaskStatus) m.Get("/stopwatches", reqSignIn, user.GetStopwatches) - m.Get("/search", ignExploreSignIn, user.Search) + m.Get("/search_candidates", ignExploreSignIn, user.SearchCandidates) m.Group("/oauth2", func() { m.Get("/{provider}", auth.SignInOAuth) m.Get("/{provider}/callback", auth.SignInOAuthCallback) diff --git a/services/user/user.go b/services/user/user.go index 606a4b2137..817c46e059 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -306,6 +306,7 @@ func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) error { // Ignore users that were set inactive by admin. if models.IsErrUserOwnRepos(err) || models.IsErrUserHasOrgs(err) || models.IsErrUserOwnPackages(err) || models.IsErrDeleteLastAdminUser(err) { + log.Warn("Inactive user %q has repositories, organizations or packages, skipping deletion: %v", u.Name, err) continue } return err diff --git a/templates/explore/navbar.tmpl b/templates/explore/navbar.tmpl index 8e619fa66f..c07c0ba4a4 100644 --- a/templates/explore/navbar.tmpl +++ b/templates/explore/navbar.tmpl @@ -3,15 +3,18 @@ {{svg "octicon-repo"}} {{ctx.Locale.Tr "explore.repos"}} - {{if not .UsersIsDisabled}} + {{if not .UsersPageIsDisabled}} {{svg "octicon-person"}} {{ctx.Locale.Tr "explore.users"}} {{end}} + {{if not .OrganizationsPageIsDisabled}} {{svg "octicon-organization"}} {{ctx.Locale.Tr "explore.organizations"}} - {{if and (not $.UnitTypeCode.UnitGlobalDisabled) .IsRepoIndexerEnabled}} + {{end}} + + {{if and (not $.UnitTypeCode.UnitGlobalDisabled) .IsRepoIndexerEnabled (not .CodePageIsDisabled)}} {{svg "octicon-code"}} {{ctx.Locale.Tr "explore.code"}} diff --git a/web_src/js/features/comp/SearchUserBox.js b/web_src/js/features/comp/SearchUserBox.js index 081c47425f..b03cbad16f 100644 --- a/web_src/js/features/comp/SearchUserBox.js +++ b/web_src/js/features/comp/SearchUserBox.js @@ -8,41 +8,38 @@ export function initCompSearchUserBox() { const searchUserBox = document.getElementById('search-user-box'); if (!searchUserBox) return; - const $searchUserBox = $(searchUserBox); const allowEmailInput = searchUserBox.getAttribute('data-allow-email') === 'true'; const allowEmailDescription = searchUserBox.getAttribute('data-allow-email-description') ?? undefined; - $searchUserBox.search({ + $(searchUserBox).search({ minCharacters: 2, apiSettings: { - url: `${appSubUrl}/user/search?active=1&q={query}`, + url: `${appSubUrl}/user/search_candidates?q={query}`, onResponse(response) { - const items = []; - const searchQuery = $searchUserBox.find('input').val(); + const resultItems = []; + const searchQuery = searchUserBox.querySelector('input').value; const searchQueryUppercase = searchQuery.toUpperCase(); - $.each(response.data, (_i, item) => { + for (const item of response.data) { const resultItem = { title: item.login, image: item.avatar_url, + description: htmlEscape(item.full_name), }; - if (item.full_name) { - resultItem.description = htmlEscape(item.full_name); - } if (searchQueryUppercase === item.login.toUpperCase()) { - items.unshift(resultItem); + resultItems.unshift(resultItem); // add the exact match to the top } else { - items.push(resultItem); + resultItems.push(resultItem); } - }); + } - if (allowEmailInput && !items.length && looksLikeEmailAddressCheck.test(searchQuery)) { + if (allowEmailInput && !resultItems.length && looksLikeEmailAddressCheck.test(searchQuery)) { const resultItem = { title: searchQuery, description: allowEmailDescription, }; - items.push(resultItem); + resultItems.push(resultItem); } - return {results: items}; + return {results: resultItems}; }, }, searchFields: ['login', 'full_name'],