blob: 2e0d6ab2d7c1545834d8d47e7214d23a77a039f2 [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001# Copyright 2016 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style
3# license that can be found in the LICENSE file or at
4# https://developers.google.com/open-source/licenses/bsd
5
6"""This file sets up all the urls for monorail pages."""
7from __future__ import print_function
8from __future__ import division
9from __future__ import absolute_import
10
11import logging
12import webapp2
13import settings
14
15from components import prpc
16
17from features import autolink
18from features import dateaction
19from features import banspammer
20from features import hotlistcreate
21from features import hotlistdetails
22from features import hotlistissues
23from features import hotlistissuescsv
24from features import hotlistpeople
25from features import filterrules
26from features import pubsub
27from features import userhotlists
28from features import inboundemail
29from features import notify
30from features import rerankhotlist
31from features import savedqueries
Copybara854996b2021-09-07 19:36:02 +000032
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +020033from framework import banned, excessiveactivity
Copybara854996b2021-09-07 19:36:02 +000034from framework import clientmon
35from framework import csp_report
36from framework import deleteusers
Copybara854996b2021-09-07 19:36:02 +000037from framework import trimvisitedpages
Copybara854996b2021-09-07 19:36:02 +000038from framework import reap
39from framework import registerpages_helpers
40from framework import ts_mon_js
41from framework import urls
42from framework import warmup
43
44from project import peopledetail
45from project import peoplelist
46from project import project_constants
47from project import projectadmin
48from project import projectadminadvanced
49from project import projectexport
50from project import projectsummary
51from project import projectupdates
52from project import redirects
53
54from search import backendnonviewable
55from search import backendsearch
56
57from services import cachemanager_svc
58from services import client_config_svc
59
60from sitewide import custom_404
61from sitewide import groupadmin
62from sitewide import groupcreate
63from sitewide import groupdetail
64from sitewide import grouplist
Copybara854996b2021-09-07 19:36:02 +000065from sitewide import moved
66from sitewide import projectcreate
67from sitewide import userprofile
68from sitewide import usersettings
69from sitewide import userclearbouncing
70from sitewide import userupdates
71
72from tracker import componentcreate
73from tracker import componentdetail
74from tracker import fieldcreate
75from tracker import fielddetail
76from tracker import issueadmin
77from tracker import issueadvsearch
78from tracker import issueattachment
79from tracker import issueattachmenttext
80from tracker import issuebulkedit
81from tracker import webcomponentspage
82from tracker import issuedetailezt
83from tracker import issueentry
84from tracker import issueentryafterlogin
85from tracker import issueexport
86from tracker import issueimport
87from tracker import issueoriginal
88from tracker import issuereindex
89from tracker import issuetips
Copybara854996b2021-09-07 19:36:02 +000090from tracker import templatecreate
91from tracker import templatedetail
92from tracker import fltconversion
93
94from api import api_routes as api_routes_v0
95from api.v3 import api_routes as api_routes_v3
96
97
98class ServletRegistry(object):
99
100 _PROJECT_NAME_REGEX = project_constants.PROJECT_NAME_PATTERN
101 _USERNAME_REGEX = r'[-+\w=.%]+(@([-a-z0-9]+\.)*[a-z0-9]+)?'
102 _HOTLIST_ID_NAME_REGEX = r'\d+|[a-zA-Z][-0-9a-zA-Z\.]*'
103
104 def __init__(self):
105 self.routes = []
106
107 def _AddRoute(self, path_regex, servlet_class, method, does_write=False):
108 """Add a GET or POST handler to our webapp2 route list.
109
110 Args:
111 path_regex: string with webapp2 URL template regex.
112 servlet_class: a subclass of class Servlet.
113 method: string 'GET' or 'POST'.
114 does_write: True if the servlet could write to the database, we skip
115 registering such servlets when the site is in read_only mode. GET
116 handlers never write. Most, but not all, POST handlers do write.
117 """
118 if settings.read_only and does_write:
119 logging.info('Not registring %r because site is read-only', path_regex)
120 # TODO(jrobbins): register a helpful error page instead.
121 else:
122 self.routes.append(
123 webapp2.Route(path_regex, handler=servlet_class, methods=[method]))
124
125 def _SetupServlets(self, spec_dict, base='', post_does_write=True):
126 """Register each of the given servlets."""
127 for get_uri, servlet_class in spec_dict.items():
128 self._AddRoute(base + get_uri, servlet_class, 'GET')
129 post_uri = get_uri + ('edit.do' if get_uri.endswith('/') else '.do')
130 self._AddRoute(base + post_uri, servlet_class, 'POST',
131 does_write=post_does_write)
132
133 def _SetupProjectServlets(self, spec_dict, post_does_write=True):
134 """Register each of the given servlets in the project URI space."""
135 self._SetupServlets(
136 spec_dict, base='/p/<project_name:%s>' % self._PROJECT_NAME_REGEX,
137 post_does_write=post_does_write)
138
139 def _SetupUserServlets(self, spec_dict, post_does_write=True):
140 """Register each of the given servlets in the user URI space."""
141 self._SetupServlets(
142 spec_dict, base='/u/<viewed_username:%s>' % self._USERNAME_REGEX,
143 post_does_write=post_does_write)
144
145 def _SetupGroupServlets(self, spec_dict, post_does_write=True):
146 """Register each of the given servlets in the user group URI space."""
147 self._SetupServlets(
148 spec_dict, base='/g/<viewed_username:%s>' % self._USERNAME_REGEX,
149 post_does_write=post_does_write)
150
151 def _SetupUserHotlistServlets(self, spec_dict, post_does_write=True):
152 """ Register given user hotlist servlets in the user URI space."""
153 self._SetupServlets(
154 spec_dict,
155 base ='/u/<viewed_username:%s>/hotlists/<hotlist_id:%s>'
156 % (self._USERNAME_REGEX, self._HOTLIST_ID_NAME_REGEX),
157 post_does_write=post_does_write)
158
159 def Register(self, services):
160 """Register all the monorail request handlers."""
161 self._RegisterFrameworkHandlers()
162 self._RegisterSitewideHandlers()
163 self._RegisterProjectHandlers()
164 self._RegisterIssueHandlers()
165 self._RegisterWebComponentsHanders()
166 self._RegisterRedirects()
167 self._RegisterInboundMail()
168
169 # Register pRPC API routes
170 prpc_server = prpc.Server(
171 allowed_origins=client_config_svc.GetAllowedOriginsSet())
172 api_routes_v0.RegisterApiHandlers(prpc_server, services)
173 api_routes_v3.RegisterApiHandlers(prpc_server, services)
174 self.routes.extend(prpc_server.get_routes())
175
176 autolink.RegisterAutolink(services)
177 # Error pages should be the last to register.
178 self._RegisterErrorPages()
179 return self.routes
180
181 def _RegisterProjectHandlers(self):
182 """Register page and form handlers that operate within a project."""
183
184 self._SetupServlets({
185 # Note: the following are at URLS that are not externally accessible.
186 urls.NOTIFY_RULES_DELETED_TASK: notify.NotifyRulesDeletedTask,
187 })
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200188 self._SetupProjectServlets(
189 {
190 urls.ADMIN_INTRO: projectsummary.ProjectSummary,
191 urls.PEOPLE_LIST: peoplelist.PeopleList,
192 urls.PEOPLE_DETAIL: peopledetail.PeopleDetail,
193 urls.UPDATES_LIST: projectupdates.ProjectUpdates,
194 urls.ADMIN_META: projectadmin.ProjectAdmin,
195 urls.ADMIN_ADVANCED: projectadminadvanced.ProjectAdminAdvanced,
196 urls.ADMIN_EXPORT: projectexport.ProjectExport,
197 urls.ADMIN_EXPORT_JSON: projectexport.ProjectExportJSON,
Copybara854996b2021-09-07 19:36:02 +0000198 })
199
200 def _RegisterIssueHandlers(self):
201 """Register page and form handlers for the issue tracker."""
202 self._SetupServlets({
203 # Note: the following are at URLs that are not externaly accessible.
204 urls.BACKEND_SEARCH: backendsearch.BackendSearch,
205 urls.BACKEND_NONVIEWABLE: backendnonviewable.BackendNonviewable,
206 urls.RECOMPUTE_DERIVED_FIELDS_TASK:
207 filterrules.RecomputeDerivedFieldsTask,
208 urls.REINDEX_QUEUE_CRON: filterrules.ReindexQueueCron,
209 urls.NOTIFY_ISSUE_CHANGE_TASK: notify.NotifyIssueChangeTask,
210 urls.NOTIFY_BLOCKING_CHANGE_TASK: notify.NotifyBlockingChangeTask,
211 urls.NOTIFY_BULK_CHANGE_TASK: notify.NotifyBulkChangeTask,
212 urls.NOTIFY_APPROVAL_CHANGE_TASK: notify.NotifyApprovalChangeTask,
213 urls.OUTBOUND_EMAIL_TASK: notify.OutboundEmailTask,
Copybara854996b2021-09-07 19:36:02 +0000214 urls.DATE_ACTION_CRON: dateaction.DateActionCron,
Copybara854996b2021-09-07 19:36:02 +0000215 urls.PUBLISH_PUBSUB_ISSUE_CHANGE_TASK:
216 pubsub.PublishPubsubIssueChangeTask,
217 urls.ISSUE_DATE_ACTION_TASK: dateaction.IssueDateActionTask,
Copybara854996b2021-09-07 19:36:02 +0000218 urls.FLT_ISSUE_CONVERSION_TASK: fltconversion.FLTConvertTask,
219 })
220
221 self._SetupProjectServlets(
222 {
223 urls.ISSUE_APPROVAL:
224 registerpages_helpers.MakeRedirectInScope(
225 urls.ISSUE_DETAIL, 'p', keep_qs=True),
226 urls.ISSUE_LIST:
227 webcomponentspage.WebComponentsPage,
228 urls.ISSUE_LIST_NEW_TEMP:
229 registerpages_helpers.MakeRedirectInScope(
230 urls.ISSUE_LIST, 'p', keep_qs=True),
231 urls.ISSUE_REINDEX:
232 issuereindex.IssueReindex,
233 urls.ISSUE_DETAIL_FLIPPER_NEXT:
234 issuedetailezt.FlipperNext,
235 urls.ISSUE_DETAIL_FLIPPER_PREV:
236 issuedetailezt.FlipperPrev,
237 urls.ISSUE_DETAIL_FLIPPER_LIST:
238 issuedetailezt.FlipperList,
239 urls.ISSUE_DETAIL_FLIPPER_INDEX:
240 issuedetailezt.FlipperIndex,
241 urls.ISSUE_DETAIL_LEGACY:
242 registerpages_helpers.MakeRedirectInScope(
243 urls.ISSUE_DETAIL, 'p', keep_qs=True),
244 urls.ISSUE_WIZARD:
245 webcomponentspage.WebComponentsPage,
246 urls.ISSUE_ENTRY:
247 issueentry.IssueEntry,
248 urls.ISSUE_ENTRY_NEW:
249 webcomponentspage.WebComponentsPage,
250 urls.ISSUE_ENTRY_AFTER_LOGIN:
251 issueentryafterlogin.IssueEntryAfterLogin,
252 urls.ISSUE_TIPS:
253 issuetips.IssueSearchTips,
254 urls.ISSUE_ATTACHMENT:
255 issueattachment.AttachmentPage,
256 urls.ISSUE_ATTACHMENT_TEXT:
257 issueattachmenttext.AttachmentText,
258 urls.ISSUE_BULK_EDIT:
259 issuebulkedit.IssueBulkEdit,
260 urls.COMPONENT_CREATE:
261 componentcreate.ComponentCreate,
262 urls.COMPONENT_DETAIL:
263 componentdetail.ComponentDetail,
264 urls.FIELD_CREATE:
265 fieldcreate.FieldCreate,
266 urls.FIELD_DETAIL:
267 fielddetail.FieldDetail,
268 urls.TEMPLATE_CREATE:
269 templatecreate.TemplateCreate,
270 urls.TEMPLATE_DETAIL:
271 templatedetail.TemplateDetail,
272 urls.WIKI_LIST:
273 redirects.WikiRedirect,
274 urls.WIKI_PAGE:
275 redirects.WikiRedirect,
276 urls.SOURCE_PAGE:
277 redirects.SourceRedirect,
278 urls.ADMIN_STATUSES:
279 issueadmin.AdminStatuses,
280 urls.ADMIN_LABELS:
281 issueadmin.AdminLabels,
282 urls.ADMIN_RULES:
283 issueadmin.AdminRules,
284 urls.ADMIN_TEMPLATES:
285 issueadmin.AdminTemplates,
286 urls.ADMIN_COMPONENTS:
287 issueadmin.AdminComponents,
288 urls.ADMIN_VIEWS:
289 issueadmin.AdminViews,
290 urls.ISSUE_ORIGINAL:
291 issueoriginal.IssueOriginal,
292 urls.ISSUE_EXPORT:
293 issueexport.IssueExport,
294 urls.ISSUE_EXPORT_JSON:
295 issueexport.IssueExportJSON,
296 urls.ISSUE_IMPORT:
297 issueimport.IssueImport,
Copybara854996b2021-09-07 19:36:02 +0000298 })
299
300 # GETs for /issues/detail are now handled by the web components page.
301 base = '/p/<project_name:%s>' % self._PROJECT_NAME_REGEX
302 self._AddRoute(base + urls.ISSUE_DETAIL,
303 webcomponentspage.WebComponentsPage, 'GET')
304
305 self._SetupUserServlets({
306 urls.SAVED_QUERIES: savedqueries.SavedQueries,
307 urls.HOTLISTS: userhotlists.UserHotlists,
308 })
309
310 user_hotlists_redir = registerpages_helpers.MakeRedirectInScope(
311 urls.HOTLISTS, 'u', keep_qs=True)
312 self._SetupUserServlets({
313 '/hotlists/': user_hotlists_redir,
314 })
315
316 # These servlets accept POST, but never write to the database, so they can
317 # still be used when the site is read-only.
318 self._SetupProjectServlets({
319 urls.ISSUE_ADVSEARCH: issueadvsearch.IssueAdvancedSearch,
320 }, post_does_write=False)
321
322 list_redir = registerpages_helpers.MakeRedirectInScope(
323 urls.ISSUE_LIST, 'p', keep_qs=True)
324 self._SetupProjectServlets({
325 '': list_redir,
326 '/': list_redir,
327 '/issues': list_redir,
328 '/issues/': list_redir,
329 })
330
Copybara854996b2021-09-07 19:36:02 +0000331
332 def _RegisterFrameworkHandlers(self):
333 """Register page and form handlers for framework functionality."""
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200334 self._SetupServlets(
335 {
336 urls.CSP_REPORT:
337 csp_report.CSPReportPage,
Copybara854996b2021-09-07 19:36:02 +0000338
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200339 # These are only shown to users if specific conditions are met.
340 urls.EXCESSIVE_ACTIVITY:
341 excessiveactivity.ExcessiveActivity,
342 urls.BANNED:
343 banned.Banned,
344 urls.PROJECT_MOVED:
345 moved.ProjectMoved,
Copybara854996b2021-09-07 19:36:02 +0000346
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200347 # These are not externally accessible
348 urls.RAMCACHE_CONSOLIDATE_CRON:
349 cachemanager_svc.RamCacheConsolidate,
350 urls.REAP_CRON:
351 reap.Reap,
352 urls.LOAD_API_CLIENT_CONFIGS_CRON:
353 (client_config_svc.LoadApiClientConfigs),
354 urls.CLIENT_MON:
355 clientmon.ClientMonitor,
356 urls.TRIM_VISITED_PAGES_CRON:
357 trimvisitedpages.TrimVisitedPages,
358 urls.TS_MON_JS:
359 ts_mon_js.MonorailTSMonJSHandler,
360 urls.WARMUP:
361 warmup.Warmup,
362 urls.START:
363 warmup.Start,
364 urls.STOP:
365 warmup.Stop
Copybara854996b2021-09-07 19:36:02 +0000366 })
367
368 def _RegisterSitewideHandlers(self):
369 """Register page and form handlers that aren't associated with projects."""
370 self._SetupServlets({
371 urls.PROJECT_CREATE: projectcreate.ProjectCreate,
372 # The user settings page is a site-wide servlet, not under /u/.
373 urls.USER_SETTINGS: usersettings.UserSettings,
Copybara854996b2021-09-07 19:36:02 +0000374 urls.GROUP_CREATE: groupcreate.GroupCreate,
375 urls.GROUP_LIST: grouplist.GroupList,
376 urls.GROUP_DELETE: grouplist.GroupList,
377 urls.HOTLIST_CREATE: hotlistcreate.HotlistCreate,
378 urls.BAN_SPAMMER_TASK: banspammer.BanSpammerTask,
379 urls.WIPEOUT_SYNC_CRON: deleteusers.WipeoutSyncCron,
380 urls.SEND_WIPEOUT_USER_LISTS_TASK: deleteusers.SendWipeoutUserListsTask,
381 urls.DELETE_WIPEOUT_USERS_TASK: deleteusers.DeleteWipeoutUsersTask,
382 urls.DELETE_USERS_TASK: deleteusers.DeleteUsersTask,
383 })
384
385 self._SetupUserServlets({
386 urls.USER_PROFILE: userprofile.UserProfile,
Copybara854996b2021-09-07 19:36:02 +0000387 urls.BAN_USER: userprofile.BanUser,
388 urls.BAN_SPAMMER: banspammer.BanSpammer,
389 urls.USER_CLEAR_BOUNCING: userclearbouncing.UserClearBouncing,
390 urls.USER_UPDATES_PROJECTS: userupdates.UserUpdatesProjects,
391 urls.USER_UPDATES_DEVELOPERS: userupdates.UserUpdatesDevelopers,
392 urls.USER_UPDATES_MINE: userupdates.UserUpdatesIndividual,
393 })
394
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200395 self._SetupUserHotlistServlets(
396 {
397 urls.HOTLIST_ISSUES: hotlistissues.HotlistIssues,
398 urls.HOTLIST_ISSUES_CSV: hotlistissuescsv.HotlistIssuesCsv,
399 urls.HOTLIST_PEOPLE: hotlistpeople.HotlistPeopleList,
400 urls.HOTLIST_DETAIL: hotlistdetails.HotlistDetails,
401 urls.HOTLIST_RERANK_JSON: rerankhotlist.RerankHotlistIssue,
402 })
Copybara854996b2021-09-07 19:36:02 +0000403
404 profile_redir = registerpages_helpers.MakeRedirectInScope(
405 urls.USER_PROFILE, 'u')
406 self._SetupUserServlets({'': profile_redir})
407
408 self._SetupGroupServlets({
409 urls.GROUP_DETAIL: groupdetail.GroupDetail,
410 urls.GROUP_ADMIN: groupadmin.GroupAdmin,
411 })
412
413 def _RegisterWebComponentsHanders(self):
414 """Register page handlers that are handled by WebComponentsPage."""
415 self._AddRoute('/', webcomponentspage.ProjectListPage, 'GET')
416 self._AddRoute(
417 '/hotlists<unused:.*>', webcomponentspage.WebComponentsPage, 'GET')
418 self._AddRoute('/users<unused:.*>', webcomponentspage.WebComponentsPage,
419 'GET')
420
421 def _RegisterRedirects(self):
422 """Register redirects among pages inside monorail."""
423 redirect = registerpages_helpers.MakeRedirect('/')
424 self._SetupServlets(
425 {
Copybara854996b2021-09-07 19:36:02 +0000426 '/hosting/': redirect,
427 '/hosting': redirect,
428 '/p': redirect,
429 '/p/': redirect,
430 '/u': redirect,
431 '/u/': redirect,
432 '/': redirect,
433 })
434
435 redirect = registerpages_helpers.MakeRedirectInScope(
436 urls.PEOPLE_LIST, 'p')
437 self._SetupProjectServlets({
438 '/people': redirect,
439 '/people/': redirect,
440 })
441
442 redirect = registerpages_helpers.MakeRedirect(urls.GROUP_LIST)
443 self._SetupServlets({'/g': redirect})
444
445 group_redir = registerpages_helpers.MakeRedirectInScope(
446 urls.USER_PROFILE, 'g')
447 self._SetupGroupServlets({'': group_redir})
448
449 def _RegisterInboundMail(self):
450 """Register a handler for inbound email and email bounces."""
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200451 self.routes.append(
452 webapp2.Route(
453 '/_ah/mail/<project_addr:.+>',
454 handler=inboundemail.InboundEmail,
455 methods=['POST', 'GET']))
456 self.routes.append(
457 webapp2.Route(
458 '/_ah/bounce',
459 handler=inboundemail.BouncedEmail,
460 methods=['POST', 'GET']))
Copybara854996b2021-09-07 19:36:02 +0000461
462 def _RegisterErrorPages(self):
463 """Register handlers for errors."""
464 self._AddRoute(
465 '/p/<project_name:%s>/<unrecognized:.+>' % self._PROJECT_NAME_REGEX,
466 custom_404.ErrorPage, 'GET')