diff options
-rw-r--r-- | internal/app/api.go | 8 | ||||
-rw-r--r-- | internal/app/metrics.go | 12 | ||||
-rw-r--r-- | internal/cmd/web.go | 902 | ||||
-rw-r--r-- | internal/context/auth.go | 17 | ||||
-rw-r--r-- | internal/db/access.go | 2 | ||||
-rw-r--r-- | internal/route/home.go | 11 | ||||
-rw-r--r-- | internal/route/lfs/basic.go | 8 | ||||
-rw-r--r-- | internal/route/lfs/batch.go | 4 | ||||
-rw-r--r-- | internal/route/lfs/route.go | 13 | ||||
-rw-r--r-- | internal/route/repo/http.go | 56 |
10 files changed, 515 insertions, 518 deletions
diff --git a/internal/app/api.go b/internal/app/api.go index c64e946e..94c2bbc0 100644 --- a/internal/app/api.go +++ b/internal/app/api.go @@ -9,8 +9,6 @@ import ( "github.com/microcosm-cc/bluemonday" "gopkg.in/macaron.v1" - - "gogs.io/gogs/internal/context" ) func ipynbSanitizer() *bluemonday.Policy { @@ -24,13 +22,13 @@ func ipynbSanitizer() *bluemonday.Policy { func SanitizeIpynb() macaron.Handler { p := ipynbSanitizer() - return func(c *context.Context) { + return func(c *macaron.Context) { html, err := c.Req.Body().String() if err != nil { - c.Error(err, "read body") + c.Error(http.StatusInternalServerError, "read body") return } - c.PlainText(http.StatusOK, p.Sanitize(html)) + c.PlainText(http.StatusOK, []byte(p.Sanitize(html))) } } diff --git a/internal/app/metrics.go b/internal/app/metrics.go index 80ff32f6..45a9b74e 100644 --- a/internal/app/metrics.go +++ b/internal/app/metrics.go @@ -9,14 +9,14 @@ import ( "gopkg.in/macaron.v1" + "gogs.io/gogs/internal/authutil" "gogs.io/gogs/internal/conf" - "gogs.io/gogs/internal/context" ) func MetricsFilter() macaron.Handler { - return func(c *context.Context) { + return func(w http.ResponseWriter, r *http.Request) { if !conf.Prometheus.Enabled { - c.Status(http.StatusNotFound) + w.WriteHeader(http.StatusNotFound) return } @@ -24,6 +24,10 @@ func MetricsFilter() macaron.Handler { return } - c.RequireBasicAuth(conf.Prometheus.BasicAuthUsername, conf.Prometheus.BasicAuthPassword) + username, password := authutil.DecodeBasic(r.Header) + if username != conf.Prometheus.BasicAuthUsername || password != conf.Prometheus.BasicAuthPassword { + w.WriteHeader(http.StatusForbidden) + return + } } } diff --git a/internal/cmd/web.go b/internal/cmd/web.go index 24f586dd..99b8d195 100644 --- a/internal/cmd/web.go +++ b/internal/cmd/web.go @@ -144,25 +144,6 @@ func newMacaron() *macaron.Macaron { m.Use(captcha.Captchaer(captcha.Options{ SubURL: conf.Server.Subpath, })) - m.Use(session.Sessioner(session.Options{ - Provider: conf.Session.Provider, - ProviderConfig: conf.Session.ProviderConfig, - CookieName: conf.Session.CookieName, - CookiePath: conf.Server.Subpath, - Gclifetime: conf.Session.GCInterval, - Maxlifetime: conf.Session.MaxLifeTime, - Secure: conf.Session.CookieSecure, - })) - m.Use(csrf.Csrfer(csrf.Options{ - Secret: conf.Security.SecretKey, - Header: "X-CSRF-Token", - Cookie: conf.Session.CSRFCookieName, - CookieDomain: conf.Server.URL.Hostname(), - CookiePath: conf.Server.Subpath, - CookieHttpOnly: true, - SetCookie: true, - Secure: conf.Server.URL.Scheme == "https", - })) m.Use(toolbox.Toolboxer(m, toolbox.Options{ HealthCheckFuncs: []*toolbox.HealthCheckFuncDesc{ { @@ -171,7 +152,6 @@ func newMacaron() *macaron.Macaron { }, }, })) - m.Use(context.Contexter()) return m } @@ -185,488 +165,506 @@ func runWeb(c *cli.Context) error { reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true}) ignSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: conf.Auth.RequireSigninView}) - ignSignInAndCsrf := context.Toggle(&context.ToggleOptions{DisableCSRF: true}) reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true}) bindIgnErr := binding.BindIgnErr m.SetAutoHead(true) - // FIXME: not all route need go through same middlewares. - // Especially some AJAX requests, we can reduce middleware number to improve performance. - // Routers. - m.Get("/", ignSignIn, route.Home) - m.Group("/explore", func() { - m.Get("", func(c *context.Context) { - c.Redirect(conf.Server.Subpath + "/explore/repos") - }) - m.Get("/repos", route.ExploreRepos) - m.Get("/users", route.ExploreUsers) - m.Get("/organizations", route.ExploreOrganizations) - }, ignSignIn) - m.Combo("/install", route.InstallInit).Get(route.Install). - Post(bindIgnErr(form.Install{}), route.InstallPost) - m.Get("/^:type(issues|pulls)$", reqSignIn, user.Issues) - - // ***** START: User ***** - m.Group("/user", func() { - m.Group("/login", func() { - m.Combo("").Get(user.Login). - Post(bindIgnErr(form.SignIn{}), user.LoginPost) - m.Combo("/two_factor").Get(user.LoginTwoFactor).Post(user.LoginTwoFactorPost) - m.Combo("/two_factor_recovery_code").Get(user.LoginTwoFactorRecoveryCode).Post(user.LoginTwoFactorRecoveryCodePost) - }) - - m.Get("/sign_up", user.SignUp) - m.Post("/sign_up", bindIgnErr(form.Register{}), user.SignUpPost) - m.Get("/reset_password", user.ResetPasswd) - m.Post("/reset_password", user.ResetPasswdPost) - }, reqSignOut) - - m.Group("/user/settings", func() { - m.Get("", user.Settings) - m.Post("", bindIgnErr(form.UpdateProfile{}), user.SettingsPost) - m.Combo("/avatar").Get(user.SettingsAvatar). - Post(binding.MultipartForm(form.Avatar{}), user.SettingsAvatarPost) - m.Post("/avatar/delete", user.SettingsDeleteAvatar) - m.Combo("/email").Get(user.SettingsEmails). - Post(bindIgnErr(form.AddEmail{}), user.SettingsEmailPost) - m.Post("/email/delete", user.DeleteEmail) - m.Get("/password", user.SettingsPassword) - m.Post("/password", bindIgnErr(form.ChangePassword{}), user.SettingsPasswordPost) - m.Combo("/ssh").Get(user.SettingsSSHKeys). - Post(bindIgnErr(form.AddSSHKey{}), user.SettingsSSHKeysPost) - m.Post("/ssh/delete", user.DeleteSSHKey) - m.Group("/security", func() { - m.Get("", user.SettingsSecurity) - m.Combo("/two_factor_enable").Get(user.SettingsTwoFactorEnable). - Post(user.SettingsTwoFactorEnablePost) - m.Combo("/two_factor_recovery_codes").Get(user.SettingsTwoFactorRecoveryCodes). - Post(user.SettingsTwoFactorRecoveryCodesPost) - m.Post("/two_factor_disable", user.SettingsTwoFactorDisable) - }) - m.Group("/repositories", func() { - m.Get("", user.SettingsRepos) - m.Post("/leave", user.SettingsLeaveRepo) - }) - m.Group("/organizations", func() { - m.Get("", user.SettingsOrganizations) - m.Post("/leave", user.SettingsLeaveOrganization) - }) - m.Combo("/applications").Get(user.SettingsApplications). - Post(bindIgnErr(form.NewAccessToken{}), user.SettingsApplicationsPost) - m.Post("/applications/delete", user.SettingsDeleteApplication) - m.Route("/delete", "GET,POST", user.SettingsDelete) - }, reqSignIn, func(c *context.Context) { - c.Data["PageIsUserSettings"] = true - }) - - m.Group("/user", func() { - m.Any("/activate", user.Activate) - m.Any("/activate_email", user.ActivateEmail) - m.Get("/email2user", user.Email2User) - m.Get("/forget_password", user.ForgotPasswd) - m.Post("/forget_password", user.ForgotPasswdPost) - m.Post("/logout", user.SignOut) - }) - // ***** END: User ***** - - reqAdmin := context.Toggle(&context.ToggleOptions{SignInRequired: true, AdminRequired: true}) - - // ***** START: Admin ***** - m.Group("/admin", func() { - m.Combo("").Get(admin.Dashboard).Post(admin.Operation) // "/admin" - m.Get("/config", admin.Config) - m.Post("/config/test_mail", admin.SendTestMail) - m.Get("/monitor", admin.Monitor) - - m.Group("/users", func() { - m.Get("", admin.Users) - m.Combo("/new").Get(admin.NewUser).Post(bindIgnErr(form.AdminCrateUser{}), admin.NewUserPost) - m.Combo("/:userid").Get(admin.EditUser).Post(bindIgnErr(form.AdminEditUser{}), admin.EditUserPost) - m.Post("/:userid/delete", admin.DeleteUser) - }) - - m.Group("/orgs", func() { - m.Get("", admin.Organizations) - }) - - m.Group("/repos", func() { - m.Get("", admin.Repos) - m.Post("/delete", admin.DeleteRepo) - }) + m.Group("", func() { + m.Get("/", ignSignIn, route.Home) + m.Group("/explore", func() { + m.Get("", func(c *context.Context) { + c.Redirect(conf.Server.Subpath + "/explore/repos") + }) + m.Get("/repos", route.ExploreRepos) + m.Get("/users", route.ExploreUsers) + m.Get("/organizations", route.ExploreOrganizations) + }, ignSignIn) + m.Combo("/install", route.InstallInit).Get(route.Install). + Post(bindIgnErr(form.Install{}), route.InstallPost) + m.Get("/^:type(issues|pulls)$", reqSignIn, user.Issues) + + // ***** START: User ***** + m.Group("/user", func() { + m.Group("/login", func() { + m.Combo("").Get(user.Login). + Post(bindIgnErr(form.SignIn{}), user.LoginPost) + m.Combo("/two_factor").Get(user.LoginTwoFactor).Post(user.LoginTwoFactorPost) + m.Combo("/two_factor_recovery_code").Get(user.LoginTwoFactorRecoveryCode).Post(user.LoginTwoFactorRecoveryCodePost) + }) - m.Group("/auths", func() { - m.Get("", admin.Authentications) - m.Combo("/new").Get(admin.NewAuthSource).Post(bindIgnErr(form.Authentication{}), admin.NewAuthSourcePost) - m.Combo("/:authid").Get(admin.EditAuthSource). - Post(bindIgnErr(form.Authentication{}), admin.EditAuthSourcePost) - m.Post("/:authid/delete", admin.DeleteAuthSource) + m.Get("/sign_up", user.SignUp) + m.Post("/sign_up", bindIgnErr(form.Register{}), user.SignUpPost) + m.Get("/reset_password", user.ResetPasswd) + m.Post("/reset_password", user.ResetPasswdPost) + }, reqSignOut) + + m.Group("/user/settings", func() { + m.Get("", user.Settings) + m.Post("", bindIgnErr(form.UpdateProfile{}), user.SettingsPost) + m.Combo("/avatar").Get(user.SettingsAvatar). + Post(binding.MultipartForm(form.Avatar{}), user.SettingsAvatarPost) + m.Post("/avatar/delete", user.SettingsDeleteAvatar) + m.Combo("/email").Get(user.SettingsEmails). + Post(bindIgnErr(form.AddEmail{}), user.SettingsEmailPost) + m.Post("/email/delete", user.DeleteEmail) + m.Get("/password", user.SettingsPassword) + m.Post("/password", bindIgnErr(form.ChangePassword{}), user.SettingsPasswordPost) + m.Combo("/ssh").Get(user.SettingsSSHKeys). + Post(bindIgnErr(form.AddSSHKey{}), user.SettingsSSHKeysPost) + m.Post("/ssh/delete", user.DeleteSSHKey) + m.Group("/security", func() { + m.Get("", user.SettingsSecurity) + m.Combo("/two_factor_enable").Get(user.SettingsTwoFactorEnable). + Post(user.SettingsTwoFactorEnablePost) + m.Combo("/two_factor_recovery_codes").Get(user.SettingsTwoFactorRecoveryCodes). + Post(user.SettingsTwoFactorRecoveryCodesPost) + m.Post("/two_factor_disable", user.SettingsTwoFactorDisable) + }) + m.Group("/repositories", func() { + m.Get("", user.SettingsRepos) + m.Post("/leave", user.SettingsLeaveRepo) + }) + m.Group("/organizations", func() { + m.Get("", user.SettingsOrganizations) + m.Post("/leave", user.SettingsLeaveOrganization) + }) + m.Combo("/applications").Get(user.SettingsApplications). + Post(bindIgnErr(form.NewAccessToken{}), user.SettingsApplicationsPost) + m.Post("/applications/delete", user.SettingsDeleteApplication) + m.Route("/delete", "GET,POST", user.SettingsDelete) + }, reqSignIn, func(c *context.Context) { + c.Data["PageIsUserSettings"] = true }) - m.Group("/notices", func() { - m.Get("", admin.Notices) - m.Post("/delete", admin.DeleteNotices) - m.Get("/empty", admin.EmptyNotices) + m.Group("/user", func() { + m.Any("/activate", user.Activate) + m.Any("/activate_email", user.ActivateEmail) + m.Get("/email2user", user.Email2User) + m.Get("/forget_password", user.ForgotPasswd) + m.Post("/forget_password", user.ForgotPasswdPost) + m.Post("/logout", user.SignOut) }) - }, reqAdmin) - // ***** END: Admin ***** + // ***** END: User ***** + + reqAdmin := context.Toggle(&context.ToggleOptions{SignInRequired: true, AdminRequired: true}) + + // ***** START: Admin ***** + m.Group("/admin", func() { + m.Combo("").Get(admin.Dashboard).Post(admin.Operation) // "/admin" + m.Get("/config", admin.Config) + m.Post("/config/test_mail", admin.SendTestMail) + m.Get("/monitor", admin.Monitor) + + m.Group("/users", func() { + m.Get("", admin.Users) + m.Combo("/new").Get(admin.NewUser).Post(bindIgnErr(form.AdminCrateUser{}), admin.NewUserPost) + m.Combo("/:userid").Get(admin.EditUser).Post(bindIgnErr(form.AdminEditUser{}), admin.EditUserPost) + m.Post("/:userid/delete", admin.DeleteUser) + }) - m.Group("", func() { - m.Group("/:username", func() { - m.Get("", user.Profile) - m.Get("/followers", user.Followers) - m.Get("/following", user.Following) - m.Get("/stars", user.Stars) - }, context.InjectParamsUser()) - - m.Get("/attachments/:uuid", func(c *context.Context) { - attach, err := db.GetAttachmentByUUID(c.Params(":uuid")) - if err != nil { - c.NotFoundOrError(err, "get attachment by UUID") - return - } else if !com.IsFile(attach.LocalPath()) { - c.NotFound() - return - } + m.Group("/orgs", func() { + m.Get("", admin.Organizations) + }) - fr, err := os.Open(attach.LocalPath()) - if err != nil { - c.Error(err, "open attachment file") - return - } - defer fr.Close() + m.Group("/repos", func() { + m.Get("", admin.Repos) + m.Post("/delete", admin.DeleteRepo) + }) - c.Header().Set("Cache-Control", "public,max-age=86400") - c.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, attach.Name)) + m.Group("/auths", func() { + m.Get("", admin.Authentications) + m.Combo("/new").Get(admin.NewAuthSource).Post(bindIgnErr(form.Authentication{}), admin.NewAuthSourcePost) + m.Combo("/:authid").Get(admin.EditAuthSource). + Post(bindIgnErr(form.Authentication{}), admin.EditAuthSourcePost) + m.Post("/:authid/delete", admin.DeleteAuthSource) + }) - if _, err = io.Copy(c.Resp, fr); err != nil { - c.Error(err, "copy from file to response") - return - } - }) - m.Post("/issues/attachments", repo.UploadIssueAttachment) - m.Post("/releases/attachments", repo.UploadReleaseAttachment) - }, ignSignIn) + m.Group("/notices", func() { + m.Get("", admin.Notices) + m.Post("/delete", admin.DeleteNotices) + m.Get("/empty", admin.EmptyNotices) + }) + }, reqAdmin) + // ***** END: Admin ***** - m.Group("/:username", func() { - m.Post("/action/:action", user.Action) - }, reqSignIn, context.InjectParamsUser()) + m.Group("", func() { + m.Group("/:username", func() { + m.Get("", user.Profile) + m.Get("/followers", user.Followers) + m.Get("/following", user.Following) + m.Get("/stars", user.Stars) + }, context.InjectParamsUser()) + + m.Get("/attachments/:uuid", func(c *context.Context) { + attach, err := db.GetAttachmentByUUID(c.Params(":uuid")) + if err != nil { + c.NotFoundOrError(err, "get attachment by UUID") + return + } else if !com.IsFile(attach.LocalPath()) { + c.NotFound() + return + } - if macaron.Env == macaron.DEV { - m.Get("/template/*", dev.TemplatePreview) - } + fr, err := os.Open(attach.LocalPath()) + if err != nil { + c.Error(err, "open attachment file") + return + } + defer fr.Close() - reqRepoAdmin := context.RequireRepoAdmin() - reqRepoWriter := context.RequireRepoWriter() + c.Header().Set("Cache-Control", "public,max-age=86400") + c.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, attach.Name)) - webhookRoutes := func() { - m.Group("", func() { - m.Get("", repo.Webhooks) - m.Post("/delete", repo.DeleteWebhook) - m.Get("/:type/new", repo.WebhooksNew) - m.Post("/gogs/new", bindIgnErr(form.NewWebhook{}), repo.WebhooksNewPost) - m.Post("/slack/new", bindIgnErr(form.NewSlackHook{}), repo.WebhooksSlackNewPost) - m.Post("/discord/new", bindIgnErr(form.NewDiscordHook{}), repo.WebhooksDiscordNewPost) - m.Post("/dingtalk/new", bindIgnErr(form.NewDingtalkHook{}), repo.WebhooksDingtalkNewPost) - m.Get("/:id", repo.WebhooksEdit) - m.Post("/gogs/:id", bindIgnErr(form.NewWebhook{}), repo.WebhooksEditPost) - m.Post("/slack/:id", bindIgnErr(form.NewSlackHook{}), repo.WebhooksSlackEditPost) - m.Post("/discord/:id", bindIgnErr(form.NewDiscordHook{}), repo.WebhooksDiscordEditPost) - m.Post("/dingtalk/:id", bindIgnErr(form.NewDingtalkHook{}), repo.WebhooksDingtalkEditPost) - }, repo.InjectOrgRepoContext()) - } + if _, err = io.Copy(c.Resp, fr); err != nil { + c.Error(err, "copy from file to response") + return + } + }) + m.Post("/issues/attachments", repo.UploadIssueAttachment) + m.Post("/releases/attachments", repo.UploadReleaseAttachment) + }, ignSignIn) - // ***** START: Organization ***** - m.Group("/org", func() { - m.Group("", func() { - m.Get("/create", org.Create) - m.Post("/create", bindIgnErr(form.CreateOrg{}), org.CreatePost) - }, func(c *context.Context) { - if !c.User.CanCreateOrganization() { - c.NotFound() - } - }) + m.Group("/:username", func() { + m.Post("/action/:action", user.Action) + }, reqSignIn, context.InjectParamsUser()) - m.Group("/:org", func() { - m.Get("/dashboard", user.Dashboard) - m.Get("/^:type(issues|pulls)$", user.Issues) - m.Get("/members", org.Members) - m.Get("/members/action/:action", org.MembersAction) - - m.Get("/teams", org.Teams) - }, context.OrgAssignment(true)) - - m.Group("/:org", func() { - m.Get("/teams/:team", org.TeamMembers) - m.Get("/teams/:team/repositories", org.TeamRepositories) - m.Route("/teams/:team/action/:action", "GET,POST", org.TeamsAction) - m.Route("/teams/:team/action/repo/:action", "GET,POST", org.TeamsRepoAction) - }, context.OrgAssignment(true, false, true)) - - m.Group("/:org", func() { - m.Get("/teams/new", org.NewTeam) - m.Post("/teams/new", bindIgnErr(form.CreateTeam{}), org.NewTeamPost) - m.Get("/teams/:team/edit", org.EditTeam) - m.Post("/teams/:team/edit", bindIgnErr(form.CreateTeam{}), org.EditTeamPost) - m.Post("/teams/:team/delete", org.DeleteTeam) + if macaron.Env == macaron.DEV { + m.Get("/template/*", dev.TemplatePreview) + } - m.Group("/settings", func() { - m.Combo("").Get(org.Settings). - Post(bindIgnErr(form.UpdateOrgSetting{}), org.SettingsPost) - m.Post("/avatar", binding.MultipartForm(form.Avatar{}), org.SettingsAvatar) - m.Post("/avatar/delete", org.SettingsDeleteAvatar) - m.Group("/hooks", webhookRoutes) - m.Route("/delete", "GET,POST", org.SettingsDelete) - }) + reqRepoAdmin := context.RequireRepoAdmin() + reqRepoWriter := context.RequireRepoWriter() - m.Route("/invitations/new", "GET,POST", org.Invitation) - }, context.OrgAssignment(true, true)) - }, reqSignIn) - // ***** END: Organization ***** - - // ***** START: Repository ***** - m.Group("/repo", func() { - m.Get("/create", repo.Create) - m.Post("/create", bindIgnErr(form.CreateRepo{}), repo.CreatePost) - m.Get("/migrate", repo.Migrate) - m.Post("/migrate", bindIgnErr(form.MigrateRepo{}), repo.MigratePost) - m.Combo("/fork/:repoid").Get(repo.Fork). - Post(bindIgnErr(form.CreateRepo{}), repo.ForkPost) - }, reqSignIn) + webhookRoutes := func() { + m.Group("", func() { + m.Get("", repo.Webhooks) + m.Post("/delete", repo.DeleteWebhook) + m.Get("/:type/new", repo.WebhooksNew) + m.Post("/gogs/new", bindIgnErr(form.NewWebhook{}), repo.WebhooksNewPost) + m.Post("/slack/new", bindIgnErr(form.NewSlackHook{}), repo.WebhooksSlackNewPost) + m.Post("/discord/new", bindIgnErr(form.NewDiscordHook{}), repo.WebhooksDiscordNewPost) + m.Post("/dingtalk/new", bindIgnErr(form.NewDingtalkHook{}), repo.WebhooksDingtalkNewPost) + m.Get("/:id", repo.WebhooksEdit) + m.Post("/gogs/:id", bindIgnErr(form.NewWebhook{}), repo.WebhooksEditPost) + m.Post("/slack/:id", bindIgnErr(form.NewSlackHook{}), repo.WebhooksSlackEditPost) + m.Post("/discord/:id", bindIgnErr(form.NewDiscordHook{}), repo.WebhooksDiscordEditPost) + m.Post("/dingtalk/:id", bindIgnErr(form.NewDingtalkHook{}), repo.WebhooksDingtalkEditPost) + }, repo.InjectOrgRepoContext()) + } - m.Group("/:username/:reponame", func() { - m.Group("/settings", func() { - m.Combo("").Get(repo.Settings). - Post(bindIgnErr(form.RepoSetting{}), repo.SettingsPost) - m.Combo("/avatar").Get(repo.SettingsAvatar). - Post(binding.MultipartForm(form.Avatar{}), repo.SettingsAvatarPost) - m.Post("/avatar/delete", repo.SettingsDeleteAvatar) - m.Group("/collaboration", func() { - m.Combo("").Get(repo.SettingsCollaboration).Post(repo.SettingsCollaborationPost) - m.Post("/access_mode", repo.ChangeCollaborationAccessMode) - m.Post("/delete", repo.DeleteCollaboration) - }) - m.Group("/branches", func() { - m.Get("", repo.SettingsBranches) - m.Post("/default_branch", repo.UpdateDefaultBranch) - m.Combo("/*").Get(repo.SettingsProtectedBranch). - Post(bindIgnErr(form.ProtectBranch{}), repo.SettingsProtectedBranchPost) + // ***** START: Organization ***** + m.Group("/org", func() { + m.Group("", func() { + m.Get("/create", org.Create) + m.Post("/create", bindIgnErr(form.CreateOrg{}), org.CreatePost) }, func(c *context.Context) { - if c.Repo.Repository.IsMirror { + if !c.User.CanCreateOrganization() { c.NotFound() - return } }) - m.Group("/hooks", func() { - webhookRoutes() - - m.Group("/:id", func() { - m.Post("/test", repo.TestWebhook) - m.Post("/redelivery", repo.RedeliveryWebhook) + m.Group("/:org", func() { + m.Get("/dashboard", user.Dashboard) + m.Get("/^:type(issues|pulls)$", user.Issues) + m.Get("/members", org.Members) + m.Get("/members/action/:action", org.MembersAction) + + m.Get("/teams", org.Teams) + }, context.OrgAssignment(true)) + + m.Group("/:org", func() { + m.Get("/teams/:team", org.TeamMembers) + m.Get("/teams/:team/repositories", org.TeamRepositories) + m.Route("/teams/:team/action/:action", "GET,POST", org.TeamsAction) + m.Route("/teams/:team/action/repo/:action", "GET,POST", org.TeamsRepoAction) + }, context.OrgAssignment(true, false, true)) + + m.Group("/:org", func() { + m.Get("/teams/new", org.NewTeam) + m.Post("/teams/new", bindIgnErr(form.CreateTeam{}), org.NewTeamPost) + m.Get("/teams/:team/edit", org.EditTeam) + m.Post("/teams/:team/edit", bindIgnErr(form.CreateTeam{}), org.EditTeamPost) + m.Post("/teams/:team/delete", org.DeleteTeam) + + m.Group("/settings", func() { + m.Combo("").Get(org.Settings). + Post(bindIgnErr(form.UpdateOrgSetting{}), org.SettingsPost) + m.Post("/avatar", binding.MultipartForm(form.Avatar{}), org.SettingsAvatar) + m.Post("/avatar/delete", org.SettingsDeleteAvatar) + m.Group("/hooks", webhookRoutes) + m.Route("/delete", "GET,POST", org.SettingsDelete) }) - m.Group("/git", func() { - m.Get("", repo.SettingsGitHooks) - m.Combo("/:name").Get(repo.SettingsGitHooksEdit). - Post(repo.SettingsGitHooksEditPost) - }, context.GitHookService()) - }) + m.Route("/invitations/new", "GET,POST", org.Invitation) + }, context.OrgAssignment(true, true)) + }, reqSignIn) + // ***** END: Organization ***** + + // ***** START: Repository ***** + m.Group("/repo", func() { + m.Get("/create", repo.Create) + m.Post("/create", bindIgnErr(form.CreateRepo{}), repo.CreatePost) + m.Get("/migrate", repo.Migrate) + m.Post("/migrate", bindIgnErr(form.MigrateRepo{}), repo.MigratePost) + m.Combo("/fork/:repoid").Get(repo.Fork). + Post(bindIgnErr(form.CreateRepo{}), repo.ForkPost) + }, reqSignIn) + + m.Group("/:username/:reponame", func() { + m.Group("/settings", func() { + m.Combo("").Get(repo.Settings). + Post(bindIgnErr(form.RepoSetting{}), repo.SettingsPost) + m.Combo("/avatar").Get(repo.SettingsAvatar). + Post(binding.MultipartForm(form.Avatar{}), repo.SettingsAvatarPost) + m.Post("/avatar/delete", repo.SettingsDeleteAvatar) + m.Group("/collaboration", func() { + m.Combo("").Get(repo.SettingsCollaboration).Post(repo.SettingsCollaborationPost) + m.Post("/access_mode", repo.ChangeCollaborationAccessMode) + m.Post("/delete", repo.DeleteCollaboration) + }) + m.Group("/branches", func() { + m.Get("", repo.SettingsBranches) + m.Post("/default_branch", repo.UpdateDefaultBranch) + m.Combo("/*").Get(repo.SettingsProtectedBranch). + Post(bindIgnErr(form.ProtectBranch{}), repo.SettingsProtectedBranchPost) + }, func(c *context.Context) { + if c.Repo.Repository.IsMirror { + c.NotFound() + return + } + }) - m.Group("/keys", func() { - m.Combo("").Get(repo.SettingsDeployKeys). - Post(bindIgnErr(form.AddSSHKey{}), repo.SettingsDeployKeysPost) - m.Post("/delete", repo.DeleteDeployKey) - }) + m.Group("/hooks", func() { + webhookRoutes() - }, func(c *context.Context) { - c.Data["PageIsSettings"] = true - }) - }, reqSignIn, context.RepoAssignment(), reqRepoAdmin, context.RepoRef()) + m.Group("/:id", func() { + m.Post("/test", repo.TestWebhook) + m.Post("/redelivery", repo.RedeliveryWebhook) + }) - m.Post("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), repo.Action) - m.Group("/:username/:reponame", func() { - m.Get("/issues", repo.RetrieveLabels, repo.Issues) - m.Get("/issues/:index", repo.ViewIssue) - m.Get("/labels/", repo.RetrieveLabels, repo.Labels) - m.Get("/milestones", repo.Milestones) - }, ignSignIn, context.RepoAssignment(true)) - m.Group("/:username/:reponame", func() { - // FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest. - // So they can apply their own enable/disable logic on routers. - m.Group("/issues", func() { - m.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue). - Post(bindIgnErr(form.NewIssue{}), repo.NewIssuePost) - - m.Group("/:index", func() { - m.Post("/title", repo.UpdateIssueTitle) - m.Post("/content", repo.UpdateIssueContent) - m.Combo("/comments").Post(bindIgnErr(form.CreateComment{}), repo.NewComment) - }) - }) - m.Group("/comments/:id", func() { - m.Post("", repo.UpdateCommentContent) - m.Post("/delete", repo.DeleteComment) - }) - }, reqSignIn, context.RepoAssignment(true)) - m.Group("/:username/:reponame", func() { - m.Group("/wiki", func() { - m.Get("/?:page", repo.Wiki) - m.Get("/_pages", repo.WikiPages) - }, repo.MustEnableWiki, context.RepoRef()) - }, ignSignIn, context.RepoAssignment(false, true)) + m.Group("/git", func() { + m.Get("", repo.SettingsGitHooks) + m.Combo("/:name").Get(repo.SettingsGitHooksEdit). + Post(repo.SettingsGitHooksEditPost) + }, context.GitHookService()) + }) - m.Group("/:username/:reponame", func() { - // FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest. - // So they can apply their own enable/disable logic on routers. - m.Group("/issues", func() { - m.Group("/:index", func() { - m.Post("/label", repo.UpdateIssueLabel) - m.Post("/milestone", repo.UpdateIssueMilestone) - m.Post("/assignee", repo.UpdateIssueAssignee) - }, reqRepoWriter) - }) - m.Group("/labels", func() { - m.Post("/new", bindIgnErr(form.CreateLabel{}), repo.NewLabel) - m.Post("/edit", bindIgnErr(form.CreateLabel{}), repo.UpdateLabel) - m.Post("/delete", repo.DeleteLabel) - m.Post("/initialize", bindIgnErr(form.InitializeLabels{}), repo.InitializeLabels) - }, reqRepoWriter, context.RepoRef()) - m.Group("/milestones", func() { - m.Combo("/new").Get(repo.NewMilestone). - Post(bindIgnErr(form.CreateMilestone{}), repo.NewMilestonePost) - m.Get("/:id/edit", repo.EditMilestone) - m.Post("/:id/edit", bindIgnErr(form.CreateMilestone{}), repo.EditMilestonePost) - m.Get("/:id/:action", repo.ChangeMilestonStatus) - m.Post("/delete", repo.DeleteMilestone) - }, reqRepoWriter, context.RepoRef()) - - m.Group("/releases", func() { - m.Get("/new", repo.NewRelease) - m.Post("/new", bindIgnErr(form.NewRelease{}), repo.NewReleasePost) - m.Post("/delete", repo.DeleteRelease) - m.Get("/edit/*", repo.EditRelease) - m.Post("/edit/*", bindIgnErr(form.EditRelease{}), repo.EditReleasePost) - }, repo.MustBeNotBare, reqRepoWriter, func(c *context.Context) { - c.Data["PageIsViewFiles"] = true - }) + m.Group("/keys", func() { + m.Combo("").Get(repo.SettingsDeployKeys). + Post(bindIgnErr(form.AddSSHKey{}), repo.SettingsDeployKeysPost) + m.Post("/delete", repo.DeleteDeployKey) + }) - // FIXME: Should use c.Repo.PullRequest to unify template, currently we have inconsistent URL - // for PR in same repository. After select branch on the page, the URL contains redundant head user name. - // e.g. /org1/test-repo/compare/master...org1:develop - // which should be /org1/test-repo/compare/master...develop - m.Combo("/compare/*", repo.MustAllowPulls).Get(repo.CompareAndPullRequest). - Post(bindIgnErr(form.NewIssue{}), repo.CompareAndPullRequestPost) + }, func(c *context.Context) { + c.Data["PageIsSettings"] = true + }) + }, reqSignIn, context.RepoAssignment(), reqRepoAdmin, context.RepoRef()) + + m.Post("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), repo.Action) + m.Group("/:username/:reponame", func() { + m.Get("/issues", repo.RetrieveLabels, repo.Issues) + m.Get("/issues/:index", repo.ViewIssue) + m.Get("/labels/", repo.RetrieveLabels, repo.Labels) + m.Get("/milestones", repo.Milestones) + }, ignSignIn, context.RepoAssignment(true)) + m.Group("/:username/:reponame", func() { + // FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest. + // So they can apply their own enable/disable logic on routers. + m.Group("/issues", func() { + m.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue). + Post(bindIgnErr(form.NewIssue{}), repo.NewIssuePost) + + m.Group("/:index", func() { + m.Post("/title", repo.UpdateIssueTitle) + m.Post("/content", repo.UpdateIssueContent) + m.Combo("/comments").Post(bindIgnErr(form.CreateComment{}), repo.NewComment) + }) + }) + m.Group("/comments/:id", func() { + m.Post("", repo.UpdateCommentContent) + m.Post("/delete", repo.DeleteComment) + }) + }, reqSignIn, context.RepoAssignment(true)) + m.Group("/:username/:reponame", func() { + m.Group("/wiki", func() { + m.Get("/?:page", repo.Wiki) + m.Get("/_pages", repo.WikiPages) + }, repo.MustEnableWiki, context.RepoRef()) + }, ignSignIn, context.RepoAssignment(false, true)) + + m.Group("/:username/:reponame", func() { + // FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest. + // So they can apply their own enable/disable logic on routers. + m.Group("/issues", func() { + m.Group("/:index", func() { + m.Post("/label", repo.UpdateIssueLabel) + m.Post("/milestone", repo.UpdateIssueMilestone) + m.Post("/assignee", repo.UpdateIssueAssignee) + }, reqRepoWriter) + }) + m.Group("/labels", func() { + m.Post("/new", bindIgnErr(form.CreateLabel{}), repo.NewLabel) + m.Post("/edit", bindIgnErr(form.CreateLabel{}), repo.UpdateLabel) + m.Post("/delete", repo.DeleteLabel) + m.Post("/initialize", bindIgnErr(form.InitializeLabels{}), repo.InitializeLabels) + }, reqRepoWriter, context.RepoRef()) + m.Group("/milestones", func() { + m.Combo("/new").Get(repo.NewMilestone). + Post(bindIgnErr(form.CreateMilestone{}), repo.NewMilestonePost) + m.Get("/:id/edit", repo.EditMilestone) + m.Post("/:id/edit", bindIgnErr(form.CreateMilestone{}), repo.EditMilestonePost) + m.Get("/:id/:action", repo.ChangeMilestonStatus) + m.Post("/delete", repo.DeleteMilestone) + }, reqRepoWriter, context.RepoRef()) + + m.Group("/releases", func() { + m.Get("/new", repo.NewRelease) + m.Post("/new", bindIgnErr(form.NewRelease{}), repo.NewReleasePost) + m.Post("/delete", repo.DeleteRelease) + m.Get("/edit/*", repo.EditRelease) + m.Post("/edit/*", bindIgnErr(form.EditRelease{}), repo.EditReleasePost) + }, repo.MustBeNotBare, reqRepoWriter, func(c *context.Context) { + c.Data["PageIsViewFiles"] = true + }) - m.Group("", func() { - m.Combo("/_edit/*").Get(repo.EditFile). - Post(bindIgnErr(form.EditRepoFile{}), repo.EditFilePost) - m.Combo("/_new/*").Get(repo.NewFile). - Post(bindIgnErr(form.EditRepoFile{}), repo.NewFilePost) - m.Post("/_preview/*", bindIgnErr(form.EditPreviewDiff{}), repo.DiffPreviewPost) - m.Combo("/_delete/*").Get(repo.DeleteFile). - Post(bindIgnErr(form.DeleteRepoFile{}), repo.DeleteFilePost) + // FIXME: Should use c.Repo.PullRequest to unify template, currently we have inconsistent URL + // for PR in same repository. After select branch on the page, the URL contains redundant head user name. + // e.g. /org1/test-repo/compare/master...org1:develop + // which should be /org1/test-repo/compare/master...develop + m.Combo("/compare/*", repo.MustAllowPulls).Get(repo.CompareAndPullRequest). + Post(bindIgnErr(form.NewIssue{}), repo.CompareAndPullRequestPost) m.Group("", func() { - m.Combo("/_upload/*").Get(repo.UploadFile). - Post(bindIgnErr(form.UploadRepoFile{}), repo.UploadFilePost) - m.Post("/upload-file", repo.UploadFileToServer) - m.Post("/upload-remove", bindIgnErr(form.RemoveUploadFile{}), repo.RemoveUploadFileFromServer) - }, func(c *context.Context) { - if !conf.Repository.Upload.Enabled { + m.Combo("/_edit/*").Get(repo.EditFile). + Post(bindIgnErr(form.EditRepoFile{}), repo.EditFilePost) + m.Combo("/_new/*").Get(repo.NewFile). + Post(bindIgnErr(form.EditRepoFile{}), repo.NewFilePost) + m.Post("/_preview/*", bindIgnErr(form.EditPreviewDiff{}), repo.DiffPreviewPost) + m.Combo("/_delete/*").Get(repo.DeleteFile). + Post(bindIgnErr(form.DeleteRepoFile{}), repo.DeleteFilePost) + + m.Group("", func() { + m.Combo("/_upload/*").Get(repo.UploadFile). + Post(bindIgnErr(form.UploadRepoFile{}), repo.UploadFilePost) + m.Post("/upload-file", repo.UploadFileToServer) + m.Post("/upload-remove", bindIgnErr(form.RemoveUploadFile{}), repo.RemoveUploadFileFromServer) + }, func(c *context.Context) { + if !conf.Repository.Upload.Enabled { + c.NotFound() + return + } + }) + }, repo.MustBeNotBare, reqRepoWriter, context.RepoRef(), func(c *context.Context) { + if !c.Repo.CanEnableEditor() { c.NotFound() return } + + c.Data["PageIsViewFiles"] = true }) - }, repo.MustBeNotBare, reqRepoWriter, context.RepoRef(), func(c *context.Context) { - if !c.Repo.CanEnableEditor() { - c.NotFound() - return - } + }, reqSignIn, context.RepoAssignment()) - c.Data["PageIsViewFiles"] = true - }) - }, reqSignIn, context.RepoAssignment()) + m.Group("/:username/:reponame", func() { + m.Group("", func() { + m.Get("/releases", repo.MustBeNotBare, repo.Releases) + m.Get("/pulls", repo.RetrieveLabels, repo.Pulls) + m.Get("/pulls/:index", repo.ViewPull) + }, context.RepoRef()) - m.Group("/:username/:reponame", func() { - m.Group("", func() { - m.Get("/releases", repo.MustBeNotBare, repo.Releases) - m.Get("/pulls", repo.RetrieveLabels, repo.Pulls) - m.Get("/pulls/:index", repo.ViewPull) - }, context.RepoRef()) - - m.Group("/branches", func() { - m.Get("", repo.Branches) - m.Get("/all", repo.AllBranches) - m.Post("/delete/*", reqSignIn, reqRepoWriter, repo.DeleteBranchPost) - }, repo.MustBeNotBare, func(c *context.Context) { - c.Data["PageIsViewFiles"] = true - }) + m.Group("/branches", func() { + m.Get("", repo.Branches) + m.Get("/all", repo.AllBranches) + m.Post("/delete/*", reqSignIn, reqRepoWriter, repo.DeleteBranchPost) + }, repo.MustBeNotBare, func(c *context.Context) { + c.Data["PageIsViewFiles"] = true + }) - m.Group("/wiki", func() { - m.Group("", func() { - m.Combo("/_new").Get(repo.NewWiki). - Post(bindIgnErr(form.NewWiki{}), repo.NewWikiPost) - m.Combo("/:page/_edit").Get(repo.EditWiki). - Post(bindIgnErr(form.NewWiki{}), repo.EditWikiPost) - m.Post("/:page/delete", repo.DeleteWikiPagePost) - }, reqSignIn, reqRepoWriter) - }, repo.MustEnableWiki, context.RepoRef()) - - m.Get("/archive/*", repo.MustBeNotBare, repo.Download) - - m.Group("/pulls/:index", func() { - m.Get("/commits", context.RepoRef(), repo.ViewPullCommits) - m.Get("/files", context.RepoRef(), repo.ViewPullFiles) - m.Post("/merge", reqRepoWriter, repo.MergePullRequest) - }, repo.MustAllowPulls) + m.Group("/wiki", func() { + m.Group("", func() { + m.Combo("/_new").Get(repo.NewWiki). + Post(bindIgnErr(form.NewWiki{}), repo.NewWikiPost) + m.Combo("/:page/_edit").Get(repo.EditWiki). + Post(bindIgnErr(form.NewWiki{}), repo.EditWikiPost) + m.Post("/:page/delete", repo.DeleteWikiPagePost) + }, reqSignIn, reqRepoWriter) + }, repo.MustEnableWiki, context.RepoRef()) - m.Group("", func() { - m.Get("/src/*", repo.Home) - m.Get("/raw/*", repo.SingleDownload) - m.Get("/commits/*", repo.RefCommits) - m.Get("/commit/:sha([a-f0-9]{7,40})$", repo.Diff) - m.Get("/forks", repo.Forks) - }, repo.MustBeNotBare, context.RepoRef()) - m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)", repo.MustBeNotBare, repo.RawDiff) - - m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", repo.MustBeNotBare, context.RepoRef(), repo.CompareDiff) - }, ignSignIn, context.RepoAssignment()) - m.Group("/:username/:reponame", func() { - m.Get("/stars", repo.Stars) - m.Get("/watchers", repo.Watchers) - }, ignSignIn, context.RepoAssignment(), context.RepoRef()) + m.Get("/archive/*", repo.MustBeNotBare, repo.Download) - m.Group("/:username", func() { - m.Get("/:reponame", ignSignIn, context.RepoAssignment(), context.RepoRef(), repo.Home) + m.Group("/pulls/:index", func() { + m.Get("/commits", context.RepoRef(), repo.ViewPullCommits) + m.Get("/files", context.RepoRef(), repo.ViewPullFiles) + m.Post("/merge", reqRepoWriter, repo.MergePullRequest) + }, repo.MustAllowPulls) - m.Group("/:reponame", func() { - m.Head("/tasks/trigger", repo.TriggerTask) - }) - // Use the regexp to match the repository name - // Duplicated route to enable different ways of accessing same set of URLs, - // e.g. with or without ".git" suffix. - m.Group("/:reponame([\\d\\w-_\\.]+\\.git$)", func() { - m.Get("", ignSignIn, context.RepoAssignment(), context.RepoRef(), repo.Home) + m.Group("", func() { + m.Get("/src/*", repo.Home) + m.Get("/raw/*", repo.SingleDownload) + m.Get("/commits/*", repo.RefCommits) + m.Get("/commit/:sha([a-f0-9]{7,40})$", repo.Diff) + m.Get("/forks", repo.Forks) + }, repo.MustBeNotBare, context.RepoRef()) + m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)", repo.MustBeNotBare, repo.RawDiff) + + m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", repo.MustBeNotBare, context.RepoRef(), repo.CompareDiff) + }, ignSignIn, context.RepoAssignment()) + m.Group("/:username/:reponame", func() { + m.Get("", repo.Home) + m.Get("/stars", repo.Stars) + m.Get("/watchers", repo.Watchers) + m.Head("/tasks/trigger", repo.TriggerTask) // TODO: Without session and CSRF + }, ignSignIn, context.RepoAssignment(), context.RepoRef()) + // ***** END: Repository ***** + + // ********************** + // ----- API routes ----- + // ********************** + + // TODO: Without session and CSRF + m.Group("/api", func() { + apiv1.RegisterRoutes(m) + }, ignSignIn) + }, + session.Sessioner(session.Options{ + Provider: conf.Session.Provider, + ProviderConfig: conf.Session.ProviderConfig, + CookieName: conf.Session.CookieName, + CookiePath: conf.Server.Subpath, + Gclifetime: conf.Session.GCInterval, + Maxlifetime: conf.Session.MaxLifeTime, + Secure: conf.Session.CookieSecure, + }), + csrf.Csrfer(csrf.Options{ + Secret: conf.Security.SecretKey, + Header: "X-CSRF-Token", + Cookie: conf.Session.CSRFCookieName, + CookieDomain: conf.Server.URL.Hostname(), + CookiePath: conf.Server.Subpath, + CookieHttpOnly: true, + SetCookie: true, + Secure: conf.Server.URL.Scheme == "https", + }), + context.Contexter(), + ) - m.Group("/info/lfs", func() { - lfs.RegisterRoutes(m.Router) - }, ignSignInAndCsrf) + // *************************** + // ----- HTTP Git routes ----- + // *************************** - m.Route("/*", "GET,POST,OPTIONS", ignSignInAndCsrf, repo.HTTPContexter(), repo.HTTP) + m.Group("/:username/:reponame", func() { + m.Group("/info/lfs", func() { + lfs.RegisterRoutes(m.Router) }) - m.Route("/:reponame/*", "GET,POST,OPTIONS", ignSignInAndCsrf, repo.HTTPContexter(), repo.HTTP) - }) - // ***** END: Repository ***** - m.Group("/api", func() { - apiv1.RegisterRoutes(m) - }, ignSignIn) + m.Route("/*", "GET,POST,OPTIONS", repo.HTTPContexter(), repo.HTTP) + }) // *************************** // ----- Internal routes ----- // *************************** + m.Group("/-", func() { m.Get("/metrics", app.MetricsFilter(), promhttp.Handler()) // "/-/metrics" @@ -675,16 +673,18 @@ func runWeb(c *cli.Context) error { }) }) - // robots.txt - m.Get("/robots.txt", func(c *context.Context) { + // ********************** + // ----- robots.txt ----- + // ********************** + + m.Get("/robots.txt", func(w http.ResponseWriter, r *http.Request) { if conf.HasRobotsTxt { - c.ServeFileContent(filepath.Join(conf.CustomDir(), "robots.txt")) + http.ServeFile(w, r, filepath.Join(conf.CustomDir(), "robots.txt")) } else { - c.NotFound() + w.WriteHeader(http.StatusNotFound) } }) - // Not found handler. m.NotFound(route.NotFound) // Flag for port number in case first time run conflict. diff --git a/internal/context/auth.go b/internal/context/auth.go index ad583791..424a4f4f 100644 --- a/internal/context/auth.go +++ b/internal/context/auth.go @@ -7,14 +7,12 @@ package context import ( "net/http" "net/url" - "strings" "github.com/go-macaron/csrf" "gopkg.in/macaron.v1" "gogs.io/gogs/internal/auth" "gogs.io/gogs/internal/conf" - "gogs.io/gogs/internal/tool" ) type ToggleOptions struct { @@ -95,18 +93,3 @@ func Toggle(options *ToggleOptions) macaron.Handler { } } } - -// RequireBasicAuth verifies HTTP Basic Authentication header with given credentials. -func (c *Context) RequireBasicAuth(username, password string) { - fields := strings.Fields(c.Req.Header.Get("Authorization")) - if len(fields) != 2 || fields[0] != "Basic" { - c.Status(http.StatusUnauthorized) - return - } - - uname, passwd, _ := tool.BasicAuthDecode(fields[1]) - if uname != username || passwd != password { - c.Status(http.StatusForbidden) - return - } -} diff --git a/internal/db/access.go b/internal/db/access.go index 551c29d7..5f9960af 100644 --- a/internal/db/access.go +++ b/internal/db/access.go @@ -93,7 +93,7 @@ func hasAccess(e Engine, userID int64, repo *Repository, testMode AccessMode) (b } // HasAccess returns true if someone has the request access level. User can be nil! -// Deprecated: Use Perms.HasAccess instead. +// Deprecated: Use Perms.Authorize instead. func HasAccess(userID int64, repo *Repository, testMode AccessMode) (bool, error) { return hasAccess(x, userID, repo, testMode) } diff --git a/internal/route/home.go b/internal/route/home.go index 86a659d0..7338c9e8 100644 --- a/internal/route/home.go +++ b/internal/route/home.go @@ -5,7 +5,12 @@ package route import ( + "fmt" + "net/http" + + "github.com/go-macaron/i18n" "github.com/unknwon/paginater" + "gopkg.in/macaron.v1" "gogs.io/gogs/internal/conf" "gogs.io/gogs/internal/context" @@ -157,7 +162,7 @@ func ExploreOrganizations(c *context.Context) { }) } -func NotFound(c *context.Context) { - c.Data["Title"] = "Page Not Found" - c.NotFound() +func NotFound(c *macaron.Context, l i18n.Locale) { + c.Data["Title"] = l.Tr("status.page_not_found") + c.HTML(http.StatusNotFound, fmt.Sprintf("status/%d", http.StatusNotFound)) } diff --git a/internal/route/lfs/basic.go b/internal/route/lfs/basic.go index e266c541..f0c2dc8b 100644 --- a/internal/route/lfs/basic.go +++ b/internal/route/lfs/basic.go @@ -12,10 +12,10 @@ import ( "os" "strconv" + "gopkg.in/macaron.v1" log "unknwon.dev/clog/v2" "gogs.io/gogs/internal/conf" - "gogs.io/gogs/internal/context" "gogs.io/gogs/internal/db" "gogs.io/gogs/internal/lfsutil" "gogs.io/gogs/internal/strutil" @@ -28,7 +28,7 @@ const ( ) // GET /{owner}/{repo}.git/info/lfs/object/basic/{oid} -func serveBasicDownload(c *context.Context, repo *db.Repository, oid lfsutil.OID) { +func serveBasicDownload(c *macaron.Context, repo *db.Repository, oid lfsutil.OID) { object, err := db.LFS.GetObjectByOID(repo.ID, oid) if err != nil { if db.IsErrLFSObjectNotExist(err) { @@ -63,7 +63,7 @@ func serveBasicDownload(c *context.Context, repo *db.Repository, oid lfsutil.OID } // PUT /{owner}/{repo}.git/info/lfs/object/basic/{oid} -func serveBasicUpload(c *context.Context, repo *db.Repository, oid lfsutil.OID) { +func serveBasicUpload(c *macaron.Context, repo *db.Repository, oid lfsutil.OID) { // NOTE: LFS client will retry upload the same object if there was a partial failure, // therefore we would like to skip ones that already exist. _, err := db.LFS.GetObjectByOID(repo.ID, oid) @@ -91,7 +91,7 @@ func serveBasicUpload(c *context.Context, repo *db.Repository, oid lfsutil.OID) } // POST /{owner}/{repo}.git/info/lfs/object/basic/verify -func serveBasicVerify(c *context.Context, repo *db.Repository) { +func serveBasicVerify(c *macaron.Context, repo *db.Repository) { var request basicVerifyRequest defer c.Req.Request.Body.Close() err := json.NewDecoder(c.Req.Request.Body).Decode(&request) diff --git a/internal/route/lfs/batch.go b/internal/route/lfs/batch.go index 357aeace..b38435e7 100644 --- a/internal/route/lfs/batch.go +++ b/internal/route/lfs/batch.go @@ -9,17 +9,17 @@ import ( "net/http" jsoniter "github.com/json-iterator/go" + "gopkg.in/macaron.v1" log "unknwon.dev/clog/v2" "gogs.io/gogs/internal/conf" - "gogs.io/gogs/internal/context" "gogs.io/gogs/internal/db" "gogs.io/gogs/internal/lfsutil" "gogs.io/gogs/internal/strutil" ) // POST /{owner}/{repo}.git/info/lfs/object/batch -func serveBatch(c *context.Context, owner *db.User, repo *db.Repository) { +func serveBatch(c *macaron.Context, owner *db.User, repo *db.Repository) { var request batchRequest defer c.Req.Request.Body.Close() err := jsoniter.NewDecoder(c.Req.Request.Body).Decode(&request) diff --git a/internal/route/lfs/route.go b/internal/route/lfs/route.go index b6bd20bf..39ecc348 100644 --- a/internal/route/lfs/route.go +++ b/internal/route/lfs/route.go @@ -13,7 +13,6 @@ import ( log "unknwon.dev/clog/v2" "gogs.io/gogs/internal/authutil" - "gogs.io/gogs/internal/context" "gogs.io/gogs/internal/db" "gogs.io/gogs/internal/lfsutil" ) @@ -44,7 +43,7 @@ func authenticate() macaron.Handler { }) } - return func(c *context.Context) { + return func(c *macaron.Context) { username, password := authutil.DecodeBasic(c.Req.Header) if username == "" { askCredentials(c.Resp) @@ -59,7 +58,7 @@ func authenticate() macaron.Handler { } if err == nil && user.IsEnabledTwoFactor() { - c.PlainText(http.StatusBadRequest, `Users with 2FA enabled are not allowed to authenticate via username and password.`) + c.Error(http.StatusBadRequest, `Users with 2FA enabled are not allowed to authenticate via username and password.`) return } @@ -98,7 +97,7 @@ func authenticate() macaron.Handler { // authorize tries to authorize the user to the context repository with given access mode. func authorize(mode db.AccessMode) macaron.Handler { - return func(c *context.Context, user *db.User) { + return func(c *macaron.Context, user *db.User) { username := c.Params(":username") reponame := strings.TrimSuffix(c.Params(":reponame"), ".git") @@ -137,7 +136,7 @@ func authorize(mode db.AccessMode) macaron.Handler { // verifyHeader checks if the HTTP header contains given value. // When not, response given "failCode" as status code. func verifyHeader(key, value string, failCode int) macaron.Handler { - return func(c *context.Context) { + return func(c *macaron.Context) { if !strings.Contains(c.Req.Header.Get(key), value) { c.Status(failCode) return @@ -147,10 +146,10 @@ func verifyHeader(key, value string, failCode int) macaron.Handler { // verifyOID checks if the ":oid" URL parameter is valid. func verifyOID() macaron.Handler { - return func(c *context.Context) { + return func(c *macaron.Context) { oid := lfsutil.OID(c.Params(":oid")) if !lfsutil.ValidOID(oid) { - c.PlainText(http.StatusBadRequest, "Invalid oid") + c.Error(http.StatusBadRequest, "Invalid oid") return } diff --git a/internal/route/repo/http.go b/internal/route/repo/http.go index c48cbdb8..59d7bf23 100644 --- a/internal/route/repo/http.go +++ b/internal/route/repo/http.go @@ -21,14 +21,13 @@ import ( log "unknwon.dev/clog/v2" "gogs.io/gogs/internal/conf" - "gogs.io/gogs/internal/context" "gogs.io/gogs/internal/db" "gogs.io/gogs/internal/lazyregexp" "gogs.io/gogs/internal/tool" ) type HTTPContext struct { - *context.Context + *macaron.Context OwnerName string OwnerSalt string RepoID int64 @@ -37,17 +36,17 @@ type HTTPContext struct { } // askCredentials responses HTTP header and status which informs client to provide credentials. -func askCredentials(c *context.Context, status int, text string) { - c.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"") - c.PlainText(status, text) +func askCredentials(c *macaron.Context, status int, text string) { + c.Header().Set("WWW-Authenticate", "Basic realm=\".\"") + c.Error(status, text) } func HTTPContexter() macaron.Handler { - return func(c *context.Context) { + return func(c *macaron.Context) { if len(conf.HTTP.AccessControlAllowOrigin) > 0 { // Set CORS headers for browser-based git clients - c.Resp.Header().Set("Access-Control-Allow-Origin", conf.HTTP.AccessControlAllowOrigin) - c.Resp.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, User-Agent") + c.Header().Set("Access-Control-Allow-Origin", conf.HTTP.AccessControlAllowOrigin) + c.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, User-Agent") // Handle preflight OPTIONS request if c.Req.Method == "OPTIONS" { @@ -64,15 +63,25 @@ func HTTPContexter() macaron.Handler { strings.HasSuffix(c.Req.URL.Path, "git-upload-pack") || c.Req.Method == "GET" - owner, err := db.GetUserByName(ownerName) + owner, err := db.Users.GetByUsername(ownerName) if err != nil { - c.NotFoundOrError(err, "get user by name") + if db.IsErrUserNotExist(err) { + c.Status(http.StatusNotFound) + } else { + c.Status(http.StatusInternalServerError) + log.Error("Failed to get user [name: %s]: %v", ownerName, err) + } return } - repo, err := db.GetRepositoryByName(owner.ID, repoName) + repo, err := db.Repos.GetByName(owner.ID, repoName) if err != nil { - c.NotFoundOrError(err, "get repository by name") + if db.IsErrRepoNotExist(err) { + c.Status(http.StatusNotFound) + } else { + c.Status(http.StatusInternalServerError) + log.Error("Failed to get repository [owner_id: %d, name: %s]: %v", owner.ID, repoName, err) + } return } @@ -114,7 +123,8 @@ func HTTPContexter() macaron.Handler { authUser, err := db.Users.Authenticate(authUsername, authPassword, -1) if err != nil && !db.IsErrUserNotExist(err) { - c.Error(err, "authenticate user") + c.Status(http.StatusInternalServerError) + log.Error("Failed to authenticate user [name: %s]: %v", authUsername, err) return } @@ -125,7 +135,8 @@ func HTTPContexter() macaron.Handler { if db.IsErrAccessTokenNotExist(err) { askCredentials(c, http.StatusUnauthorized, "") } else { - c.Error(err, "get access token by SHA") + c.Status(http.StatusInternalServerError) + log.Error("Failed to get access token [sha: %s]: %v", authUsername, err) } return } @@ -134,11 +145,12 @@ func HTTPContexter() macaron.Handler { log.Error("Failed to update access token: %v", err) } - authUser, err = db.GetUserByID(token.UserID) + authUser, err = db.Users.GetByID(token.UserID) if err != nil { // Once we found token, we're supposed to find its related user, // thus any error is unexpected. - c.Error(err, "get user by ID") + c.Status(http.StatusInternalServerError) + log.Error("Failed to get user [id: %d]: %v", token.UserID, err) return } } else if authUser.IsEnabledTwoFactor() { @@ -147,23 +159,19 @@ Please create and use personal access token on user settings page`) return } - log.Trace("HTTPGit - Authenticated user: %s", authUser.Name) + log.Trace("[Git] Authenticated user: %s", authUser.Name) mode := db.AccessModeWrite if isPull { mode = db.AccessModeRead } - has, err := db.HasAccess(authUser.ID, repo, mode) - if err != nil { - c.Error(err, "check access") - return - } else if !has { + if !db.Perms.Authorize(authUser.ID, repo, mode) { askCredentials(c, http.StatusForbidden, "User permission denied") return } if !isPull && repo.IsMirror { - c.PlainText(http.StatusForbidden, "Mirror repository is read-only") + c.Error(http.StatusForbidden, "Mirror repository is read-only") return } @@ -390,7 +398,7 @@ func HTTP(c *HTTPContext) { // but we only want to output this message only if user is really trying to access // Git HTTP endpoints. if conf.Repository.DisableHTTPGit { - c.PlainText(http.StatusForbidden, "Interacting with repositories by HTTP protocol is disabled") + c.Error(http.StatusForbidden, "Interacting with repositories by HTTP protocol is disabled") return } |