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