mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-26 06:16:56 +09:00
refactor: username sub route (#39967)
Remove the workaround for chi router, do as much as possible in middlewares --------- Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: Giteabot <teabot@gitea.io>
This commit is contained in:
13 files changed
+103
-117
No files matched your search
+1
-1
@@ -660,7 +660,7 @@ var (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// These names are reserved for user accounts: user's keys, user's rss feed, user's avatar, etc.
|
// These names are reserved for user accounts: user's keys, user's rss feed, user's avatar, etc.
|
||||||
// DO NOT add any new stuff! The paths with these names are processed by `/{username}` handler (UsernameSubRoute) manually.
|
// HINT: OWNER-NAME-RESERVED-EXT: DO NOT add any new stuff! The paths with these names are processed by UsernamePreRoute handler manually.
|
||||||
reservedUserPatterns = []string{"*.keys", "*.gpg", "*.rss", "*.atom", "*.png"}
|
reservedUserPatterns = []string{"*.keys", "*.gpg", "*.rss", "*.atom", "*.png"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -260,18 +260,21 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
|
|||||||
return items, err
|
return items, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ExtNameRss = ".rss"
|
||||||
|
ExtNameAtom = ".atom"
|
||||||
|
ContentTypeRssXml = "application/rss+xml"
|
||||||
|
ContentTypeAtomXml = "application/atom+xml"
|
||||||
|
)
|
||||||
|
|
||||||
// GetFeedType return if it is a feed request and altered name and feed type.
|
// GetFeedType return if it is a feed request and altered name and feed type.
|
||||||
func GetFeedType(name string, req *http.Request) (showFeed bool, feedType string) {
|
func GetFeedType(name string, req *http.Request) (showFeed bool, feedType string) {
|
||||||
if strings.HasSuffix(name, ".rss") ||
|
if strings.HasSuffix(name, ExtNameRss) || strings.Contains(req.Header.Get("Accept"), ContentTypeRssXml) {
|
||||||
strings.Contains(req.Header.Get("Accept"), "application/rss+xml") {
|
|
||||||
return true, "rss"
|
return true, "rss"
|
||||||
}
|
}
|
||||||
|
if strings.HasSuffix(name, ExtNameAtom) || strings.Contains(req.Header.Get("Accept"), ContentTypeAtomXml) {
|
||||||
if strings.HasSuffix(name, ".atom") ||
|
|
||||||
strings.Contains(req.Header.Get("Accept"), "application/atom+xml") {
|
|
||||||
return true, "atom"
|
return true, "atom"
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, ""
|
return false, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
activities_model "gitea.dev/models/activities"
|
activities_model "gitea.dev/models/activities"
|
||||||
"gitea.dev/models/organization"
|
"gitea.dev/models/organization"
|
||||||
"gitea.dev/models/renderhelper"
|
"gitea.dev/models/renderhelper"
|
||||||
user_model "gitea.dev/models/user"
|
|
||||||
"gitea.dev/modules/markup/markdown"
|
"gitea.dev/modules/markup/markdown"
|
||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
"gitea.dev/services/context"
|
"gitea.dev/services/context"
|
||||||
@@ -30,16 +29,13 @@ func ShowUserFeedAtom(ctx *context.Context) {
|
|||||||
|
|
||||||
// showUserFeed show user activity as RSS / Atom feed
|
// showUserFeed show user activity as RSS / Atom feed
|
||||||
func showUserFeed(ctx *context.Context, formatType string) {
|
func showUserFeed(ctx *context.Context, formatType string) {
|
||||||
isOrganisation := ctx.ContextUser.IsOrganization()
|
if !setting.Other.EnableFeed {
|
||||||
if !setting.Other.EnableFeed ||
|
|
||||||
isOrganisation && !organization.HasOrgOrUserVisible(ctx, ctx.ContextUser, ctx.Doer) ||
|
|
||||||
!isOrganisation && !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) {
|
|
||||||
ctx.NotFound(nil)
|
ctx.NotFound(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
includePrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID)
|
includePrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID)
|
||||||
if ctx.IsSigned && isOrganisation && !includePrivate {
|
if ctx.IsSigned && ctx.ContextUser.IsOrganization() && !includePrivate {
|
||||||
// When feed is requested by a member of the organization,
|
// When feed is requested by a member of the organization,
|
||||||
// include the private repo's the member has access to.
|
// include the private repo's the member has access to.
|
||||||
isOrgMember, err := organization.IsOrganizationMember(ctx, ctx.ContextUser.ID, ctx.Doer.ID)
|
isOrgMember, err := organization.IsOrganizationMember(ctx, ctx.ContextUser.ID, ctx.Doer.ID)
|
||||||
@@ -54,7 +50,7 @@ func showUserFeed(ctx *context.Context, formatType string) {
|
|||||||
RequestedUser: ctx.ContextUser,
|
RequestedUser: ctx.ContextUser,
|
||||||
Actor: ctx.Doer,
|
Actor: ctx.Doer,
|
||||||
IncludePrivate: includePrivate,
|
IncludePrivate: includePrivate,
|
||||||
OnlyPerformedBy: !isOrganisation,
|
OnlyPerformedBy: !ctx.ContextUser.IsOrganization(),
|
||||||
IncludeDeleted: false,
|
IncludeDeleted: false,
|
||||||
Date: ctx.FormString("date"),
|
Date: ctx.FormString("date"),
|
||||||
})
|
})
|
||||||
@@ -90,12 +86,12 @@ func showUserFeed(ctx *context.Context, formatType string) {
|
|||||||
// writeFeed write a feeds.Feed as atom or rss to ctx.Resp
|
// writeFeed write a feeds.Feed as atom or rss to ctx.Resp
|
||||||
func writeFeed(ctx *context.Context, feed *feeds.Feed, formatType string) {
|
func writeFeed(ctx *context.Context, feed *feeds.Feed, formatType string) {
|
||||||
if formatType == "atom" {
|
if formatType == "atom" {
|
||||||
ctx.Resp.Header().Set("Content-Type", "application/atom+xml;charset=utf-8")
|
ctx.Resp.Header().Set("Content-Type", ContentTypeAtomXml+"; charset=utf-8")
|
||||||
if err := feed.WriteAtom(ctx.Resp); err != nil {
|
if err := feed.WriteAtom(ctx.Resp); err != nil {
|
||||||
ctx.ServerError("Render Atom failed", err)
|
ctx.ServerError("Render Atom failed", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ctx.Resp.Header().Set("Content-Type", "application/rss+xml;charset=utf-8")
|
ctx.Resp.Header().Set("Content-Type", ContentTypeRssXml+"; charset=utf-8")
|
||||||
if err := feed.WriteRss(ctx.Resp); err != nil {
|
if err := feed.WriteRss(ctx.Resp); err != nil {
|
||||||
ctx.ServerError("Render RSS failed", err)
|
ctx.ServerError("Render RSS failed", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,18 +96,14 @@ func goGetDefaultBranch(ctx *context.Context, repo *repo_model.Repository) strin
|
|||||||
if len(repo.DefaultBranch) == 0 {
|
if len(repo.DefaultBranch) == 0 {
|
||||||
return def
|
return def
|
||||||
}
|
}
|
||||||
if err := repo.LoadOwner(ctx); err != nil || repo.Owner == nil {
|
|
||||||
return def
|
|
||||||
}
|
|
||||||
// a token that was not granted repository read scope must not learn repository details, even when the
|
// a token that was not granted repository read scope must not learn repository details, even when the
|
||||||
// account behind it could read the repo through the web UI
|
// account behind it could read the repo through the web UI
|
||||||
if !goGetTokenCanReadRepo(ctx) {
|
if !goGetTokenCanReadRepo(ctx) {
|
||||||
return def
|
return def
|
||||||
}
|
}
|
||||||
// the caller must be able to read the code and see the owner: a limited/private owner hides its repos
|
// the caller must be able to read the code
|
||||||
// from anonymous/non-member callers even when the repo itself is public
|
|
||||||
perm, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
|
perm, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
|
||||||
if err != nil || !perm.CanRead(unit.TypeCode) || !user_model.IsUserVisibleToViewer(ctx, repo.Owner, ctx.Doer) {
|
if err != nil || !perm.CanRead(unit.TypeCode) {
|
||||||
return def
|
return def
|
||||||
}
|
}
|
||||||
return repo.DefaultBranch
|
return repo.DefaultBranch
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"gitea.dev/modules/sitemap"
|
"gitea.dev/modules/sitemap"
|
||||||
"gitea.dev/modules/structs"
|
"gitea.dev/modules/structs"
|
||||||
"gitea.dev/modules/templates"
|
"gitea.dev/modules/templates"
|
||||||
|
"gitea.dev/routers/web/feed"
|
||||||
"gitea.dev/routers/web/user"
|
"gitea.dev/routers/web/user"
|
||||||
"gitea.dev/services/context"
|
"gitea.dev/services/context"
|
||||||
)
|
)
|
||||||
@@ -24,6 +25,10 @@ import (
|
|||||||
const tplHome templates.TplName = "home"
|
const tplHome templates.TplName = "home"
|
||||||
|
|
||||||
func Home(ctx *context.Context) {
|
func Home(ctx *context.Context) {
|
||||||
|
if _, feedType := feed.GetFeedType(ctx.Req.URL.Path, ctx.Req); feedType != "" {
|
||||||
|
ctx.Resp.WriteHeader(http.StatusUnsupportedMediaType)
|
||||||
|
return
|
||||||
|
}
|
||||||
if ctx.IsSigned {
|
if ctx.IsSigned {
|
||||||
user.Dashboard(ctx)
|
user.Dashboard(ctx)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package org
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
"gitea.dev/models/organization"
|
"gitea.dev/models/organization"
|
||||||
@@ -25,14 +24,7 @@ const tplOrgHome templates.TplName = "org/home"
|
|||||||
|
|
||||||
// Home show organization home page
|
// Home show organization home page
|
||||||
func Home(ctx *context.Context) {
|
func Home(ctx *context.Context) {
|
||||||
uname := ctx.PathParam("username")
|
ctx.SetPathParam("org", ctx.PathParam("username"))
|
||||||
|
|
||||||
if strings.HasSuffix(uname, ".keys") || strings.HasSuffix(uname, ".gpg") {
|
|
||||||
ctx.NotFound(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.SetPathParam("org", uname)
|
|
||||||
context.OrgAssignment(context.OrgAssignmentOptions{})(ctx)
|
context.OrgAssignment(context.OrgAssignmentOptions{})(ctx)
|
||||||
if ctx.Written() {
|
if ctx.Written() {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
activities_model "gitea.dev/models/activities"
|
activities_model "gitea.dev/models/activities"
|
||||||
user_model "gitea.dev/models/user"
|
|
||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
"gitea.dev/services/context"
|
"gitea.dev/services/context"
|
||||||
)
|
)
|
||||||
@@ -52,12 +51,6 @@ func DashboardHeatmap(ctx *context.Context) {
|
|||||||
ctx.NotFound(nil)
|
ctx.NotFound(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ctx.Org.Organization == nil && !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) {
|
|
||||||
// this end point is mounted on different routes, so we need to
|
|
||||||
// check if the user is visible to the viewer when there is no organization context (no visibility check)
|
|
||||||
ctx.NotFound(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var data []*activities_model.UserHeatmapData
|
var data []*activities_model.UserHeatmapData
|
||||||
var err error
|
var err error
|
||||||
if ctx.Org.Organization == nil {
|
if ctx.Org.Organization == nil {
|
||||||
|
|||||||
+41
-42
@@ -6,8 +6,8 @@ package user
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -43,6 +43,7 @@ import (
|
|||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp"
|
"github.com/ProtonMail/go-crypto/openpgp"
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -702,52 +703,50 @@ func ShowGPGKeys(ctx *context.Context) {
|
|||||||
ctx.PlainTextBytes(http.StatusOK, buf.Bytes())
|
ctx.PlainTextBytes(http.StatusOK, buf.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
func UsernameSubRoute(ctx *context.Context) {
|
// UsernamePreRoute rewrites the route path if the username has a suffix, so that the correct handler can be called.
|
||||||
// WORKAROUND to support usernames with "." in it
|
func UsernamePreRoute(next http.Handler) http.Handler {
|
||||||
// https://github.com/go-chi/chi/issues/781
|
// HINT: OWNER-NAME-RESERVED-EXT: use the reserved extensions to check and rewrite requests
|
||||||
username := ctx.PathParam("username")
|
suffixes := container.SetOf(".png", ".keys", ".gpg", feed.ExtNameRss, feed.ExtNameAtom)
|
||||||
reloadParam := func(suffix string) (success bool) {
|
rewriteRoutePath := func(rctx *chi.Context, r *http.Request) (ownerPath, rewrittenExt string) {
|
||||||
ctx.SetPathParam("username", strings.TrimSuffix(username, suffix))
|
if rctx.RouteMethod != "HEAD" && rctx.RouteMethod != "GET" {
|
||||||
context.UserAssignmentWeb()(ctx)
|
return "", ""
|
||||||
if ctx.Written() {
|
}
|
||||||
return false
|
// if there is no slash in the path, then it is possible to be a username or a username with a suffix
|
||||||
|
possibleUsernameWithSuffix := rctx.RoutePath != "" && strings.IndexByte(rctx.RoutePath[1:], '/') == -1
|
||||||
|
if !possibleUsernameWithSuffix {
|
||||||
|
return "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// check view permissions
|
// HINT: USERNAME-AVATAR-APPLE-TOUCH-ICON: skip rewriting the path if it is an apple touch icon request
|
||||||
if !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) {
|
isAppleTouchIcon := rctx.RoutePath == "/apple-touch-icon.png" || rctx.RoutePath == "/apple-touch-icon-precomposed.png"
|
||||||
ctx.NotFound(fmt.Errorf("%s", ctx.ContextUser.Name))
|
if isAppleTouchIcon {
|
||||||
return false
|
return "", ""
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
|
// try to extract the suffix from the path, if it is a valid suffix, then rewrite the path to "/-/ownername.png/{username}"
|
||||||
|
ext := path.Ext(rctx.RoutePath)
|
||||||
|
if suffixes.Contains(ext) {
|
||||||
|
return rctx.RoutePath[:len(rctx.RoutePath)-len(ext)], ext
|
||||||
|
}
|
||||||
|
// also try to rewrite "/username" to "/-/ownername.rss/{username}" if the Accept header is for RSS or Atom
|
||||||
|
if strings.Contains(r.Header.Get("Accept"), feed.ContentTypeRssXml) {
|
||||||
|
return rctx.RoutePath, feed.ExtNameRss
|
||||||
|
} else if strings.Contains(r.Header.Get("Accept"), feed.ContentTypeAtomXml) {
|
||||||
|
return rctx.RoutePath, feed.ExtNameAtom
|
||||||
|
}
|
||||||
|
return "", ""
|
||||||
}
|
}
|
||||||
switch {
|
|
||||||
case strings.HasSuffix(username, ".png"):
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if reloadParam(".png") {
|
rctx := chi.RouteContext(r.Context())
|
||||||
AvatarByUsernameSize(ctx)
|
if ownerPath, rewrittenExt := rewriteRoutePath(rctx, r); ownerPath != "/" && rewrittenExt != "" {
|
||||||
|
// Owner path can be something like "/" (site home) or "/explore" (endpoint of reserved username)
|
||||||
|
// It's safe to pass the owner path to the handler as is and just let the handler respond 4xx.
|
||||||
|
// Because the server doesn't respond avatar, keys or feeds for these endpoints.
|
||||||
|
rctx.RoutePath = "/-/ownername" + rewrittenExt + ownerPath
|
||||||
}
|
}
|
||||||
case strings.HasSuffix(username, ".keys"):
|
next.ServeHTTP(w, r)
|
||||||
if reloadParam(".keys") {
|
})
|
||||||
ShowSSHKeys(ctx)
|
|
||||||
}
|
|
||||||
case strings.HasSuffix(username, ".gpg"):
|
|
||||||
if reloadParam(".gpg") {
|
|
||||||
ShowGPGKeys(ctx)
|
|
||||||
}
|
|
||||||
case strings.HasSuffix(username, ".rss"):
|
|
||||||
if reloadParam(".rss") {
|
|
||||||
feed.ShowUserFeedRSS(ctx)
|
|
||||||
}
|
|
||||||
case strings.HasSuffix(username, ".atom"):
|
|
||||||
if reloadParam(".atom") {
|
|
||||||
feed.ShowUserFeedAtom(ctx)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
context.UserAssignmentWeb()(ctx)
|
|
||||||
if !ctx.Written() {
|
|
||||||
ctx.Data["EnableFeed"] = setting.Other.EnableFeed
|
|
||||||
OwnerProfile(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUserIssueStats(ctx *context.Context, ctxUser *user_model.User, filterMode int, opts *issue_indexer.SearchOptions) (ret *issues_model.IssueStats, err error) {
|
func getUserIssueStats(ctx *context.Context, ctxUser *user_model.User, filterMode int, opts *issue_indexer.SearchOptions) (ret *issues_model.IssueStats, err error) {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ package user
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
activities_model "gitea.dev/models/activities"
|
activities_model "gitea.dev/models/activities"
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
@@ -21,7 +20,6 @@ import (
|
|||||||
"gitea.dev/modules/optional"
|
"gitea.dev/modules/optional"
|
||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
"gitea.dev/modules/templates"
|
"gitea.dev/modules/templates"
|
||||||
"gitea.dev/routers/web/feed"
|
|
||||||
"gitea.dev/routers/web/org"
|
"gitea.dev/routers/web/org"
|
||||||
shared_user "gitea.dev/routers/web/shared/user"
|
shared_user "gitea.dev/routers/web/shared/user"
|
||||||
"gitea.dev/services/context"
|
"gitea.dev/services/context"
|
||||||
@@ -35,15 +33,7 @@ const (
|
|||||||
|
|
||||||
// OwnerProfile render profile page for a user or a organization (aka, repo owner)
|
// OwnerProfile render profile page for a user or a organization (aka, repo owner)
|
||||||
func OwnerProfile(ctx *context.Context) {
|
func OwnerProfile(ctx *context.Context) {
|
||||||
if strings.Contains(ctx.Req.Header.Get("Accept"), "application/rss+xml") {
|
ctx.Data["EnableFeed"] = setting.Other.EnableFeed
|
||||||
feed.ShowUserFeedRSS(ctx)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if strings.Contains(ctx.Req.Header.Get("Accept"), "application/atom+xml") {
|
|
||||||
feed.ShowUserFeedAtom(ctx)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.ContextUser.IsOrganization() {
|
if ctx.ContextUser.IsOrganization() {
|
||||||
org.Home(ctx)
|
org.Home(ctx)
|
||||||
} else {
|
} else {
|
||||||
@@ -52,12 +42,6 @@ func OwnerProfile(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func userProfile(ctx *context.Context) {
|
func userProfile(ctx *context.Context) {
|
||||||
// check view permissions
|
|
||||||
if !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) {
|
|
||||||
ctx.NotFound(fmt.Errorf("%s", ctx.ContextUser.Name))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Data["Title"] = ctx.ContextUser.DisplayName()
|
ctx.Data["Title"] = ctx.ContextUser.DisplayName()
|
||||||
ctx.Data["PageIsUserProfile"] = true
|
ctx.Data["PageIsUserProfile"] = true
|
||||||
|
|
||||||
@@ -322,13 +306,6 @@ func prepareUserProfileTabData(ctx *context.Context, profileDbRepo *repo_model.R
|
|||||||
|
|
||||||
// ActionUserFollow is for follow/unfollow user request
|
// ActionUserFollow is for follow/unfollow user request
|
||||||
func ActionUserFollow(ctx *context.Context) {
|
func ActionUserFollow(ctx *context.Context) {
|
||||||
isOrg := ctx.ContextUser.IsOrganization()
|
|
||||||
if isOrg && !organization.HasOrgOrUserVisible(ctx, ctx.ContextUser, ctx.Doer) ||
|
|
||||||
!isOrg && !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) {
|
|
||||||
ctx.NotFound(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
switch ctx.FormString("action") {
|
switch ctx.FormString("action") {
|
||||||
case "follow":
|
case "follow":
|
||||||
|
|||||||
+19
-10
@@ -258,15 +258,18 @@ func Routes() *web.Router {
|
|||||||
|
|
||||||
// GetHead allows a HEAD request redirect to GET if HEAD method is not defined for that route
|
// GetHead allows a HEAD request redirect to GET if HEAD method is not defined for that route
|
||||||
routes.BeforeRouting(chi_middleware.GetHead)
|
routes.BeforeRouting(chi_middleware.GetHead)
|
||||||
|
routes.BeforeRouting(user.UsernamePreRoute)
|
||||||
|
|
||||||
routes.Head("/", misc.DummyOK) // for health check - doesn't need to be passed through gzip handler
|
routes.Head("/", misc.DummyOK) // for health check - doesn't need to be passed through gzip handler
|
||||||
routes.Methods("GET, HEAD", "/assets/site-manifest.json", misc.SiteManifest)
|
routes.Methods("GET, HEAD", "/assets/site-manifest.json", misc.SiteManifest)
|
||||||
routes.Methods("GET, HEAD, OPTIONS", "/assets/*", routing.MarkLogLevelTrace, public.AssetsCors(), public.FileHandlerFunc())
|
routes.Methods("GET, HEAD, OPTIONS", "/assets/*", routing.MarkLogLevelTrace, public.AssetsCors(), public.FileHandlerFunc())
|
||||||
routes.Methods("GET, HEAD", "/avatars/*", avatarStorageHandler(setting.Avatar.Storage, "avatars", storage.Avatars))
|
routes.Methods("GET, HEAD", "/avatars/*", avatarStorageHandler(setting.Avatar.Storage, "avatars", storage.Avatars))
|
||||||
routes.Methods("GET, HEAD", "/repo-avatars/*", avatarStorageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars))
|
routes.Methods("GET, HEAD", "/repo-avatars/*", avatarStorageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars))
|
||||||
|
routes.Methods("GET, HEAD", "/favicon.ico", misc.StaticRedirect("/assets/img/favicon.png"))
|
||||||
|
|
||||||
|
// HINT: USERNAME-AVATAR-APPLE-TOUCH-ICON: these two paths conflict with "/{username}.png"
|
||||||
routes.Methods("GET, HEAD", "/apple-touch-icon.png", misc.StaticRedirect("/assets/img/apple-touch-icon.png"))
|
routes.Methods("GET, HEAD", "/apple-touch-icon.png", misc.StaticRedirect("/assets/img/apple-touch-icon.png"))
|
||||||
routes.Methods("GET, HEAD", "/apple-touch-icon-precomposed.png", misc.StaticRedirect("/assets/img/apple-touch-icon.png"))
|
routes.Methods("GET, HEAD", "/apple-touch-icon-precomposed.png", misc.StaticRedirect("/assets/img/apple-touch-icon.png"))
|
||||||
routes.Methods("GET, HEAD", "/favicon.ico", misc.StaticRedirect("/assets/img/favicon.png"))
|
|
||||||
|
|
||||||
var mid []any
|
var mid []any
|
||||||
|
|
||||||
@@ -906,13 +909,19 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
|||||||
}, adminReq, ctxDataSet(reqctx.ContextData{"EnableOAuth2": setting.OAuth2.Enabled, "EnablePackages": setting.Packages.Enabled}))
|
}, adminReq, ctxDataSet(reqctx.ContextData{"EnableOAuth2": setting.OAuth2.Enabled, "EnablePackages": setting.Packages.Enabled}))
|
||||||
// ***** END: Admin *****
|
// ***** END: Admin *****
|
||||||
|
|
||||||
m.Group("", func() {
|
m.Methods("GET, OPTIONS", "/attachments/{uuid}", optionsCorsHandler(), webAuth.AllowBasic, webAuth.AllowOAuth2, optSignIn, repo.GetAttachment)
|
||||||
// it handles "username.rss" in the handler, so allow basic auth as other rss/atom routes
|
|
||||||
m.Get("/{username}", webAuth.AllowBasic, user.UsernameSubRoute)
|
|
||||||
m.Methods("GET, OPTIONS", "/attachments/{uuid}", optionsCorsHandler(), webAuth.AllowBasic, webAuth.AllowOAuth2, repo.GetAttachment)
|
|
||||||
}, optSignIn)
|
|
||||||
|
|
||||||
m.Post("/{username}", reqSignIn, context.UserAssignmentWeb(), user.ActionUserFollow)
|
m.Group("", func() {
|
||||||
|
// "UsernamePreRoute" is used to rewrite username with a suffix like "/username.png" to "/-/ownername.png/username"
|
||||||
|
m.Get("/-/ownername.png/{username}", user.AvatarByUsernameSize)
|
||||||
|
m.Get("/-/ownername.keys/{username}", user.ShowSSHKeys)
|
||||||
|
m.Get("/-/ownername.gpg/{username}", user.ShowGPGKeys)
|
||||||
|
m.Get("/-/ownername.rss/{username}", feed.ShowUserFeedRSS)
|
||||||
|
m.Get("/-/ownername.atom/{username}", feed.ShowUserFeedAtom)
|
||||||
|
m.Get("/{username}", user.OwnerProfile)
|
||||||
|
}, webAuth.AllowBasic, optSignIn, context.UserAssignmentWeb(), context.ReqContextUserVisibleToDoer())
|
||||||
|
|
||||||
|
m.Post("/{username}", reqSignIn, context.UserAssignmentWeb(), context.ReqContextUserVisibleToDoer(), user.ActionUserFollow)
|
||||||
|
|
||||||
reqRepoAdmin := context.RequireRepoAdmin()
|
reqRepoAdmin := context.RequireRepoAdmin()
|
||||||
reqRepoCodeWriter := context.RequireUnitWriter(unit.TypeCode)
|
reqRepoCodeWriter := context.RequireUnitWriter(unit.TypeCode)
|
||||||
@@ -981,8 +990,8 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
|||||||
m.Group("/{org}", func() {
|
m.Group("/{org}", func() {
|
||||||
m.Get("/dashboard", user.Dashboard)
|
m.Get("/dashboard", user.Dashboard)
|
||||||
m.Get("/dashboard/{team}", user.Dashboard)
|
m.Get("/dashboard/{team}", user.Dashboard)
|
||||||
m.Get("/dashboard/-/heatmap", user.DashboardHeatmap)
|
m.Get("/dashboard/-/heatmap", context.ReqContextUserVisibleToDoer(), user.DashboardHeatmap)
|
||||||
m.Get("/dashboard/-/heatmap/{team}", user.DashboardHeatmap)
|
m.Get("/dashboard/-/heatmap/{team}", context.ReqContextUserVisibleToDoer(), user.DashboardHeatmap)
|
||||||
m.Get("/issues", user.Issues)
|
m.Get("/issues", user.Issues)
|
||||||
m.Get("/issues/{team}", user.Issues)
|
m.Get("/issues/{team}", user.Issues)
|
||||||
m.Get("/pulls", user.Pulls)
|
m.Get("/pulls", user.Pulls)
|
||||||
@@ -1132,7 +1141,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
|||||||
m.Get("/mentions-in-owner", reqAnyRepoUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true), org.GetMentionsInOwner)
|
m.Get("/mentions-in-owner", reqAnyRepoUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true), org.GetMentionsInOwner)
|
||||||
|
|
||||||
m.Get("/repositories", org.Repositories)
|
m.Get("/repositories", org.Repositories)
|
||||||
m.Get("/heatmap", user.DashboardHeatmap)
|
m.Get("/heatmap", context.ReqContextUserVisibleToDoer(), user.DashboardHeatmap)
|
||||||
|
|
||||||
m.Group("/projects", func() {
|
m.Group("/projects", func() {
|
||||||
m.Group("", func() {
|
m.Group("", func() {
|
||||||
|
|||||||
@@ -8,10 +8,22 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"gitea.dev/models/organization"
|
||||||
user_model "gitea.dev/models/user"
|
user_model "gitea.dev/models/user"
|
||||||
"gitea.dev/modules/session"
|
"gitea.dev/modules/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ReqContextUserVisibleToDoer() func(ctx *Context) {
|
||||||
|
return func(ctx *Context) {
|
||||||
|
isOrg := ctx.ContextUser.IsOrganization()
|
||||||
|
if isOrg && !organization.HasOrgOrUserVisible(ctx, ctx.ContextUser, ctx.Doer) ||
|
||||||
|
!isOrg && !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) {
|
||||||
|
ctx.NotFound(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UserAssignmentWeb returns a middleware to handle context-user assignment for web routes
|
// UserAssignmentWeb returns a middleware to handle context-user assignment for web routes
|
||||||
func UserAssignmentWeb() func(ctx *Context) {
|
func UserAssignmentWeb() func(ctx *Context) {
|
||||||
return func(ctx *Context) {
|
return func(ctx *Context) {
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ func testViewLimitedAndPrivateUserAndRename(t *testing.T) {
|
|||||||
req = NewRequest(t, "GET", "/"+user31.Name)
|
req = NewRequest(t, "GET", "/"+user31.Name)
|
||||||
MakeRequest(t, req, http.StatusNotFound)
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
MakeRequest(t, NewRequest(t, "GET", "/"+user31.Name).SetHeader("Accept", "application/rss+xml"), http.StatusNotFound)
|
MakeRequest(t, NewRequest(t, "GET", "/"+user31.Name).SetHeader("Accept", "application/rss+xml"), http.StatusNotFound)
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", "/"+user31.Name+".keys"), http.StatusNotFound)
|
||||||
strangerSession.MakeRequest(t, NewRequest(t, "POST", "/"+user31.Name+"?action=follow"), http.StatusNotFound)
|
strangerSession.MakeRequest(t, NewRequest(t, "POST", "/"+user31.Name+"?action=follow"), http.StatusNotFound)
|
||||||
|
|
||||||
oldName = user31.Name
|
oldName = user31.Name
|
||||||
@@ -237,6 +238,7 @@ func testRenameReservedUsername(t *testing.T) {
|
|||||||
|
|
||||||
func testExportUserGPGKeys(t *testing.T) {
|
func testExportUserGPGKeys(t *testing.T) {
|
||||||
testExportUserGPGKeys := func(t *testing.T, user, expected string) {
|
testExportUserGPGKeys := func(t *testing.T, user, expected string) {
|
||||||
|
t.Helper()
|
||||||
session := loginUser(t, user)
|
session := loginUser(t, user)
|
||||||
t.Logf("Testing username %s export gpg keys", user)
|
t.Logf("Testing username %s export gpg keys", user)
|
||||||
req := NewRequest(t, "GET", "/"+user+".gpg")
|
req := NewRequest(t, "GET", "/"+user+".gpg")
|
||||||
@@ -324,7 +326,7 @@ func testGetUserRss(t *testing.T) {
|
|||||||
user34 := "the_34-user.with.all.allowedChars"
|
user34 := "the_34-user.with.all.allowedChars"
|
||||||
req := NewRequestf(t, "GET", "/%s.rss", user34)
|
req := NewRequestf(t, "GET", "/%s.rss", user34)
|
||||||
resp := MakeRequest(t, req, http.StatusOK)
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
if assert.Equal(t, "application/rss+xml;charset=utf-8", resp.Header().Get("Content-Type")) {
|
if assert.Equal(t, "application/rss+xml; charset=utf-8", resp.Header().Get("Content-Type")) {
|
||||||
rssDoc := NewHTMLParser(t, resp.Body).Find("channel")
|
rssDoc := NewHTMLParser(t, resp.Body).Find("channel")
|
||||||
title, _ := rssDoc.ChildrenFiltered("title").Html()
|
title, _ := rssDoc.ChildrenFiltered("title").Html()
|
||||||
assert.Equal(t, "Feed of "the_1-user.with.all.allowedChars"", title)
|
assert.Equal(t, "Feed of "the_1-user.with.all.allowedChars"", title)
|
||||||
@@ -339,6 +341,8 @@ func testGetUserRss(t *testing.T) {
|
|||||||
req = NewRequestf(t, "GET", "/non-existent-user.rss")
|
req = NewRequestf(t, "GET", "/non-existent-user.rss")
|
||||||
session.MakeRequest(t, req, http.StatusNotFound)
|
session.MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", "/").SetHeader("Accept", "application/rss+xml"), http.StatusUnsupportedMediaType)
|
||||||
|
|
||||||
defer test.MockVariableValue(&setting.Other.EnableFeed, false)()
|
defer test.MockVariableValue(&setting.Other.EnableFeed, false)()
|
||||||
MakeRequest(t, NewRequestf(t, "GET", "/%s.rss", user34), http.StatusNotFound)
|
MakeRequest(t, NewRequestf(t, "GET", "/%s.rss", user34), http.StatusNotFound)
|
||||||
MakeRequest(t, NewRequestf(t, "GET", "/%s", user34).SetHeader("Accept", "application/rss+xml"), http.StatusNotFound)
|
MakeRequest(t, NewRequestf(t, "GET", "/%s", user34).SetHeader("Accept", "application/rss+xml"), http.StatusNotFound)
|
||||||
|
|||||||
@@ -126,6 +126,6 @@ func testViewPageCurrentURL(t *testing.T) {
|
|||||||
currentURL, _ = ctx.Data["CurrentURL"].(string)
|
currentURL, _ = ctx.Data["CurrentURL"].(string)
|
||||||
})
|
})
|
||||||
defer web.RouteMockReset()
|
defer web.RouteMockReset()
|
||||||
MakeRequest(t, NewRequest(t, "GET", "/any-page?k=v"), http.StatusNotFound)
|
MakeRequest(t, NewRequest(t, "GET", "/user2?k=v"), http.StatusOK)
|
||||||
assert.Equal(t, "/subpath/any-page?k=v", currentURL)
|
assert.Equal(t, "/subpath/user2?k=v", currentURL)
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user