code.gitea.io/gitea@v1.22.3/routers/web/web.go (about) 1 // Copyright 2017 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package web 5 6 import ( 7 gocontext "context" 8 "net/http" 9 "strings" 10 11 auth_model "code.gitea.io/gitea/models/auth" 12 "code.gitea.io/gitea/models/db" 13 "code.gitea.io/gitea/models/perm" 14 "code.gitea.io/gitea/models/unit" 15 "code.gitea.io/gitea/modules/log" 16 "code.gitea.io/gitea/modules/metrics" 17 "code.gitea.io/gitea/modules/public" 18 "code.gitea.io/gitea/modules/setting" 19 "code.gitea.io/gitea/modules/storage" 20 "code.gitea.io/gitea/modules/structs" 21 "code.gitea.io/gitea/modules/templates" 22 "code.gitea.io/gitea/modules/validation" 23 "code.gitea.io/gitea/modules/web" 24 "code.gitea.io/gitea/modules/web/middleware" 25 "code.gitea.io/gitea/modules/web/routing" 26 "code.gitea.io/gitea/routers/common" 27 "code.gitea.io/gitea/routers/web/admin" 28 "code.gitea.io/gitea/routers/web/auth" 29 "code.gitea.io/gitea/routers/web/devtest" 30 "code.gitea.io/gitea/routers/web/events" 31 "code.gitea.io/gitea/routers/web/explore" 32 "code.gitea.io/gitea/routers/web/feed" 33 "code.gitea.io/gitea/routers/web/healthcheck" 34 "code.gitea.io/gitea/routers/web/misc" 35 "code.gitea.io/gitea/routers/web/org" 36 org_setting "code.gitea.io/gitea/routers/web/org/setting" 37 "code.gitea.io/gitea/routers/web/repo" 38 "code.gitea.io/gitea/routers/web/repo/actions" 39 repo_setting "code.gitea.io/gitea/routers/web/repo/setting" 40 "code.gitea.io/gitea/routers/web/shared/project" 41 "code.gitea.io/gitea/routers/web/user" 42 user_setting "code.gitea.io/gitea/routers/web/user/setting" 43 "code.gitea.io/gitea/routers/web/user/setting/security" 44 auth_service "code.gitea.io/gitea/services/auth" 45 "code.gitea.io/gitea/services/context" 46 "code.gitea.io/gitea/services/forms" 47 "code.gitea.io/gitea/services/lfs" 48 49 _ "code.gitea.io/gitea/modules/session" // to registers all internal adapters 50 51 "gitea.com/go-chi/captcha" 52 chi_middleware "github.com/go-chi/chi/v5/middleware" 53 "github.com/go-chi/cors" 54 "github.com/klauspost/compress/gzhttp" 55 "github.com/prometheus/client_golang/prometheus" 56 ) 57 58 var GzipMinSize = 1400 // min size to compress for the body size of response 59 60 // optionsCorsHandler return a http handler which sets CORS options if enabled by config, it blocks non-CORS OPTIONS requests. 61 func optionsCorsHandler() func(next http.Handler) http.Handler { 62 var corsHandler func(next http.Handler) http.Handler 63 if setting.CORSConfig.Enabled { 64 corsHandler = cors.Handler(cors.Options{ 65 AllowedOrigins: setting.CORSConfig.AllowDomain, 66 AllowedMethods: setting.CORSConfig.Methods, 67 AllowCredentials: setting.CORSConfig.AllowCredentials, 68 AllowedHeaders: setting.CORSConfig.Headers, 69 MaxAge: int(setting.CORSConfig.MaxAge.Seconds()), 70 }) 71 } 72 73 return func(next http.Handler) http.Handler { 74 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 75 if r.Method == http.MethodOptions { 76 if corsHandler != nil && r.Header.Get("Access-Control-Request-Method") != "" { 77 corsHandler(next).ServeHTTP(w, r) 78 } else { 79 // it should explicitly deny OPTIONS requests if CORS handler is not executed, to avoid the next GET/POST handler being incorrectly called by the OPTIONS request 80 w.WriteHeader(http.StatusMethodNotAllowed) 81 } 82 return 83 } 84 // for non-OPTIONS requests, call the CORS handler to add some related headers like "Vary" 85 if corsHandler != nil { 86 corsHandler(next).ServeHTTP(w, r) 87 } else { 88 next.ServeHTTP(w, r) 89 } 90 }) 91 } 92 } 93 94 // The OAuth2 plugin is expected to be executed first, as it must ignore the user id stored 95 // in the session (if there is a user id stored in session other plugins might return the user 96 // object for that id). 97 // 98 // The Session plugin is expected to be executed second, in order to skip authentication 99 // for users that have already signed in. 100 func buildAuthGroup() *auth_service.Group { 101 group := auth_service.NewGroup() 102 group.Add(&auth_service.OAuth2{}) // FIXME: this should be removed and only applied in download and oauth related routers 103 group.Add(&auth_service.Basic{}) // FIXME: this should be removed and only applied in download and git/lfs routers 104 105 if setting.Service.EnableReverseProxyAuth { 106 group.Add(&auth_service.ReverseProxy{}) // reverseproxy should before Session, otherwise the header will be ignored if user has login 107 } 108 group.Add(&auth_service.Session{}) 109 110 if setting.IsWindows && auth_model.IsSSPIEnabled(db.DefaultContext) { 111 group.Add(&auth_service.SSPI{}) // it MUST be the last, see the comment of SSPI 112 } 113 114 return group 115 } 116 117 func webAuth(authMethod auth_service.Method) func(*context.Context) { 118 return func(ctx *context.Context) { 119 ar, err := common.AuthShared(ctx.Base, ctx.Session, authMethod) 120 if err != nil { 121 log.Error("Failed to verify user: %v", err) 122 ctx.Error(http.StatusUnauthorized, "Verify") 123 return 124 } 125 ctx.Doer = ar.Doer 126 ctx.IsSigned = ar.Doer != nil 127 ctx.IsBasicAuth = ar.IsBasicAuth 128 if ctx.Doer == nil { 129 // ensure the session uid is deleted 130 _ = ctx.Session.Delete("uid") 131 } 132 133 ctx.Csrf.PrepareForSessionUser(ctx) 134 } 135 } 136 137 // verifyAuthWithOptions checks authentication according to options 138 func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.Context) { 139 return func(ctx *context.Context) { 140 // Check prohibit login users. 141 if ctx.IsSigned { 142 if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm { 143 ctx.Data["Title"] = ctx.Tr("auth.active_your_account") 144 ctx.HTML(http.StatusOK, "user/auth/activate") 145 return 146 } 147 if !ctx.Doer.IsActive || ctx.Doer.ProhibitLogin { 148 log.Info("Failed authentication attempt for %s from %s", ctx.Doer.Name, ctx.RemoteAddr()) 149 ctx.Data["Title"] = ctx.Tr("auth.prohibit_login") 150 ctx.HTML(http.StatusOK, "user/auth/prohibit_login") 151 return 152 } 153 154 if ctx.Doer.MustChangePassword { 155 if ctx.Req.URL.Path != "/user/settings/change_password" { 156 if strings.HasPrefix(ctx.Req.UserAgent(), "git") { 157 ctx.Error(http.StatusUnauthorized, ctx.Locale.TrString("auth.must_change_password")) 158 return 159 } 160 ctx.Data["Title"] = ctx.Tr("auth.must_change_password") 161 ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password" 162 if ctx.Req.URL.Path != "/user/events" { 163 middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI()) 164 } 165 ctx.Redirect(setting.AppSubURL + "/user/settings/change_password") 166 return 167 } 168 } else if ctx.Req.URL.Path == "/user/settings/change_password" { 169 // make sure that the form cannot be accessed by users who don't need this 170 ctx.Redirect(setting.AppSubURL + "/") 171 return 172 } 173 } 174 175 // Redirect to dashboard (or alternate location) if user tries to visit any non-login page. 176 if options.SignOutRequired && ctx.IsSigned && ctx.Req.URL.RequestURI() != "/" { 177 ctx.RedirectToCurrentSite(ctx.FormString("redirect_to")) 178 return 179 } 180 181 if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == "POST" { 182 ctx.Csrf.Validate(ctx) 183 if ctx.Written() { 184 return 185 } 186 } 187 188 if options.SignInRequired { 189 if !ctx.IsSigned { 190 if ctx.Req.URL.Path != "/user/events" { 191 middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI()) 192 } 193 ctx.Redirect(setting.AppSubURL + "/user/login") 194 return 195 } else if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm { 196 ctx.Data["Title"] = ctx.Tr("auth.active_your_account") 197 ctx.HTML(http.StatusOK, "user/auth/activate") 198 return 199 } 200 } 201 202 // Redirect to log in page if auto-signin info is provided and has not signed in. 203 if !options.SignOutRequired && !ctx.IsSigned && 204 ctx.GetSiteCookie(setting.CookieRememberName) != "" { 205 if ctx.Req.URL.Path != "/user/events" { 206 middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI()) 207 } 208 ctx.Redirect(setting.AppSubURL + "/user/login") 209 return 210 } 211 212 if options.AdminRequired { 213 if !ctx.Doer.IsAdmin { 214 ctx.Error(http.StatusForbidden) 215 return 216 } 217 ctx.Data["PageIsAdmin"] = true 218 } 219 } 220 } 221 222 func ctxDataSet(args ...any) func(ctx *context.Context) { 223 return func(ctx *context.Context) { 224 for i := 0; i < len(args); i += 2 { 225 ctx.Data[args[i].(string)] = args[i+1] 226 } 227 } 228 } 229 230 // Routes returns all web routes 231 func Routes() *web.Route { 232 routes := web.NewRoute() 233 234 routes.Head("/", misc.DummyOK) // for health check - doesn't need to be passed through gzip handler 235 routes.Methods("GET, HEAD, OPTIONS", "/assets/*", optionsCorsHandler(), public.FileHandlerFunc()) 236 routes.Methods("GET, HEAD", "/avatars/*", storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars)) 237 routes.Methods("GET, HEAD", "/repo-avatars/*", storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars)) 238 routes.Methods("GET, HEAD", "/apple-touch-icon.png", misc.StaticRedirect("/assets/img/apple-touch-icon.png")) 239 routes.Methods("GET, HEAD", "/apple-touch-icon-precomposed.png", misc.StaticRedirect("/assets/img/apple-touch-icon.png")) 240 routes.Methods("GET, HEAD", "/favicon.ico", misc.StaticRedirect("/assets/img/favicon.png")) 241 242 _ = templates.HTMLRenderer() 243 244 var mid []any 245 246 if setting.EnableGzip { 247 // random jitter is recommended by: https://pkg.go.dev/github.com/klauspost/compress/gzhttp#readme-breach-mitigation 248 // compression level 6 is the gzip default and a good general tradeoff between speed, CPU usage, and compression 249 wrapper, err := gzhttp.NewWrapper(gzhttp.RandomJitter(32, 0, false), gzhttp.MinSize(GzipMinSize), gzhttp.CompressionLevel(6)) 250 if err != nil { 251 log.Fatal("gzhttp.NewWrapper failed: %v", err) 252 } 253 mid = append(mid, wrapper) 254 } 255 256 if setting.Service.EnableCaptcha { 257 // The captcha http.Handler should only fire on /captcha/* so we can just mount this on that url 258 routes.Methods("GET,HEAD", "/captcha/*", append(mid, captcha.Captchaer(context.GetImageCaptcha()))...) 259 } 260 261 if setting.Metrics.Enabled { 262 prometheus.MustRegister(metrics.NewCollector()) 263 routes.Get("/metrics", append(mid, Metrics)...) 264 } 265 266 routes.Methods("GET,HEAD", "/robots.txt", append(mid, misc.RobotsTxt)...) 267 routes.Get("/ssh_info", misc.SSHInfo) 268 routes.Get("/api/healthz", healthcheck.Check) 269 270 mid = append(mid, common.Sessioner(), context.Contexter()) 271 272 // Get user from session if logged in. 273 mid = append(mid, webAuth(buildAuthGroup())) 274 275 // GetHead allows a HEAD request redirect to GET if HEAD method is not defined for that route 276 mid = append(mid, chi_middleware.GetHead) 277 278 if setting.API.EnableSwagger { 279 // Note: The route is here but no in API routes because it renders a web page 280 routes.Get("/api/swagger", append(mid, misc.Swagger)...) // Render V1 by default 281 } 282 283 // TODO: These really seem like things that could be folded into Contexter or as helper functions 284 mid = append(mid, user.GetNotificationCount) 285 mid = append(mid, repo.GetActiveStopwatch) 286 mid = append(mid, goGet) 287 288 others := web.NewRoute() 289 others.Use(mid...) 290 registerRoutes(others) 291 routes.Mount("", others) 292 return routes 293 } 294 295 var ignSignInAndCsrf = verifyAuthWithOptions(&common.VerifyOptions{DisableCSRF: true}) 296 297 // registerRoutes register routes 298 func registerRoutes(m *web.Route) { 299 reqSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true}) 300 reqSignOut := verifyAuthWithOptions(&common.VerifyOptions{SignOutRequired: true}) 301 // TODO: rename them to "optSignIn", which means that the "sign-in" could be optional, depends on the VerifyOptions (RequireSignInView) 302 ignSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView}) 303 ignExploreSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView}) 304 305 validation.AddBindingRules() 306 307 linkAccountEnabled := func(ctx *context.Context) { 308 if !setting.Service.EnableOpenIDSignIn && !setting.Service.EnableOpenIDSignUp && !setting.OAuth2.Enabled { 309 ctx.Error(http.StatusForbidden) 310 return 311 } 312 } 313 314 openIDSignInEnabled := func(ctx *context.Context) { 315 if !setting.Service.EnableOpenIDSignIn { 316 ctx.Error(http.StatusForbidden) 317 return 318 } 319 } 320 321 openIDSignUpEnabled := func(ctx *context.Context) { 322 if !setting.Service.EnableOpenIDSignUp { 323 ctx.Error(http.StatusForbidden) 324 return 325 } 326 } 327 328 reqMilestonesDashboardPageEnabled := func(ctx *context.Context) { 329 if !setting.Service.ShowMilestonesDashboardPage { 330 ctx.Error(http.StatusForbidden) 331 return 332 } 333 } 334 335 // webhooksEnabled requires webhooks to be enabled by admin. 336 webhooksEnabled := func(ctx *context.Context) { 337 if setting.DisableWebhooks { 338 ctx.Error(http.StatusForbidden) 339 return 340 } 341 } 342 343 lfsServerEnabled := func(ctx *context.Context) { 344 if !setting.LFS.StartServer { 345 ctx.Error(http.StatusNotFound) 346 return 347 } 348 } 349 350 federationEnabled := func(ctx *context.Context) { 351 if !setting.Federation.Enabled { 352 ctx.Error(http.StatusNotFound) 353 return 354 } 355 } 356 357 dlSourceEnabled := func(ctx *context.Context) { 358 if setting.Repository.DisableDownloadSourceArchives { 359 ctx.Error(http.StatusNotFound) 360 return 361 } 362 } 363 364 sitemapEnabled := func(ctx *context.Context) { 365 if !setting.Other.EnableSitemap { 366 ctx.Error(http.StatusNotFound) 367 return 368 } 369 } 370 371 packagesEnabled := func(ctx *context.Context) { 372 if !setting.Packages.Enabled { 373 ctx.Error(http.StatusForbidden) 374 return 375 } 376 } 377 378 feedEnabled := func(ctx *context.Context) { 379 if !setting.Other.EnableFeed { 380 ctx.Error(http.StatusNotFound) 381 return 382 } 383 } 384 385 reqUnitAccess := func(unitType unit.Type, accessMode perm.AccessMode, ignoreGlobal bool) func(ctx *context.Context) { 386 return func(ctx *context.Context) { 387 // only check global disabled units when ignoreGlobal is false 388 if !ignoreGlobal && unitType.UnitGlobalDisabled() { 389 ctx.NotFound(unitType.String(), nil) 390 return 391 } 392 393 if ctx.ContextUser == nil { 394 ctx.NotFound(unitType.String(), nil) 395 return 396 } 397 398 if ctx.ContextUser.IsOrganization() { 399 if ctx.Org.Organization.UnitPermission(ctx, ctx.Doer, unitType) < accessMode { 400 ctx.NotFound(unitType.String(), nil) 401 return 402 } 403 } 404 } 405 } 406 407 addWebhookAddRoutes := func() { 408 m.Get("/{type}/new", repo_setting.WebhooksNew) 409 m.Post("/gitea/new", web.Bind(forms.NewWebhookForm{}), repo_setting.GiteaHooksNewPost) 410 m.Post("/gogs/new", web.Bind(forms.NewGogshookForm{}), repo_setting.GogsHooksNewPost) 411 m.Post("/slack/new", web.Bind(forms.NewSlackHookForm{}), repo_setting.SlackHooksNewPost) 412 m.Post("/discord/new", web.Bind(forms.NewDiscordHookForm{}), repo_setting.DiscordHooksNewPost) 413 m.Post("/dingtalk/new", web.Bind(forms.NewDingtalkHookForm{}), repo_setting.DingtalkHooksNewPost) 414 m.Post("/telegram/new", web.Bind(forms.NewTelegramHookForm{}), repo_setting.TelegramHooksNewPost) 415 m.Post("/matrix/new", web.Bind(forms.NewMatrixHookForm{}), repo_setting.MatrixHooksNewPost) 416 m.Post("/msteams/new", web.Bind(forms.NewMSTeamsHookForm{}), repo_setting.MSTeamsHooksNewPost) 417 m.Post("/feishu/new", web.Bind(forms.NewFeishuHookForm{}), repo_setting.FeishuHooksNewPost) 418 m.Post("/wechatwork/new", web.Bind(forms.NewWechatWorkHookForm{}), repo_setting.WechatworkHooksNewPost) 419 m.Post("/packagist/new", web.Bind(forms.NewPackagistHookForm{}), repo_setting.PackagistHooksNewPost) 420 } 421 422 addWebhookEditRoutes := func() { 423 m.Post("/gitea/{id}", web.Bind(forms.NewWebhookForm{}), repo_setting.GiteaHooksEditPost) 424 m.Post("/gogs/{id}", web.Bind(forms.NewGogshookForm{}), repo_setting.GogsHooksEditPost) 425 m.Post("/slack/{id}", web.Bind(forms.NewSlackHookForm{}), repo_setting.SlackHooksEditPost) 426 m.Post("/discord/{id}", web.Bind(forms.NewDiscordHookForm{}), repo_setting.DiscordHooksEditPost) 427 m.Post("/dingtalk/{id}", web.Bind(forms.NewDingtalkHookForm{}), repo_setting.DingtalkHooksEditPost) 428 m.Post("/telegram/{id}", web.Bind(forms.NewTelegramHookForm{}), repo_setting.TelegramHooksEditPost) 429 m.Post("/matrix/{id}", web.Bind(forms.NewMatrixHookForm{}), repo_setting.MatrixHooksEditPost) 430 m.Post("/msteams/{id}", web.Bind(forms.NewMSTeamsHookForm{}), repo_setting.MSTeamsHooksEditPost) 431 m.Post("/feishu/{id}", web.Bind(forms.NewFeishuHookForm{}), repo_setting.FeishuHooksEditPost) 432 m.Post("/wechatwork/{id}", web.Bind(forms.NewWechatWorkHookForm{}), repo_setting.WechatworkHooksEditPost) 433 m.Post("/packagist/{id}", web.Bind(forms.NewPackagistHookForm{}), repo_setting.PackagistHooksEditPost) 434 } 435 436 addSettingsVariablesRoutes := func() { 437 m.Group("/variables", func() { 438 m.Get("", repo_setting.Variables) 439 m.Post("/new", web.Bind(forms.EditVariableForm{}), repo_setting.VariableCreate) 440 m.Post("/{variable_id}/edit", web.Bind(forms.EditVariableForm{}), repo_setting.VariableUpdate) 441 m.Post("/{variable_id}/delete", repo_setting.VariableDelete) 442 }) 443 } 444 445 addSettingsSecretsRoutes := func() { 446 m.Group("/secrets", func() { 447 m.Get("", repo_setting.Secrets) 448 m.Post("", web.Bind(forms.AddSecretForm{}), repo_setting.SecretsPost) 449 m.Post("/delete", repo_setting.SecretsDelete) 450 }) 451 } 452 453 addSettingsRunnersRoutes := func() { 454 m.Group("/runners", func() { 455 m.Get("", repo_setting.Runners) 456 m.Combo("/{runnerid}").Get(repo_setting.RunnersEdit). 457 Post(web.Bind(forms.EditRunnerForm{}), repo_setting.RunnersEditPost) 458 m.Post("/{runnerid}/delete", repo_setting.RunnerDeletePost) 459 m.Get("/reset_registration_token", repo_setting.ResetRunnerRegistrationToken) 460 }) 461 } 462 463 // FIXME: not all routes need go through same middleware. 464 // Especially some AJAX requests, we can reduce middleware number to improve performance. 465 466 m.Get("/", Home) 467 m.Get("/sitemap.xml", sitemapEnabled, ignExploreSignIn, HomeSitemap) 468 m.Group("/.well-known", func() { 469 m.Get("/openid-configuration", auth.OIDCWellKnown) 470 m.Group("", func() { 471 m.Get("/nodeinfo", NodeInfoLinks) 472 m.Get("/webfinger", WebfingerQuery) 473 }, federationEnabled) 474 m.Get("/change-password", func(ctx *context.Context) { 475 ctx.Redirect(setting.AppSubURL + "/user/settings/account") 476 }) 477 m.Get("/passkey-endpoints", passkeyEndpoints) 478 m.Methods("GET, HEAD", "/*", public.FileHandlerFunc()) 479 }, optionsCorsHandler()) 480 481 m.Group("/explore", func() { 482 m.Get("", func(ctx *context.Context) { 483 ctx.Redirect(setting.AppSubURL + "/explore/repos") 484 }) 485 m.Get("/repos", explore.Repos) 486 m.Get("/repos/sitemap-{idx}.xml", sitemapEnabled, explore.Repos) 487 m.Get("/users", explore.Users) 488 m.Get("/users/sitemap-{idx}.xml", sitemapEnabled, explore.Users) 489 m.Get("/organizations", explore.Organizations) 490 m.Get("/code", func(ctx *context.Context) { 491 if unit.TypeCode.UnitGlobalDisabled() { 492 ctx.NotFound(unit.TypeCode.String(), nil) 493 return 494 } 495 }, explore.Code) 496 m.Get("/topics/search", explore.TopicSearch) 497 }, ignExploreSignIn) 498 499 m.Group("/issues", func() { 500 m.Get("", user.Issues) 501 m.Get("/search", repo.SearchIssues) 502 }, reqSignIn) 503 504 m.Get("/pulls", reqSignIn, user.Pulls) 505 m.Get("/milestones", reqSignIn, reqMilestonesDashboardPageEnabled, user.Milestones) 506 507 // ***** START: User ***** 508 // "user/login" doesn't need signOut, then logged-in users can still access this route for redirection purposes by "/user/login?redirec_to=..." 509 m.Get("/user/login", auth.SignIn) 510 m.Group("/user", func() { 511 m.Post("/login", web.Bind(forms.SignInForm{}), auth.SignInPost) 512 m.Group("", func() { 513 m.Combo("/login/openid"). 514 Get(auth.SignInOpenID). 515 Post(web.Bind(forms.SignInOpenIDForm{}), auth.SignInOpenIDPost) 516 }, openIDSignInEnabled) 517 m.Group("/openid", func() { 518 m.Combo("/connect"). 519 Get(auth.ConnectOpenID). 520 Post(web.Bind(forms.ConnectOpenIDForm{}), auth.ConnectOpenIDPost) 521 m.Group("/register", func() { 522 m.Combo(""). 523 Get(auth.RegisterOpenID, openIDSignUpEnabled). 524 Post(web.Bind(forms.SignUpOpenIDForm{}), auth.RegisterOpenIDPost) 525 }, openIDSignUpEnabled) 526 }, openIDSignInEnabled) 527 m.Get("/sign_up", auth.SignUp) 528 m.Post("/sign_up", web.Bind(forms.RegisterForm{}), auth.SignUpPost) 529 m.Get("/link_account", linkAccountEnabled, auth.LinkAccount) 530 m.Post("/link_account_signin", linkAccountEnabled, web.Bind(forms.SignInForm{}), auth.LinkAccountPostSignIn) 531 m.Post("/link_account_signup", linkAccountEnabled, web.Bind(forms.RegisterForm{}), auth.LinkAccountPostRegister) 532 m.Group("/two_factor", func() { 533 m.Get("", auth.TwoFactor) 534 m.Post("", web.Bind(forms.TwoFactorAuthForm{}), auth.TwoFactorPost) 535 m.Get("/scratch", auth.TwoFactorScratch) 536 m.Post("/scratch", web.Bind(forms.TwoFactorScratchAuthForm{}), auth.TwoFactorScratchPost) 537 }) 538 m.Group("/webauthn", func() { 539 m.Get("", auth.WebAuthn) 540 m.Get("/assertion", auth.WebAuthnLoginAssertion) 541 m.Post("/assertion", auth.WebAuthnLoginAssertionPost) 542 }) 543 }, reqSignOut) 544 545 m.Any("/user/events", routing.MarkLongPolling, events.Events) 546 547 m.Group("/login/oauth", func() { 548 m.Get("/authorize", web.Bind(forms.AuthorizationForm{}), auth.AuthorizeOAuth) 549 m.Post("/grant", web.Bind(forms.GrantApplicationForm{}), auth.GrantApplicationOAuth) 550 // TODO manage redirection 551 m.Post("/authorize", web.Bind(forms.AuthorizationForm{}), auth.AuthorizeOAuth) 552 }, ignSignInAndCsrf, reqSignIn) 553 554 m.Methods("GET, OPTIONS", "/login/oauth/userinfo", optionsCorsHandler(), ignSignInAndCsrf, auth.InfoOAuth) 555 m.Methods("POST, OPTIONS", "/login/oauth/access_token", optionsCorsHandler(), web.Bind(forms.AccessTokenForm{}), ignSignInAndCsrf, auth.AccessTokenOAuth) 556 m.Methods("GET, OPTIONS", "/login/oauth/keys", optionsCorsHandler(), ignSignInAndCsrf, auth.OIDCKeys) 557 m.Methods("POST, OPTIONS", "/login/oauth/introspect", optionsCorsHandler(), web.Bind(forms.IntrospectTokenForm{}), ignSignInAndCsrf, auth.IntrospectOAuth) 558 559 m.Group("/user/settings", func() { 560 m.Get("", user_setting.Profile) 561 m.Post("", web.Bind(forms.UpdateProfileForm{}), user_setting.ProfilePost) 562 m.Get("/change_password", auth.MustChangePassword) 563 m.Post("/change_password", web.Bind(forms.MustChangePasswordForm{}), auth.MustChangePasswordPost) 564 m.Post("/avatar", web.Bind(forms.AvatarForm{}), user_setting.AvatarPost) 565 m.Post("/avatar/delete", user_setting.DeleteAvatar) 566 m.Group("/account", func() { 567 m.Combo("").Get(user_setting.Account).Post(web.Bind(forms.ChangePasswordForm{}), user_setting.AccountPost) 568 m.Post("/email", web.Bind(forms.AddEmailForm{}), user_setting.EmailPost) 569 m.Post("/email/delete", user_setting.DeleteEmail) 570 m.Post("/delete", user_setting.DeleteAccount) 571 }) 572 m.Group("/appearance", func() { 573 m.Get("", user_setting.Appearance) 574 m.Post("/language", web.Bind(forms.UpdateLanguageForm{}), user_setting.UpdateUserLang) 575 m.Post("/hidden_comments", user_setting.UpdateUserHiddenComments) 576 m.Post("/theme", web.Bind(forms.UpdateThemeForm{}), user_setting.UpdateUIThemePost) 577 }) 578 m.Group("/security", func() { 579 m.Get("", security.Security) 580 m.Group("/two_factor", func() { 581 m.Post("/regenerate_scratch", security.RegenerateScratchTwoFactor) 582 m.Post("/disable", security.DisableTwoFactor) 583 m.Get("/enroll", security.EnrollTwoFactor) 584 m.Post("/enroll", web.Bind(forms.TwoFactorAuthForm{}), security.EnrollTwoFactorPost) 585 }) 586 m.Group("/webauthn", func() { 587 m.Post("/request_register", web.Bind(forms.WebauthnRegistrationForm{}), security.WebAuthnRegister) 588 m.Post("/register", security.WebauthnRegisterPost) 589 m.Post("/delete", web.Bind(forms.WebauthnDeleteForm{}), security.WebauthnDelete) 590 }) 591 m.Group("/openid", func() { 592 m.Post("", web.Bind(forms.AddOpenIDForm{}), security.OpenIDPost) 593 m.Post("/delete", security.DeleteOpenID) 594 m.Post("/toggle_visibility", security.ToggleOpenIDVisibility) 595 }, openIDSignInEnabled) 596 m.Post("/account_link", linkAccountEnabled, security.DeleteAccountLink) 597 }) 598 m.Group("/applications/oauth2", func() { 599 m.Get("/{id}", user_setting.OAuth2ApplicationShow) 600 m.Post("/{id}", web.Bind(forms.EditOAuth2ApplicationForm{}), user_setting.OAuthApplicationsEdit) 601 m.Post("/{id}/regenerate_secret", user_setting.OAuthApplicationsRegenerateSecret) 602 m.Post("", web.Bind(forms.EditOAuth2ApplicationForm{}), user_setting.OAuthApplicationsPost) 603 m.Post("/{id}/delete", user_setting.DeleteOAuth2Application) 604 m.Post("/{id}/revoke/{grantId}", user_setting.RevokeOAuth2Grant) 605 }) 606 m.Combo("/applications").Get(user_setting.Applications). 607 Post(web.Bind(forms.NewAccessTokenForm{}), user_setting.ApplicationsPost) 608 m.Post("/applications/delete", user_setting.DeleteApplication) 609 m.Combo("/keys").Get(user_setting.Keys). 610 Post(web.Bind(forms.AddKeyForm{}), user_setting.KeysPost) 611 m.Post("/keys/delete", user_setting.DeleteKey) 612 m.Group("/packages", func() { 613 m.Get("", user_setting.Packages) 614 m.Group("/rules", func() { 615 m.Group("/add", func() { 616 m.Get("", user_setting.PackagesRuleAdd) 617 m.Post("", web.Bind(forms.PackageCleanupRuleForm{}), user_setting.PackagesRuleAddPost) 618 }) 619 m.Group("/{id}", func() { 620 m.Get("", user_setting.PackagesRuleEdit) 621 m.Post("", web.Bind(forms.PackageCleanupRuleForm{}), user_setting.PackagesRuleEditPost) 622 m.Get("/preview", user_setting.PackagesRulePreview) 623 }) 624 }) 625 m.Group("/cargo", func() { 626 m.Post("/initialize", user_setting.InitializeCargoIndex) 627 m.Post("/rebuild", user_setting.RebuildCargoIndex) 628 }) 629 m.Post("/chef/regenerate_keypair", user_setting.RegenerateChefKeyPair) 630 }, packagesEnabled) 631 632 m.Group("/actions", func() { 633 m.Get("", user_setting.RedirectToDefaultSetting) 634 addSettingsRunnersRoutes() 635 addSettingsSecretsRoutes() 636 addSettingsVariablesRoutes() 637 }, actions.MustEnableActions) 638 639 m.Get("/organization", user_setting.Organization) 640 m.Get("/repos", user_setting.Repos) 641 m.Post("/repos/unadopted", user_setting.AdoptOrDeleteRepository) 642 643 m.Group("/hooks", func() { 644 m.Get("", user_setting.Webhooks) 645 m.Post("/delete", user_setting.DeleteWebhook) 646 addWebhookAddRoutes() 647 m.Group("/{id}", func() { 648 m.Get("", repo_setting.WebHooksEdit) 649 m.Post("/replay/{uuid}", repo_setting.ReplayWebhook) 650 }) 651 addWebhookEditRoutes() 652 }, webhooksEnabled) 653 654 m.Group("/blocked_users", func() { 655 m.Get("", user_setting.BlockedUsers) 656 m.Post("", web.Bind(forms.BlockUserForm{}), user_setting.BlockedUsersPost) 657 }) 658 }, reqSignIn, ctxDataSet("PageIsUserSettings", true, "EnablePackages", setting.Packages.Enabled)) 659 660 m.Group("/user", func() { 661 m.Get("/activate", auth.Activate) 662 m.Post("/activate", auth.ActivatePost) 663 m.Any("/activate_email", auth.ActivateEmail) 664 m.Get("/avatar/{username}/{size}", user.AvatarByUserName) 665 m.Get("/recover_account", auth.ResetPasswd) 666 m.Post("/recover_account", auth.ResetPasswdPost) 667 m.Get("/forgot_password", auth.ForgotPasswd) 668 m.Post("/forgot_password", auth.ForgotPasswdPost) 669 m.Post("/logout", auth.SignOut) 670 m.Get("/stopwatches", reqSignIn, user.GetStopwatches) 671 m.Get("/search", ignExploreSignIn, user.Search) 672 m.Group("/oauth2", func() { 673 m.Get("/{provider}", auth.SignInOAuth) 674 m.Get("/{provider}/callback", auth.SignInOAuthCallback) 675 }) 676 }) 677 // ***** END: User ***** 678 679 m.Get("/avatar/{hash}", user.AvatarByEmailHash) 680 681 adminReq := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true, AdminRequired: true}) 682 683 // ***** START: Admin ***** 684 m.Group("/admin", func() { 685 m.Get("", admin.Dashboard) 686 m.Get("/system_status", admin.SystemStatus) 687 m.Post("", web.Bind(forms.AdminDashboardForm{}), admin.DashboardPost) 688 689 m.Get("/self_check", admin.SelfCheck) 690 m.Post("/self_check", admin.SelfCheckPost) 691 692 m.Group("/config", func() { 693 m.Get("", admin.Config) 694 m.Post("", admin.ChangeConfig) 695 m.Post("/test_mail", admin.SendTestMail) 696 m.Get("/settings", admin.ConfigSettings) 697 }) 698 699 m.Group("/monitor", func() { 700 m.Get("/stats", admin.MonitorStats) 701 m.Get("/cron", admin.CronTasks) 702 m.Get("/stacktrace", admin.Stacktrace) 703 m.Post("/stacktrace/cancel/{pid}", admin.StacktraceCancel) 704 m.Get("/queue", admin.Queues) 705 m.Group("/queue/{qid}", func() { 706 m.Get("", admin.QueueManage) 707 m.Post("/set", admin.QueueSet) 708 m.Post("/remove-all-items", admin.QueueRemoveAllItems) 709 }) 710 m.Get("/diagnosis", admin.MonitorDiagnosis) 711 }) 712 713 m.Group("/users", func() { 714 m.Get("", admin.Users) 715 m.Combo("/new").Get(admin.NewUser).Post(web.Bind(forms.AdminCreateUserForm{}), admin.NewUserPost) 716 m.Get("/{userid}", admin.ViewUser) 717 m.Combo("/{userid}/edit").Get(admin.EditUser).Post(web.Bind(forms.AdminEditUserForm{}), admin.EditUserPost) 718 m.Post("/{userid}/delete", admin.DeleteUser) 719 m.Post("/{userid}/avatar", web.Bind(forms.AvatarForm{}), admin.AvatarPost) 720 m.Post("/{userid}/avatar/delete", admin.DeleteAvatar) 721 }) 722 723 m.Group("/emails", func() { 724 m.Get("", admin.Emails) 725 m.Post("/activate", admin.ActivateEmail) 726 }) 727 728 m.Group("/orgs", func() { 729 m.Get("", admin.Organizations) 730 }) 731 732 m.Group("/repos", func() { 733 m.Get("", admin.Repos) 734 m.Combo("/unadopted").Get(admin.UnadoptedRepos).Post(admin.AdoptOrDeleteRepository) 735 m.Post("/delete", admin.DeleteRepo) 736 }) 737 738 m.Group("/packages", func() { 739 m.Get("", admin.Packages) 740 m.Post("/delete", admin.DeletePackageVersion) 741 m.Post("/cleanup", admin.CleanupExpiredData) 742 }, packagesEnabled) 743 744 m.Group("/hooks", func() { 745 m.Get("", admin.DefaultOrSystemWebhooks) 746 m.Post("/delete", admin.DeleteDefaultOrSystemWebhook) 747 m.Group("/{id}", func() { 748 m.Get("", repo_setting.WebHooksEdit) 749 m.Post("/replay/{uuid}", repo_setting.ReplayWebhook) 750 }) 751 addWebhookEditRoutes() 752 }, webhooksEnabled) 753 754 m.Group("/{configType:default-hooks|system-hooks}", func() { 755 addWebhookAddRoutes() 756 }) 757 758 m.Group("/auths", func() { 759 m.Get("", admin.Authentications) 760 m.Combo("/new").Get(admin.NewAuthSource).Post(web.Bind(forms.AuthenticationForm{}), admin.NewAuthSourcePost) 761 m.Combo("/{authid}").Get(admin.EditAuthSource). 762 Post(web.Bind(forms.AuthenticationForm{}), admin.EditAuthSourcePost) 763 m.Post("/{authid}/delete", admin.DeleteAuthSource) 764 }) 765 766 m.Group("/notices", func() { 767 m.Get("", admin.Notices) 768 m.Post("/delete", admin.DeleteNotices) 769 m.Post("/empty", admin.EmptyNotices) 770 }) 771 772 m.Group("/applications", func() { 773 m.Get("", admin.Applications) 774 m.Post("/oauth2", web.Bind(forms.EditOAuth2ApplicationForm{}), admin.ApplicationsPost) 775 m.Group("/oauth2/{id}", func() { 776 m.Combo("").Get(admin.EditApplication).Post(web.Bind(forms.EditOAuth2ApplicationForm{}), admin.EditApplicationPost) 777 m.Post("/regenerate_secret", admin.ApplicationsRegenerateSecret) 778 m.Post("/delete", admin.DeleteApplication) 779 }) 780 }, func(ctx *context.Context) { 781 if !setting.OAuth2.Enabled { 782 ctx.Error(http.StatusForbidden) 783 return 784 } 785 }) 786 787 m.Group("/actions", func() { 788 m.Get("", admin.RedirectToDefaultSetting) 789 addSettingsRunnersRoutes() 790 addSettingsVariablesRoutes() 791 }) 792 }, adminReq, ctxDataSet("EnableOAuth2", setting.OAuth2.Enabled, "EnablePackages", setting.Packages.Enabled)) 793 // ***** END: Admin ***** 794 795 m.Group("", func() { 796 m.Get("/{username}", user.UsernameSubRoute) 797 m.Methods("GET, OPTIONS", "/attachments/{uuid}", optionsCorsHandler(), repo.GetAttachment) 798 }, ignSignIn) 799 800 m.Post("/{username}", reqSignIn, context.UserAssignmentWeb(), user.Action) 801 802 reqRepoAdmin := context.RequireRepoAdmin() 803 reqRepoCodeWriter := context.RequireRepoWriter(unit.TypeCode) 804 canEnableEditor := context.CanEnableEditor() 805 reqRepoCodeReader := context.RequireRepoReader(unit.TypeCode) 806 reqRepoReleaseWriter := context.RequireRepoWriter(unit.TypeReleases) 807 reqRepoReleaseReader := context.RequireRepoReader(unit.TypeReleases) 808 reqRepoWikiReader := context.RequireRepoReader(unit.TypeWiki) 809 reqRepoWikiWriter := context.RequireRepoWriter(unit.TypeWiki) 810 reqRepoIssueReader := context.RequireRepoReader(unit.TypeIssues) 811 reqRepoPullsReader := context.RequireRepoReader(unit.TypePullRequests) 812 reqRepoIssuesOrPullsWriter := context.RequireRepoWriterOr(unit.TypeIssues, unit.TypePullRequests) 813 reqRepoIssuesOrPullsReader := context.RequireRepoReaderOr(unit.TypeIssues, unit.TypePullRequests) 814 reqRepoProjectsReader := context.RequireRepoReader(unit.TypeProjects) 815 reqRepoProjectsWriter := context.RequireRepoWriter(unit.TypeProjects) 816 reqRepoActionsReader := context.RequireRepoReader(unit.TypeActions) 817 reqRepoActionsWriter := context.RequireRepoWriter(unit.TypeActions) 818 819 reqPackageAccess := func(accessMode perm.AccessMode) func(ctx *context.Context) { 820 return func(ctx *context.Context) { 821 if ctx.Package.AccessMode < accessMode && !ctx.IsUserSiteAdmin() { 822 ctx.NotFound("", nil) 823 } 824 } 825 } 826 827 individualPermsChecker := func(ctx *context.Context) { 828 // org permissions have been checked in context.OrgAssignment(), but individual permissions haven't been checked. 829 if ctx.ContextUser.IsIndividual() { 830 switch { 831 case ctx.ContextUser.Visibility == structs.VisibleTypePrivate: 832 if ctx.Doer == nil || (ctx.ContextUser.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin) { 833 ctx.NotFound("Visit Project", nil) 834 return 835 } 836 case ctx.ContextUser.Visibility == structs.VisibleTypeLimited: 837 if ctx.Doer == nil { 838 ctx.NotFound("Visit Project", nil) 839 return 840 } 841 } 842 } 843 } 844 845 m.Group("/org", func() { 846 m.Group("/{org}", func() { 847 m.Get("/members", org.Members) 848 }, context.OrgAssignment()) 849 }, ignSignIn) 850 // end "/org": members 851 852 m.Group("/org", func() { 853 m.Group("", func() { 854 m.Get("/create", org.Create) 855 m.Post("/create", web.Bind(forms.CreateOrgForm{}), org.CreatePost) 856 }) 857 858 m.Group("/invite/{token}", func() { 859 m.Get("", org.TeamInvite) 860 m.Post("", org.TeamInvitePost) 861 }) 862 863 m.Group("/{org}", func() { 864 m.Get("/dashboard", user.Dashboard) 865 m.Get("/dashboard/{team}", user.Dashboard) 866 m.Get("/issues", user.Issues) 867 m.Get("/issues/{team}", user.Issues) 868 m.Get("/pulls", user.Pulls) 869 m.Get("/pulls/{team}", user.Pulls) 870 m.Get("/milestones", reqMilestonesDashboardPageEnabled, user.Milestones) 871 m.Get("/milestones/{team}", reqMilestonesDashboardPageEnabled, user.Milestones) 872 m.Post("/members/action/{action}", org.MembersAction) 873 m.Get("/teams", org.Teams) 874 }, context.OrgAssignment(true, false, true)) 875 876 m.Group("/{org}", func() { 877 m.Get("/teams/{team}", org.TeamMembers) 878 m.Get("/teams/{team}/repositories", org.TeamRepositories) 879 m.Post("/teams/{team}/action/{action}", org.TeamsAction) 880 m.Post("/teams/{team}/action/repo/{action}", org.TeamsRepoAction) 881 }, context.OrgAssignment(true, false, true)) 882 883 // require admin permission 884 m.Group("/{org}", func() { 885 m.Get("/teams/-/search", org.SearchTeam) 886 }, context.OrgAssignment(true, false, false, true)) 887 888 // require owner permission 889 m.Group("/{org}", func() { 890 m.Get("/teams/new", org.NewTeam) 891 m.Post("/teams/new", web.Bind(forms.CreateTeamForm{}), org.NewTeamPost) 892 m.Get("/teams/{team}/edit", org.EditTeam) 893 m.Post("/teams/{team}/edit", web.Bind(forms.CreateTeamForm{}), org.EditTeamPost) 894 m.Post("/teams/{team}/delete", org.DeleteTeam) 895 896 m.Group("/settings", func() { 897 m.Combo("").Get(org.Settings). 898 Post(web.Bind(forms.UpdateOrgSettingForm{}), org.SettingsPost) 899 m.Post("/avatar", web.Bind(forms.AvatarForm{}), org.SettingsAvatar) 900 m.Post("/avatar/delete", org.SettingsDeleteAvatar) 901 m.Group("/applications", func() { 902 m.Get("", org.Applications) 903 m.Post("/oauth2", web.Bind(forms.EditOAuth2ApplicationForm{}), org.OAuthApplicationsPost) 904 m.Group("/oauth2/{id}", func() { 905 m.Combo("").Get(org.OAuth2ApplicationShow).Post(web.Bind(forms.EditOAuth2ApplicationForm{}), org.OAuth2ApplicationEdit) 906 m.Post("/regenerate_secret", org.OAuthApplicationsRegenerateSecret) 907 m.Post("/delete", org.DeleteOAuth2Application) 908 }) 909 }, func(ctx *context.Context) { 910 if !setting.OAuth2.Enabled { 911 ctx.Error(http.StatusForbidden) 912 return 913 } 914 }) 915 916 m.Group("/hooks", func() { 917 m.Get("", org.Webhooks) 918 m.Post("/delete", org.DeleteWebhook) 919 addWebhookAddRoutes() 920 m.Group("/{id}", func() { 921 m.Get("", repo_setting.WebHooksEdit) 922 m.Post("/replay/{uuid}", repo_setting.ReplayWebhook) 923 }) 924 addWebhookEditRoutes() 925 }, webhooksEnabled) 926 927 m.Group("/labels", func() { 928 m.Get("", org.RetrieveLabels, org.Labels) 929 m.Post("/new", web.Bind(forms.CreateLabelForm{}), org.NewLabel) 930 m.Post("/edit", web.Bind(forms.CreateLabelForm{}), org.UpdateLabel) 931 m.Post("/delete", org.DeleteLabel) 932 m.Post("/initialize", web.Bind(forms.InitializeLabelsForm{}), org.InitializeLabels) 933 }) 934 935 m.Group("/actions", func() { 936 m.Get("", org_setting.RedirectToDefaultSetting) 937 addSettingsRunnersRoutes() 938 addSettingsSecretsRoutes() 939 addSettingsVariablesRoutes() 940 }, actions.MustEnableActions) 941 942 m.Methods("GET,POST", "/delete", org.SettingsDelete) 943 944 m.Group("/packages", func() { 945 m.Get("", org.Packages) 946 m.Group("/rules", func() { 947 m.Group("/add", func() { 948 m.Get("", org.PackagesRuleAdd) 949 m.Post("", web.Bind(forms.PackageCleanupRuleForm{}), org.PackagesRuleAddPost) 950 }) 951 m.Group("/{id}", func() { 952 m.Get("", org.PackagesRuleEdit) 953 m.Post("", web.Bind(forms.PackageCleanupRuleForm{}), org.PackagesRuleEditPost) 954 m.Get("/preview", org.PackagesRulePreview) 955 }) 956 }) 957 m.Group("/cargo", func() { 958 m.Post("/initialize", org.InitializeCargoIndex) 959 m.Post("/rebuild", org.RebuildCargoIndex) 960 }) 961 }, packagesEnabled) 962 963 m.Group("/blocked_users", func() { 964 m.Get("", org.BlockedUsers) 965 m.Post("", web.Bind(forms.BlockUserForm{}), org.BlockedUsersPost) 966 }) 967 }, ctxDataSet("EnableOAuth2", setting.OAuth2.Enabled, "EnablePackages", setting.Packages.Enabled, "PageIsOrgSettings", true)) 968 }, context.OrgAssignment(true, true)) 969 }, reqSignIn) 970 // end "/org": most org routes 971 972 m.Group("/repo", func() { 973 m.Get("/create", repo.Create) 974 m.Post("/create", web.Bind(forms.CreateRepoForm{}), repo.CreatePost) 975 m.Get("/migrate", repo.Migrate) 976 m.Post("/migrate", web.Bind(forms.MigrateRepoForm{}), repo.MigratePost) 977 m.Get("/search", repo.SearchRepo) 978 }, reqSignIn) 979 // end "/repo": create, migrate, search 980 981 m.Group("/{username}/-", func() { 982 if setting.Packages.Enabled { 983 m.Group("/packages", func() { 984 m.Get("", user.ListPackages) 985 m.Group("/{type}/{name}", func() { 986 m.Get("", user.RedirectToLastVersion) 987 m.Get("/versions", user.ListPackageVersions) 988 m.Group("/{version}", func() { 989 m.Get("", user.ViewPackageVersion) 990 m.Get("/files/{fileid}", user.DownloadPackageFile) 991 m.Group("/settings", func() { 992 m.Get("", user.PackageSettings) 993 m.Post("", web.Bind(forms.PackageSettingForm{}), user.PackageSettingsPost) 994 }, reqPackageAccess(perm.AccessModeWrite)) 995 }) 996 }) 997 }, context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead)) 998 } 999 1000 m.Group("/projects", func() { 1001 m.Group("", func() { 1002 m.Get("", org.Projects) 1003 m.Get("/{id}", org.ViewProject) 1004 }, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true)) 1005 m.Group("", func() { //nolint:dupl 1006 m.Get("/new", org.RenderNewProject) 1007 m.Post("/new", web.Bind(forms.CreateProjectForm{}), org.NewProjectPost) 1008 m.Group("/{id}", func() { 1009 m.Post("", web.Bind(forms.EditProjectBoardForm{}), org.AddBoardToProjectPost) 1010 m.Post("/move", project.MoveColumns) 1011 m.Post("/delete", org.DeleteProject) 1012 1013 m.Get("/edit", org.RenderEditProject) 1014 m.Post("/edit", web.Bind(forms.CreateProjectForm{}), org.EditProjectPost) 1015 m.Post("/{action:open|close}", org.ChangeProjectStatus) 1016 1017 m.Group("/{boardID}", func() { 1018 m.Put("", web.Bind(forms.EditProjectBoardForm{}), org.EditProjectBoard) 1019 m.Delete("", org.DeleteProjectBoard) 1020 m.Post("/default", org.SetDefaultProjectBoard) 1021 m.Post("/move", org.MoveIssues) 1022 }) 1023 }) 1024 }, reqSignIn, reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true), func(ctx *context.Context) { 1025 if ctx.ContextUser.IsIndividual() && ctx.ContextUser.ID != ctx.Doer.ID { 1026 ctx.NotFound("NewProject", nil) 1027 return 1028 } 1029 }) 1030 }, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true), individualPermsChecker) 1031 1032 m.Group("", func() { 1033 m.Get("/code", user.CodeSearch) 1034 }, reqUnitAccess(unit.TypeCode, perm.AccessModeRead, false), individualPermsChecker) 1035 }, ignSignIn, context.UserAssignmentWeb(), context.OrgAssignment()) 1036 // end "/{username}/-": packages, projects, code 1037 1038 m.Group("/{username}/{reponame}/-", func() { 1039 m.Group("/migrate", func() { 1040 m.Get("/status", repo.MigrateStatus) 1041 }) 1042 }, ignSignIn, context.RepoAssignment, reqRepoCodeReader) 1043 // end "/{username}/{reponame}/-": migrate 1044 1045 m.Group("/{username}/{reponame}/settings", func() { 1046 m.Group("", func() { 1047 m.Combo("").Get(repo_setting.Settings). 1048 Post(web.Bind(forms.RepoSettingForm{}), repo_setting.SettingsPost) 1049 }, repo_setting.SettingsCtxData) 1050 m.Post("/avatar", web.Bind(forms.AvatarForm{}), repo_setting.SettingsAvatar) 1051 m.Post("/avatar/delete", repo_setting.SettingsDeleteAvatar) 1052 1053 m.Group("/collaboration", func() { 1054 m.Combo("").Get(repo_setting.Collaboration).Post(repo_setting.CollaborationPost) 1055 m.Post("/access_mode", repo_setting.ChangeCollaborationAccessMode) 1056 m.Post("/delete", repo_setting.DeleteCollaboration) 1057 m.Group("/team", func() { 1058 m.Post("", repo_setting.AddTeamPost) 1059 m.Post("/delete", repo_setting.DeleteTeam) 1060 }) 1061 }) 1062 1063 m.Group("/branches", func() { 1064 m.Post("/", repo_setting.SetDefaultBranchPost) 1065 }, repo.MustBeNotEmpty) 1066 1067 m.Group("/branches", func() { 1068 m.Get("/", repo_setting.ProtectedBranchRules) 1069 m.Combo("/edit").Get(repo_setting.SettingsProtectedBranch). 1070 Post(web.Bind(forms.ProtectBranchForm{}), context.RepoMustNotBeArchived(), repo_setting.SettingsProtectedBranchPost) 1071 m.Post("/{id}/delete", repo_setting.DeleteProtectedBranchRulePost) 1072 }) 1073 1074 m.Group("/tags", func() { 1075 m.Get("", repo_setting.ProtectedTags) 1076 m.Post("", web.Bind(forms.ProtectTagForm{}), context.RepoMustNotBeArchived(), repo_setting.NewProtectedTagPost) 1077 m.Post("/delete", context.RepoMustNotBeArchived(), repo_setting.DeleteProtectedTagPost) 1078 m.Get("/{id}", repo_setting.EditProtectedTag) 1079 m.Post("/{id}", web.Bind(forms.ProtectTagForm{}), context.RepoMustNotBeArchived(), repo_setting.EditProtectedTagPost) 1080 }) 1081 1082 m.Group("/hooks/git", func() { 1083 m.Get("", repo_setting.GitHooks) 1084 m.Combo("/{name}").Get(repo_setting.GitHooksEdit). 1085 Post(repo_setting.GitHooksEditPost) 1086 }, context.GitHookService()) 1087 1088 m.Group("/hooks", func() { 1089 m.Get("", repo_setting.Webhooks) 1090 m.Post("/delete", repo_setting.DeleteWebhook) 1091 addWebhookAddRoutes() 1092 m.Group("/{id}", func() { 1093 m.Get("", repo_setting.WebHooksEdit) 1094 m.Post("/test", repo_setting.TestWebhook) 1095 m.Post("/replay/{uuid}", repo_setting.ReplayWebhook) 1096 }) 1097 addWebhookEditRoutes() 1098 }, webhooksEnabled) 1099 1100 m.Group("/keys", func() { 1101 m.Combo("").Get(repo_setting.DeployKeys). 1102 Post(web.Bind(forms.AddKeyForm{}), repo_setting.DeployKeysPost) 1103 m.Post("/delete", repo_setting.DeleteDeployKey) 1104 }) 1105 1106 m.Group("/lfs", func() { 1107 m.Get("/", repo_setting.LFSFiles) 1108 m.Get("/show/{oid}", repo_setting.LFSFileGet) 1109 m.Post("/delete/{oid}", repo_setting.LFSDelete) 1110 m.Get("/pointers", repo_setting.LFSPointerFiles) 1111 m.Post("/pointers/associate", repo_setting.LFSAutoAssociate) 1112 m.Get("/find", repo_setting.LFSFileFind) 1113 m.Group("/locks", func() { 1114 m.Get("/", repo_setting.LFSLocks) 1115 m.Post("/", repo_setting.LFSLockFile) 1116 m.Post("/{lid}/unlock", repo_setting.LFSUnlock) 1117 }) 1118 }) 1119 m.Group("/actions", func() { 1120 m.Get("", repo_setting.RedirectToDefaultSetting) 1121 addSettingsRunnersRoutes() 1122 addSettingsSecretsRoutes() 1123 addSettingsVariablesRoutes() 1124 }, actions.MustEnableActions) 1125 // the follow handler must be under "settings", otherwise this incomplete repo can't be accessed 1126 m.Group("/migrate", func() { 1127 m.Post("/retry", repo.MigrateRetryPost) 1128 m.Post("/cancel", repo.MigrateCancelPost) 1129 }) 1130 }, 1131 reqSignIn, context.RepoAssignment, reqRepoAdmin, context.RepoRef(), 1132 ctxDataSet("PageIsRepoSettings", true, "LFSStartServer", setting.LFS.StartServer), 1133 ) 1134 // end "/{username}/{reponame}/settings" 1135 1136 // user/org home, including rss feeds 1137 m.Get("/{username}/{reponame}", ignSignIn, context.RepoAssignment, context.RepoRef(), repo.SetEditorconfigIfExists, repo.Home) 1138 1139 // TODO: maybe it should relax the permission to allow "any access" 1140 m.Post("/{username}/{reponame}/markup", ignSignIn, context.RepoAssignment, context.RequireRepoReaderOr(unit.TypeCode, unit.TypeIssues, unit.TypePullRequests, unit.TypeReleases, unit.TypeWiki), web.Bind(structs.MarkupOption{}), misc.Markup) 1141 1142 m.Group("/{username}/{reponame}", func() { 1143 m.Get("/find/*", repo.FindFiles) 1144 m.Group("/tree-list", func() { 1145 m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.TreeList) 1146 m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.TreeList) 1147 m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.TreeList) 1148 }) 1149 m.Get("/compare", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff) 1150 m.Combo("/compare/*", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists). 1151 Get(repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff). 1152 Post(reqSignIn, context.RepoMustNotBeArchived(), reqRepoPullsReader, repo.MustAllowPulls, web.Bind(forms.CreateIssueForm{}), repo.SetWhitespaceBehavior, repo.CompareAndPullRequestPost) 1153 }, ignSignIn, context.RepoAssignment, reqRepoCodeReader) 1154 // end "/{username}/{reponame}": find, compare, list (code related) 1155 1156 m.Group("/{username}/{reponame}", func() { 1157 m.Get("/issues/posters", repo.IssuePosters) // it can't use {type:issues|pulls} because it would conflict with other routes like "/pulls/{index}" 1158 m.Get("/pulls/posters", repo.PullPosters) 1159 m.Get("/comments/{id}/attachments", repo.GetCommentAttachments) 1160 m.Get("/labels", repo.RetrieveLabels, repo.Labels) 1161 m.Get("/milestones", repo.Milestones) 1162 m.Get("/milestone/{id}", context.RepoRef(), repo.MilestoneIssuesAndPulls) 1163 m.Group("/{type:issues|pulls}", func() { 1164 m.Group("/{index}", func() { 1165 m.Get("/info", repo.GetIssueInfo) 1166 m.Get("/attachments", repo.GetIssueAttachments) 1167 m.Get("/attachments/{uuid}", repo.GetAttachment) 1168 m.Group("/content-history", func() { 1169 m.Get("/overview", repo.GetContentHistoryOverview) 1170 m.Get("/list", repo.GetContentHistoryList) 1171 m.Get("/detail", repo.GetContentHistoryDetail) 1172 }) 1173 }) 1174 }, context.RepoRef()) 1175 }, ignSignIn, context.RepoAssignment, reqRepoIssuesOrPullsReader) 1176 // end "/{username}/{reponame}": view milestone, label, issue, pull, etc 1177 1178 m.Group("/{username}/{reponame}", func() { 1179 m.Group("/{type:issues|pulls}", func() { 1180 m.Get("", repo.Issues) 1181 m.Group("/{index}", func() { 1182 m.Get("", repo.ViewIssue) 1183 }) 1184 }) 1185 }, ignSignIn, context.RepoAssignment, context.RequireRepoReaderOr(unit.TypeIssues, unit.TypePullRequests, unit.TypeExternalTracker)) 1186 // end "/{username}/{reponame}": issue/pull list, issue/pull view, external tracker 1187 1188 m.Group("/{username}/{reponame}", func() { // edit issues, pulls, labels, milestones, etc 1189 m.Group("/issues", func() { 1190 m.Group("/new", func() { 1191 m.Combo("").Get(context.RepoRef(), repo.NewIssue). 1192 Post(web.Bind(forms.CreateIssueForm{}), repo.NewIssuePost) 1193 m.Get("/choose", context.RepoRef(), repo.NewIssueChooseTemplate) 1194 }) 1195 m.Get("/search", repo.ListIssues) 1196 }, context.RepoMustNotBeArchived(), reqRepoIssueReader) 1197 1198 // FIXME: should use different URLs but mostly same logic for comments of issue and pull request. 1199 // So they can apply their own enable/disable logic on routers. 1200 m.Group("/{type:issues|pulls}", func() { 1201 m.Group("/{index}", func() { 1202 m.Post("/title", repo.UpdateIssueTitle) 1203 m.Post("/content", repo.UpdateIssueContent) 1204 m.Post("/deadline", web.Bind(structs.EditDeadlineOption{}), repo.UpdateIssueDeadline) 1205 m.Post("/watch", repo.IssueWatch) 1206 m.Post("/ref", repo.UpdateIssueRef) 1207 m.Post("/pin", reqRepoAdmin, repo.IssuePinOrUnpin) 1208 m.Post("/viewed-files", repo.UpdateViewedFiles) 1209 m.Group("/dependency", func() { 1210 m.Post("/add", repo.AddDependency) 1211 m.Post("/delete", repo.RemoveDependency) 1212 }) 1213 m.Combo("/comments").Post(repo.MustAllowUserComment, web.Bind(forms.CreateCommentForm{}), repo.NewComment) 1214 m.Group("/times", func() { 1215 m.Post("/add", web.Bind(forms.AddTimeManuallyForm{}), repo.AddTimeManually) 1216 m.Post("/{timeid}/delete", repo.DeleteTime) 1217 m.Group("/stopwatch", func() { 1218 m.Post("/toggle", repo.IssueStopwatch) 1219 m.Post("/cancel", repo.CancelStopwatch) 1220 }) 1221 }) 1222 m.Post("/reactions/{action}", web.Bind(forms.ReactionForm{}), repo.ChangeIssueReaction) 1223 m.Post("/lock", reqRepoIssuesOrPullsWriter, web.Bind(forms.IssueLockForm{}), repo.LockIssue) 1224 m.Post("/unlock", reqRepoIssuesOrPullsWriter, repo.UnlockIssue) 1225 m.Post("/delete", reqRepoAdmin, repo.DeleteIssue) 1226 }, context.RepoMustNotBeArchived()) 1227 1228 m.Group("/{index}", func() { 1229 m.Post("/content-history/soft-delete", repo.SoftDeleteContentHistory) 1230 }) 1231 1232 m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel) 1233 m.Post("/milestone", reqRepoIssuesOrPullsWriter, repo.UpdateIssueMilestone) 1234 m.Post("/projects", reqRepoIssuesOrPullsWriter, reqRepoProjectsReader, repo.UpdateIssueProject) 1235 m.Post("/assignee", reqRepoIssuesOrPullsWriter, repo.UpdateIssueAssignee) 1236 m.Post("/request_review", repo.UpdatePullReviewRequest) 1237 m.Post("/dismiss_review", reqRepoAdmin, web.Bind(forms.DismissReviewForm{}), repo.DismissReview) 1238 m.Post("/status", reqRepoIssuesOrPullsWriter, repo.UpdateIssueStatus) 1239 m.Post("/delete", reqRepoAdmin, repo.BatchDeleteIssues) 1240 m.Post("/resolve_conversation", repo.SetShowOutdatedComments, repo.UpdateResolveConversation) 1241 m.Post("/attachments", repo.UploadIssueAttachment) 1242 m.Post("/attachments/remove", repo.DeleteAttachment) 1243 m.Delete("/unpin/{index}", reqRepoAdmin, repo.IssueUnpin) 1244 m.Post("/move_pin", reqRepoAdmin, repo.IssuePinMove) 1245 }, context.RepoMustNotBeArchived()) 1246 1247 m.Group("/comments/{id}", func() { 1248 m.Post("", repo.UpdateCommentContent) 1249 m.Post("/delete", repo.DeleteComment) 1250 m.Post("/reactions/{action}", web.Bind(forms.ReactionForm{}), repo.ChangeCommentReaction) 1251 }, context.RepoMustNotBeArchived()) 1252 1253 m.Group("/labels", func() { 1254 m.Post("/new", web.Bind(forms.CreateLabelForm{}), repo.NewLabel) 1255 m.Post("/edit", web.Bind(forms.CreateLabelForm{}), repo.UpdateLabel) 1256 m.Post("/delete", repo.DeleteLabel) 1257 m.Post("/initialize", web.Bind(forms.InitializeLabelsForm{}), repo.InitializeLabels) 1258 }, context.RepoMustNotBeArchived(), reqRepoIssuesOrPullsWriter, context.RepoRef()) 1259 m.Group("/milestones", func() { 1260 m.Combo("/new").Get(repo.NewMilestone). 1261 Post(web.Bind(forms.CreateMilestoneForm{}), repo.NewMilestonePost) 1262 m.Get("/{id}/edit", repo.EditMilestone) 1263 m.Post("/{id}/edit", web.Bind(forms.CreateMilestoneForm{}), repo.EditMilestonePost) 1264 m.Post("/{id}/{action}", repo.ChangeMilestoneStatus) 1265 m.Post("/delete", repo.DeleteMilestone) 1266 }, context.RepoMustNotBeArchived(), reqRepoIssuesOrPullsWriter, context.RepoRef()) 1267 m.Group("/pull", func() { 1268 m.Post("/{index}/target_branch", repo.UpdatePullRequestTarget) 1269 }, context.RepoMustNotBeArchived()) 1270 }, reqSignIn, context.RepoAssignment, reqRepoIssuesOrPullsReader) 1271 // end "/{username}/{reponame}": create or edit issues, pulls, labels, milestones 1272 1273 m.Group("/{username}/{reponame}", func() { // repo code 1274 m.Group("", func() { 1275 m.Group("", func() { 1276 m.Combo("/_edit/*").Get(repo.EditFile). 1277 Post(web.Bind(forms.EditRepoFileForm{}), repo.EditFilePost) 1278 m.Combo("/_new/*").Get(repo.NewFile). 1279 Post(web.Bind(forms.EditRepoFileForm{}), repo.NewFilePost) 1280 m.Post("/_preview/*", web.Bind(forms.EditPreviewDiffForm{}), repo.DiffPreviewPost) 1281 m.Combo("/_delete/*").Get(repo.DeleteFile). 1282 Post(web.Bind(forms.DeleteRepoFileForm{}), repo.DeleteFilePost) 1283 m.Combo("/_upload/*", repo.MustBeAbleToUpload). 1284 Get(repo.UploadFile). 1285 Post(web.Bind(forms.UploadRepoFileForm{}), repo.UploadFilePost) 1286 m.Combo("/_diffpatch/*").Get(repo.NewDiffPatch). 1287 Post(web.Bind(forms.EditRepoFileForm{}), repo.NewDiffPatchPost) 1288 m.Combo("/_cherrypick/{sha:([a-f0-9]{7,64})}/*").Get(repo.CherryPick). 1289 Post(web.Bind(forms.CherryPickForm{}), repo.CherryPickPost) 1290 }, repo.MustBeEditable) 1291 m.Group("", func() { 1292 m.Post("/upload-file", repo.UploadFileToServer) 1293 m.Post("/upload-remove", web.Bind(forms.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer) 1294 }, repo.MustBeEditable, repo.MustBeAbleToUpload) 1295 }, context.RepoRef(), canEnableEditor, context.RepoMustNotBeArchived()) 1296 1297 m.Group("/branches", func() { 1298 m.Group("/_new", func() { 1299 m.Post("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.CreateBranch) 1300 m.Post("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.CreateBranch) 1301 m.Post("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.CreateBranch) 1302 }, web.Bind(forms.NewBranchForm{})) 1303 m.Post("/delete", repo.DeleteBranchPost) 1304 m.Post("/restore", repo.RestoreBranchPost) 1305 m.Post("/rename", web.Bind(forms.RenameBranchForm{}), repo_setting.RenameBranchPost) 1306 }, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty) 1307 1308 m.Combo("/fork").Get(repo.Fork).Post(web.Bind(forms.CreateRepoForm{}), repo.ForkPost) 1309 }, reqSignIn, context.RepoAssignment, reqRepoCodeReader) 1310 // end "/{username}/{reponame}": repo code 1311 1312 m.Group("/{username}/{reponame}", func() { // repo tags 1313 m.Group("/tags", func() { 1314 m.Get("", repo.TagsList) 1315 m.Get("/list", repo.GetTagList) 1316 m.Get(".rss", feedEnabled, repo.TagsListFeedRSS) 1317 m.Get(".atom", feedEnabled, repo.TagsListFeedAtom) 1318 }, ctxDataSet("EnableFeed", setting.Other.EnableFeed), 1319 repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag, true)) 1320 m.Post("/tags/delete", repo.DeleteTag, reqSignIn, 1321 repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoCodeWriter, context.RepoRef()) 1322 }, ignSignIn, context.RepoAssignment, reqRepoCodeReader) 1323 // end "/{username}/{reponame}": repo tags 1324 1325 m.Group("/{username}/{reponame}", func() { // repo releases 1326 m.Group("/releases", func() { 1327 m.Get("", repo.Releases) 1328 m.Get("/tag/*", repo.SingleRelease) 1329 m.Get("/latest", repo.LatestRelease) 1330 m.Get(".rss", feedEnabled, repo.ReleasesFeedRSS) 1331 m.Get(".atom", feedEnabled, repo.ReleasesFeedAtom) 1332 }, ctxDataSet("EnableFeed", setting.Other.EnableFeed), 1333 repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag, true)) 1334 m.Get("/releases/attachments/{uuid}", repo.MustBeNotEmpty, repo.GetAttachment) 1335 m.Get("/releases/download/{vTag}/{fileName}", repo.MustBeNotEmpty, repo.RedirectDownload) 1336 m.Group("/releases", func() { 1337 m.Get("/new", repo.NewRelease) 1338 m.Post("/new", web.Bind(forms.NewReleaseForm{}), repo.NewReleasePost) 1339 m.Post("/delete", repo.DeleteRelease) 1340 m.Post("/attachments", repo.UploadReleaseAttachment) 1341 m.Post("/attachments/remove", repo.DeleteAttachment) 1342 }, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, context.RepoRef()) 1343 m.Group("/releases", func() { 1344 m.Get("/edit/*", repo.EditRelease) 1345 m.Post("/edit/*", web.Bind(forms.EditReleaseForm{}), repo.EditReleasePost) 1346 }, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, repo.CommitInfoCache) 1347 }, ignSignIn, context.RepoAssignment, reqRepoReleaseReader) 1348 // end "/{username}/{reponame}": repo releases 1349 1350 m.Group("/{username}/{reponame}", func() { // to maintain compatibility with old attachments 1351 m.Get("/attachments/{uuid}", repo.GetAttachment) 1352 }, ignSignIn, context.RepoAssignment) 1353 // end "/{username}/{reponame}": compatibility with old attachments 1354 1355 m.Group("/{username}/{reponame}", func() { 1356 m.Post("/topics", repo.TopicsPost) 1357 }, context.RepoAssignment, reqRepoAdmin, context.RepoMustNotBeArchived()) 1358 1359 m.Group("/{username}/{reponame}", func() { 1360 if setting.Packages.Enabled { 1361 m.Get("/packages", repo.Packages) 1362 } 1363 }, ignSignIn, context.RepoAssignment) 1364 1365 m.Group("/{username}/{reponame}/projects", func() { 1366 m.Get("", repo.Projects) 1367 m.Get("/{id}", repo.ViewProject) 1368 m.Group("", func() { //nolint:dupl 1369 m.Get("/new", repo.RenderNewProject) 1370 m.Post("/new", web.Bind(forms.CreateProjectForm{}), repo.NewProjectPost) 1371 m.Group("/{id}", func() { 1372 m.Post("", web.Bind(forms.EditProjectBoardForm{}), repo.AddBoardToProjectPost) 1373 m.Post("/move", project.MoveColumns) 1374 m.Post("/delete", repo.DeleteProject) 1375 1376 m.Get("/edit", repo.RenderEditProject) 1377 m.Post("/edit", web.Bind(forms.CreateProjectForm{}), repo.EditProjectPost) 1378 m.Post("/{action:open|close}", repo.ChangeProjectStatus) 1379 1380 m.Group("/{boardID}", func() { 1381 m.Put("", web.Bind(forms.EditProjectBoardForm{}), repo.EditProjectBoard) 1382 m.Delete("", repo.DeleteProjectBoard) 1383 m.Post("/default", repo.SetDefaultProjectBoard) 1384 m.Post("/move", repo.MoveIssues) 1385 }) 1386 }) 1387 }, reqRepoProjectsWriter, context.RepoMustNotBeArchived()) 1388 }, ignSignIn, context.RepoAssignment, reqRepoProjectsReader, repo.MustEnableRepoProjects) 1389 // end "/{username}/{reponame}/projects" 1390 1391 m.Group("/{username}/{reponame}/actions", func() { 1392 m.Get("", actions.List) 1393 m.Post("/disable", reqRepoAdmin, actions.DisableWorkflowFile) 1394 m.Post("/enable", reqRepoAdmin, actions.EnableWorkflowFile) 1395 1396 m.Group("/runs/{run}", func() { 1397 m.Combo(""). 1398 Get(actions.View). 1399 Post(web.Bind(actions.ViewRequest{}), actions.ViewPost) 1400 m.Group("/jobs/{job}", func() { 1401 m.Combo(""). 1402 Get(actions.View). 1403 Post(web.Bind(actions.ViewRequest{}), actions.ViewPost) 1404 m.Post("/rerun", reqRepoActionsWriter, actions.Rerun) 1405 m.Get("/logs", actions.Logs) 1406 }) 1407 m.Post("/cancel", reqRepoActionsWriter, actions.Cancel) 1408 m.Post("/approve", reqRepoActionsWriter, actions.Approve) 1409 m.Get("/artifacts", actions.ArtifactsView) 1410 m.Get("/artifacts/{artifact_name}", actions.ArtifactsDownloadView) 1411 m.Delete("/artifacts/{artifact_name}", actions.ArtifactsDeleteView) 1412 m.Post("/rerun", reqRepoActionsWriter, actions.Rerun) 1413 }) 1414 m.Group("/workflows/{workflow_name}", func() { 1415 m.Get("/badge.svg", actions.GetWorkflowBadge) 1416 }) 1417 }, ignSignIn, context.RepoAssignment, reqRepoActionsReader, actions.MustEnableActions) 1418 // end "/{username}/{reponame}/actions" 1419 1420 m.Group("/{username}/{reponame}/wiki", func() { 1421 m.Combo(""). 1422 Get(repo.Wiki). 1423 Post(context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost) 1424 m.Combo("/*"). 1425 Get(repo.Wiki). 1426 Post(context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost) 1427 m.Get("/commit/{sha:[a-f0-9]{7,64}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff) 1428 m.Get("/commit/{sha:[a-f0-9]{7,64}}.{ext:patch|diff}", repo.RawDiff) 1429 m.Get("/raw/*", repo.WikiRaw) 1430 }, ignSignIn, context.RepoAssignment, repo.MustEnableWiki, reqRepoWikiReader, func(ctx *context.Context) { 1431 ctx.Data["PageIsWiki"] = true 1432 ctx.Data["CloneButtonOriginLink"] = ctx.Repo.Repository.WikiCloneLink() 1433 }) 1434 // end "/{username}/{reponame}/wiki" 1435 1436 m.Group("/{username}/{reponame}/activity", func() { 1437 m.Get("", repo.Activity) 1438 m.Get("/{period}", repo.Activity) 1439 m.Group("/contributors", func() { 1440 m.Get("", repo.Contributors) 1441 m.Get("/data", repo.ContributorsData) 1442 }) 1443 m.Group("/code-frequency", func() { 1444 m.Get("", repo.CodeFrequency) 1445 m.Get("/data", repo.CodeFrequencyData) 1446 }) 1447 m.Group("/recent-commits", func() { 1448 m.Get("", repo.RecentCommits) 1449 m.Get("/data", repo.RecentCommitsData) 1450 }) 1451 }, 1452 ignSignIn, context.RepoAssignment, context.RequireRepoReaderOr(unit.TypePullRequests, unit.TypeIssues, unit.TypeReleases), 1453 context.RepoRef(), repo.MustBeNotEmpty, 1454 ) 1455 // end "/{username}/{reponame}/activity" 1456 1457 m.Group("/{username}/{reponame}", func() { 1458 m.Group("/activity_author_data", func() { 1459 m.Get("", repo.ActivityAuthors) 1460 m.Get("/{period}", repo.ActivityAuthors) 1461 }, context.RepoRef(), repo.MustBeNotEmpty) 1462 1463 m.Group("/archive", func() { 1464 m.Get("/*", repo.Download) 1465 m.Post("/*", repo.InitiateDownload) 1466 }, repo.MustBeNotEmpty, dlSourceEnabled) 1467 1468 m.Group("/branches", func() { 1469 m.Get("/list", repo.GetBranchesList) 1470 m.Get("", repo.Branches) 1471 }, repo.MustBeNotEmpty, context.RepoRef()) 1472 1473 m.Group("/blob_excerpt", func() { 1474 m.Get("/{sha}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ExcerptBlob) 1475 }, func(ctx *context.Context) gocontext.CancelFunc { 1476 // FIXME: refactor this function, use separate routes for wiki/code 1477 if ctx.FormBool("wiki") { 1478 ctx.Data["PageIsWiki"] = true 1479 repo.MustEnableWiki(ctx) 1480 return nil 1481 } 1482 1483 if ctx.Written() { 1484 return nil 1485 } 1486 cancel := context.RepoRef()(ctx) 1487 if ctx.Written() { 1488 return cancel 1489 } 1490 1491 repo.MustBeNotEmpty(ctx) 1492 return cancel 1493 }) 1494 1495 m.Group("/pulls/{index}", func() { 1496 m.Get("", repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewIssue) 1497 m.Get(".diff", repo.DownloadPullDiff) 1498 m.Get(".patch", repo.DownloadPullPatch) 1499 m.Group("/commits", func() { 1500 m.Get("", context.RepoRef(), repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits) 1501 m.Get("/list", context.RepoRef(), repo.GetPullCommits) 1502 m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit) 1503 }) 1504 m.Post("/merge", context.RepoMustNotBeArchived(), web.Bind(forms.MergePullRequestForm{}), repo.MergePullRequest) 1505 m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest) 1506 m.Post("/update", repo.UpdatePullRequest) 1507 m.Post("/set_allow_maintainer_edit", web.Bind(forms.UpdateAllowEditsForm{}), repo.SetAllowEdits) 1508 m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest) 1509 m.Group("/files", func() { 1510 m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForAllCommitsOfPr) 1511 m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesStartingFromCommit) 1512 m.Get("/{shaFrom:[a-f0-9]{7,40}}..{shaTo:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange) 1513 m.Group("/reviews", func() { 1514 m.Get("/new_comment", repo.RenderNewCodeCommentForm) 1515 m.Post("/comments", web.Bind(forms.CodeCommentForm{}), repo.SetShowOutdatedComments, repo.CreateCodeComment) 1516 m.Post("/submit", web.Bind(forms.SubmitReviewForm{}), repo.SubmitReview) 1517 }, context.RepoMustNotBeArchived()) 1518 }) 1519 }, repo.MustAllowPulls) 1520 1521 m.Group("/media", func() { 1522 m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.SingleDownloadOrLFS) 1523 m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.SingleDownloadOrLFS) 1524 m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.SingleDownloadOrLFS) 1525 m.Get("/blob/{sha}", context.RepoRefByType(context.RepoRefBlob), repo.DownloadByIDOrLFS) 1526 // "/*" route is deprecated, and kept for backward compatibility 1527 m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.SingleDownloadOrLFS) 1528 }, repo.MustBeNotEmpty) 1529 1530 m.Group("/raw", func() { 1531 m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.SingleDownload) 1532 m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.SingleDownload) 1533 m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.SingleDownload) 1534 m.Get("/blob/{sha}", context.RepoRefByType(context.RepoRefBlob), repo.DownloadByID) 1535 // "/*" route is deprecated, and kept for backward compatibility 1536 m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.SingleDownload) 1537 }, repo.MustBeNotEmpty) 1538 1539 m.Group("/render", func() { 1540 m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.RenderFile) 1541 m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.RenderFile) 1542 m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.RenderFile) 1543 m.Get("/blob/{sha}", context.RepoRefByType(context.RepoRefBlob), repo.RenderFile) 1544 }, repo.MustBeNotEmpty) 1545 1546 m.Group("/commits", func() { 1547 m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.RefCommits) 1548 m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.RefCommits) 1549 m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.RefCommits) 1550 // "/*" route is deprecated, and kept for backward compatibility 1551 m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.RefCommits) 1552 }, repo.MustBeNotEmpty) 1553 1554 m.Group("/blame", func() { 1555 m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.RefBlame) 1556 m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.RefBlame) 1557 m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.RefBlame) 1558 }, repo.MustBeNotEmpty) 1559 1560 m.Group("", func() { 1561 m.Get("/graph", repo.Graph) 1562 m.Get("/commit/{sha:([a-f0-9]{7,64})$}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff) 1563 m.Get("/commit/{sha:([a-f0-9]{7,64})$}/load-branches-and-tags", repo.LoadBranchesAndTags) 1564 m.Get("/cherry-pick/{sha:([a-f0-9]{7,64})$}", repo.SetEditorconfigIfExists, repo.CherryPick) 1565 }, repo.MustBeNotEmpty, context.RepoRef()) 1566 1567 m.Get("/rss/branch/*", context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed) 1568 m.Get("/atom/branch/*", context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed) 1569 1570 m.Group("/src", func() { 1571 m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home) 1572 m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.Home) 1573 m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.Home) 1574 m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.Home) // "/*" route is deprecated, and kept for backward compatibility 1575 }, repo.SetEditorconfigIfExists) 1576 1577 m.Get("/forks", context.RepoRef(), repo.Forks) 1578 m.Get("/commit/{sha:([a-f0-9]{7,64})}.{ext:patch|diff}", repo.MustBeNotEmpty, repo.RawDiff) 1579 m.Post("/lastcommit/*", context.RepoRefByType(context.RepoRefCommit), repo.LastCommit) 1580 }, ignSignIn, context.RepoAssignment, reqRepoCodeReader) 1581 // end "/{username}/{reponame}": repo code 1582 1583 m.Group("/{username}/{reponame}", func() { 1584 m.Get("/stars", repo.Stars) 1585 m.Get("/watchers", repo.Watchers) 1586 m.Get("/search", reqRepoCodeReader, repo.Search) 1587 m.Post("/action/{action}", reqSignIn, repo.Action) 1588 }, ignSignIn, context.RepoAssignment, context.RepoRef()) 1589 1590 m.Group("/{username}/{reponame}", func() { 1591 m.Group("/info/lfs", func() { 1592 m.Post("/objects/batch", lfs.CheckAcceptMediaType, lfs.BatchHandler) 1593 m.Put("/objects/{oid}/{size}", lfs.UploadHandler) 1594 m.Get("/objects/{oid}/{filename}", lfs.DownloadHandler) 1595 m.Get("/objects/{oid}", lfs.DownloadHandler) 1596 m.Post("/verify", lfs.CheckAcceptMediaType, lfs.VerifyHandler) 1597 m.Group("/locks", func() { 1598 m.Get("/", lfs.GetListLockHandler) 1599 m.Post("/", lfs.PostLockHandler) 1600 m.Post("/verify", lfs.VerifyLockHandler) 1601 m.Post("/{lid}/unlock", lfs.UnLockHandler) 1602 }, lfs.CheckAcceptMediaType) 1603 m.Any("/*", func(ctx *context.Context) { 1604 ctx.NotFound("", nil) 1605 }) 1606 }, ignSignInAndCsrf, lfsServerEnabled) 1607 gitHTTPRouters(m) 1608 }) 1609 // end "/{username}/{reponame}.git": git support 1610 1611 m.Group("/notifications", func() { 1612 m.Get("", user.Notifications) 1613 m.Get("/subscriptions", user.NotificationSubscriptions) 1614 m.Get("/watching", user.NotificationWatching) 1615 m.Post("/status", user.NotificationStatusPost) 1616 m.Post("/purge", user.NotificationPurgePost) 1617 m.Get("/new", user.NewAvailable) 1618 }, reqSignIn) 1619 1620 if setting.API.EnableSwagger { 1621 m.Get("/swagger.v1.json", SwaggerV1Json) 1622 } 1623 1624 if !setting.IsProd { 1625 m.Any("/devtest", devtest.List) 1626 m.Any("/devtest/fetch-action-test", devtest.FetchActionTest) 1627 m.Any("/devtest/{sub}", devtest.Tmpl) 1628 } 1629 1630 m.NotFound(func(w http.ResponseWriter, req *http.Request) { 1631 ctx := context.GetWebContext(req) 1632 routing.UpdateFuncInfo(ctx, routing.GetFuncInfo(ctx.NotFound, "WebNotFound")) 1633 ctx.NotFound("", nil) 1634 }) 1635 }