blob: ed1f04c003f226fad864cb7a8535e66eb236bed4 [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
Copybara854996b2021-09-07 19:36:02 +000020from features import hotlistdetails
21from features import hotlistissues
22from features import hotlistissuescsv
23from features import hotlistpeople
24from features import filterrules
25from features import pubsub
26from features import userhotlists
Copybara854996b2021-09-07 19:36:02 +000027from features import notify
28from features import rerankhotlist
29from features import savedqueries
Copybara854996b2021-09-07 19:36:02 +000030
Copybara854996b2021-09-07 19:36:02 +000031from framework import csp_report
32from framework import deleteusers
Copybara854996b2021-09-07 19:36:02 +000033from framework import trimvisitedpages
Copybara854996b2021-09-07 19:36:02 +000034from framework import reap
35from framework import registerpages_helpers
Copybara854996b2021-09-07 19:36:02 +000036from framework import urls
Copybara854996b2021-09-07 19:36:02 +000037
38from project import peopledetail
39from project import peoplelist
40from project import project_constants
41from project import projectadmin
42from project import projectadminadvanced
43from project import projectexport
44from project import projectsummary
45from project import projectupdates
46from project import redirects
47
Copybara854996b2021-09-07 19:36:02 +000048from services import cachemanager_svc
49from services import client_config_svc
50
51from sitewide import custom_404
Copybara854996b2021-09-07 19:36:02 +000052from sitewide import userprofile
Copybara854996b2021-09-07 19:36:02 +000053from sitewide import userclearbouncing
54from sitewide import userupdates
55
56from tracker import componentcreate
57from tracker import componentdetail
58from tracker import fieldcreate
59from tracker import fielddetail
60from tracker import issueadmin
61from tracker import issueadvsearch
62from tracker import issueattachment
63from tracker import issueattachmenttext
64from tracker import issuebulkedit
65from tracker import webcomponentspage
66from tracker import issuedetailezt
67from tracker import issueentry
68from tracker import issueentryafterlogin
69from tracker import issueexport
70from tracker import issueimport
71from tracker import issueoriginal
72from tracker import issuereindex
73from tracker import issuetips
Copybara854996b2021-09-07 19:36:02 +000074from tracker import templatecreate
75from tracker import templatedetail
76from tracker import fltconversion
77
78from api import api_routes as api_routes_v0
79from api.v3 import api_routes as api_routes_v3
80
81
82class ServletRegistry(object):
83
84 _PROJECT_NAME_REGEX = project_constants.PROJECT_NAME_PATTERN
85 _USERNAME_REGEX = r'[-+\w=.%]+(@([-a-z0-9]+\.)*[a-z0-9]+)?'
86 _HOTLIST_ID_NAME_REGEX = r'\d+|[a-zA-Z][-0-9a-zA-Z\.]*'
87
88 def __init__(self):
89 self.routes = []
90
91 def _AddRoute(self, path_regex, servlet_class, method, does_write=False):
92 """Add a GET or POST handler to our webapp2 route list.
93
94 Args:
95 path_regex: string with webapp2 URL template regex.
96 servlet_class: a subclass of class Servlet.
97 method: string 'GET' or 'POST'.
98 does_write: True if the servlet could write to the database, we skip
99 registering such servlets when the site is in read_only mode. GET
100 handlers never write. Most, but not all, POST handlers do write.
101 """
102 if settings.read_only and does_write:
103 logging.info('Not registring %r because site is read-only', path_regex)
104 # TODO(jrobbins): register a helpful error page instead.
105 else:
106 self.routes.append(
107 webapp2.Route(path_regex, handler=servlet_class, methods=[method]))
108
109 def _SetupServlets(self, spec_dict, base='', post_does_write=True):
110 """Register each of the given servlets."""
111 for get_uri, servlet_class in spec_dict.items():
112 self._AddRoute(base + get_uri, servlet_class, 'GET')
113 post_uri = get_uri + ('edit.do' if get_uri.endswith('/') else '.do')
114 self._AddRoute(base + post_uri, servlet_class, 'POST',
115 does_write=post_does_write)
116
117 def _SetupProjectServlets(self, spec_dict, post_does_write=True):
118 """Register each of the given servlets in the project URI space."""
119 self._SetupServlets(
120 spec_dict, base='/p/<project_name:%s>' % self._PROJECT_NAME_REGEX,
121 post_does_write=post_does_write)
122
123 def _SetupUserServlets(self, spec_dict, post_does_write=True):
124 """Register each of the given servlets in the user URI space."""
125 self._SetupServlets(
126 spec_dict, base='/u/<viewed_username:%s>' % self._USERNAME_REGEX,
127 post_does_write=post_does_write)
128
129 def _SetupGroupServlets(self, spec_dict, post_does_write=True):
130 """Register each of the given servlets in the user group URI space."""
131 self._SetupServlets(
132 spec_dict, base='/g/<viewed_username:%s>' % self._USERNAME_REGEX,
133 post_does_write=post_does_write)
134
135 def _SetupUserHotlistServlets(self, spec_dict, post_does_write=True):
136 """ Register given user hotlist servlets in the user URI space."""
137 self._SetupServlets(
138 spec_dict,
139 base ='/u/<viewed_username:%s>/hotlists/<hotlist_id:%s>'
140 % (self._USERNAME_REGEX, self._HOTLIST_ID_NAME_REGEX),
141 post_does_write=post_does_write)
142
143 def Register(self, services):
144 """Register all the monorail request handlers."""
Copybara854996b2021-09-07 19:36:02 +0000145 self._RegisterSitewideHandlers()
146 self._RegisterProjectHandlers()
147 self._RegisterIssueHandlers()
148 self._RegisterWebComponentsHanders()
149 self._RegisterRedirects()
Copybara854996b2021-09-07 19:36:02 +0000150
151 # Register pRPC API routes
152 prpc_server = prpc.Server(
153 allowed_origins=client_config_svc.GetAllowedOriginsSet())
154 api_routes_v0.RegisterApiHandlers(prpc_server, services)
155 api_routes_v3.RegisterApiHandlers(prpc_server, services)
156 self.routes.extend(prpc_server.get_routes())
157
158 autolink.RegisterAutolink(services)
159 # Error pages should be the last to register.
160 self._RegisterErrorPages()
161 return self.routes
162
163 def _RegisterProjectHandlers(self):
164 """Register page and form handlers that operate within a project."""
165
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200166 self._SetupProjectServlets(
167 {
168 urls.ADMIN_INTRO: projectsummary.ProjectSummary,
169 urls.PEOPLE_LIST: peoplelist.PeopleList,
170 urls.PEOPLE_DETAIL: peopledetail.PeopleDetail,
171 urls.UPDATES_LIST: projectupdates.ProjectUpdates,
172 urls.ADMIN_META: projectadmin.ProjectAdmin,
173 urls.ADMIN_ADVANCED: projectadminadvanced.ProjectAdminAdvanced,
174 urls.ADMIN_EXPORT: projectexport.ProjectExport,
175 urls.ADMIN_EXPORT_JSON: projectexport.ProjectExportJSON,
Copybara854996b2021-09-07 19:36:02 +0000176 })
177
178 def _RegisterIssueHandlers(self):
179 """Register page and form handlers for the issue tracker."""
Copybara854996b2021-09-07 19:36:02 +0000180
181 self._SetupProjectServlets(
182 {
183 urls.ISSUE_APPROVAL:
184 registerpages_helpers.MakeRedirectInScope(
185 urls.ISSUE_DETAIL, 'p', keep_qs=True),
186 urls.ISSUE_LIST:
187 webcomponentspage.WebComponentsPage,
188 urls.ISSUE_LIST_NEW_TEMP:
189 registerpages_helpers.MakeRedirectInScope(
190 urls.ISSUE_LIST, 'p', keep_qs=True),
191 urls.ISSUE_REINDEX:
192 issuereindex.IssueReindex,
193 urls.ISSUE_DETAIL_FLIPPER_NEXT:
194 issuedetailezt.FlipperNext,
195 urls.ISSUE_DETAIL_FLIPPER_PREV:
196 issuedetailezt.FlipperPrev,
197 urls.ISSUE_DETAIL_FLIPPER_LIST:
198 issuedetailezt.FlipperList,
199 urls.ISSUE_DETAIL_FLIPPER_INDEX:
200 issuedetailezt.FlipperIndex,
201 urls.ISSUE_DETAIL_LEGACY:
202 registerpages_helpers.MakeRedirectInScope(
203 urls.ISSUE_DETAIL, 'p', keep_qs=True),
204 urls.ISSUE_WIZARD:
205 webcomponentspage.WebComponentsPage,
206 urls.ISSUE_ENTRY:
207 issueentry.IssueEntry,
208 urls.ISSUE_ENTRY_NEW:
209 webcomponentspage.WebComponentsPage,
210 urls.ISSUE_ENTRY_AFTER_LOGIN:
211 issueentryafterlogin.IssueEntryAfterLogin,
212 urls.ISSUE_TIPS:
213 issuetips.IssueSearchTips,
214 urls.ISSUE_ATTACHMENT:
215 issueattachment.AttachmentPage,
216 urls.ISSUE_ATTACHMENT_TEXT:
217 issueattachmenttext.AttachmentText,
218 urls.ISSUE_BULK_EDIT:
219 issuebulkedit.IssueBulkEdit,
220 urls.COMPONENT_CREATE:
221 componentcreate.ComponentCreate,
222 urls.COMPONENT_DETAIL:
223 componentdetail.ComponentDetail,
224 urls.FIELD_CREATE:
225 fieldcreate.FieldCreate,
226 urls.FIELD_DETAIL:
227 fielddetail.FieldDetail,
228 urls.TEMPLATE_CREATE:
229 templatecreate.TemplateCreate,
230 urls.TEMPLATE_DETAIL:
231 templatedetail.TemplateDetail,
232 urls.WIKI_LIST:
233 redirects.WikiRedirect,
234 urls.WIKI_PAGE:
235 redirects.WikiRedirect,
236 urls.SOURCE_PAGE:
237 redirects.SourceRedirect,
238 urls.ADMIN_STATUSES:
239 issueadmin.AdminStatuses,
240 urls.ADMIN_LABELS:
241 issueadmin.AdminLabels,
242 urls.ADMIN_RULES:
243 issueadmin.AdminRules,
244 urls.ADMIN_TEMPLATES:
245 issueadmin.AdminTemplates,
246 urls.ADMIN_COMPONENTS:
247 issueadmin.AdminComponents,
248 urls.ADMIN_VIEWS:
249 issueadmin.AdminViews,
250 urls.ISSUE_ORIGINAL:
251 issueoriginal.IssueOriginal,
252 urls.ISSUE_EXPORT:
253 issueexport.IssueExport,
254 urls.ISSUE_EXPORT_JSON:
255 issueexport.IssueExportJSON,
256 urls.ISSUE_IMPORT:
257 issueimport.IssueImport,
Copybara854996b2021-09-07 19:36:02 +0000258 })
259
260 # GETs for /issues/detail are now handled by the web components page.
261 base = '/p/<project_name:%s>' % self._PROJECT_NAME_REGEX
262 self._AddRoute(base + urls.ISSUE_DETAIL,
263 webcomponentspage.WebComponentsPage, 'GET')
264
265 self._SetupUserServlets({
266 urls.SAVED_QUERIES: savedqueries.SavedQueries,
267 urls.HOTLISTS: userhotlists.UserHotlists,
268 })
269
270 user_hotlists_redir = registerpages_helpers.MakeRedirectInScope(
271 urls.HOTLISTS, 'u', keep_qs=True)
272 self._SetupUserServlets({
273 '/hotlists/': user_hotlists_redir,
274 })
275
276 # These servlets accept POST, but never write to the database, so they can
277 # still be used when the site is read-only.
278 self._SetupProjectServlets({
279 urls.ISSUE_ADVSEARCH: issueadvsearch.IssueAdvancedSearch,
280 }, post_does_write=False)
281
282 list_redir = registerpages_helpers.MakeRedirectInScope(
283 urls.ISSUE_LIST, 'p', keep_qs=True)
284 self._SetupProjectServlets({
285 '': list_redir,
286 '/': list_redir,
287 '/issues': list_redir,
288 '/issues/': list_redir,
289 })
290
Copybara854996b2021-09-07 19:36:02 +0000291 def _RegisterSitewideHandlers(self):
292 """Register page and form handlers that aren't associated with projects."""
Copybara854996b2021-09-07 19:36:02 +0000293
294 self._SetupUserServlets({
295 urls.USER_PROFILE: userprofile.UserProfile,
Copybara854996b2021-09-07 19:36:02 +0000296 urls.BAN_USER: userprofile.BanUser,
297 urls.BAN_SPAMMER: banspammer.BanSpammer,
298 urls.USER_CLEAR_BOUNCING: userclearbouncing.UserClearBouncing,
299 urls.USER_UPDATES_PROJECTS: userupdates.UserUpdatesProjects,
300 urls.USER_UPDATES_DEVELOPERS: userupdates.UserUpdatesDevelopers,
301 urls.USER_UPDATES_MINE: userupdates.UserUpdatesIndividual,
302 })
303
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200304 self._SetupUserHotlistServlets(
305 {
306 urls.HOTLIST_ISSUES: hotlistissues.HotlistIssues,
307 urls.HOTLIST_ISSUES_CSV: hotlistissuescsv.HotlistIssuesCsv,
308 urls.HOTLIST_PEOPLE: hotlistpeople.HotlistPeopleList,
309 urls.HOTLIST_DETAIL: hotlistdetails.HotlistDetails,
310 urls.HOTLIST_RERANK_JSON: rerankhotlist.RerankHotlistIssue,
311 })
Copybara854996b2021-09-07 19:36:02 +0000312
313 profile_redir = registerpages_helpers.MakeRedirectInScope(
314 urls.USER_PROFILE, 'u')
315 self._SetupUserServlets({'': profile_redir})
316
Copybara854996b2021-09-07 19:36:02 +0000317 def _RegisterWebComponentsHanders(self):
318 """Register page handlers that are handled by WebComponentsPage."""
319 self._AddRoute('/', webcomponentspage.ProjectListPage, 'GET')
320 self._AddRoute(
321 '/hotlists<unused:.*>', webcomponentspage.WebComponentsPage, 'GET')
322 self._AddRoute('/users<unused:.*>', webcomponentspage.WebComponentsPage,
323 'GET')
324
325 def _RegisterRedirects(self):
326 """Register redirects among pages inside monorail."""
327 redirect = registerpages_helpers.MakeRedirect('/')
328 self._SetupServlets(
329 {
Copybara854996b2021-09-07 19:36:02 +0000330 '/p': redirect,
331 '/p/': redirect,
332 '/u': redirect,
333 '/u/': redirect,
334 '/': redirect,
335 })
336
337 redirect = registerpages_helpers.MakeRedirectInScope(
338 urls.PEOPLE_LIST, 'p')
339 self._SetupProjectServlets({
340 '/people': redirect,
341 '/people/': redirect,
342 })
343
Copybara854996b2021-09-07 19:36:02 +0000344 def _RegisterErrorPages(self):
345 """Register handlers for errors."""
346 self._AddRoute(
347 '/p/<project_name:%s>/<unrecognized:.+>' % self._PROJECT_NAME_REGEX,
348 custom_404.ErrorPage, 'GET')