blob: 8dc3250400448ded83607a297c26862b2c8bb02f [file] [log] [blame]
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
"""This file sets up all the urls for monorail pages."""
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
import logging
import webapp2
import settings
from features import autolink
from features import dateaction
from features import banspammer
from features import hotlistcreate
from features import hotlistdetails
from features import hotlistissues
from features import hotlistissuescsv
from features import hotlistpeople
from features import filterrules
from features import userhotlists
from features import inboundemail
from features import notify
from features import rerankhotlist
from features import savedqueries
from features import spammodel
from features import spamtraining
from features import componentexport
from framework import artifactcollision
from framework import banned
from framework import clientmon
from framework import csp_report
from framework import deleteusers
from framework import excessiveactivity
from framework import trimvisitedpages
from framework import framework_bizobj
from framework import reap
from framework import registerpages_helpers
from framework import ts_mon_js
from framework import urls
from framework import warmup
from project import peopledetail
from project import peoplelist
from project import projectadmin
from project import projectadminadvanced
from project import projectexport
from project import projectsummary
from project import projectupdates
from project import redirects
from search import backendnonviewable
from search import backendsearch
from services import cachemanager_svc
from services import client_config_svc
from sitewide import custom_404
from sitewide import groupadmin
from sitewide import groupcreate
from sitewide import groupdetail
from sitewide import grouplist
from sitewide import hostinghome
from sitewide import moved
from sitewide import projectcreate
from sitewide import userprofile
from sitewide import usersettings
from sitewide import userclearbouncing
from sitewide import userupdates
from tracker import componentcreate
from tracker import componentdetail
from tracker import fieldcreate
from tracker import fielddetail
from tracker import issueadmin
from tracker import issueadvsearch
from tracker import issueattachment
from tracker import issueattachmenttext
from tracker import issuebulkedit
from tracker import webcomponentspage
from tracker import issuedetailezt
from tracker import issueentry
from tracker import issueentryafterlogin
from tracker import issueexport
from tracker import issueimport
from tracker import issuelist
from tracker import issuelistcsv
from tracker import issueoriginal
from tracker import issuereindex
from tracker import issuetips
from tracker import spam
from tracker import templatecreate
from tracker import templatedetail
from tracker import fltconversion
from api import api_service
class ServletRegistry(object):
_PROJECT_NAME_REGEX = r'[a-z0-9][-a-z0-9]*[a-z0-9]'
_USERNAME_REGEX = r'[-+\w=.%]+(@([-a-z0-9]+\.)*[a-z0-9]+)?'
_HOTLIST_ID_NAME_REGEX = r'\d+|[a-zA-Z][-0-9a-zA-Z\.]*'
def __init__(self):
self.routes = []
def _AddRoute(self, path_regex, servlet_class, method, does_write=False):
"""Add a GET or POST handler to our webapp2 route list.
Args:
path_regex: string with webapp2 URL template regex.
servlet_class: a subclass of class Servlet.
method: string 'GET' or 'POST'.
does_write: True if the servlet could write to the database, we skip
registering such servlets when the site is in read_only mode. GET
handlers never write. Most, but not all, POST handlers do write.
"""
if settings.read_only and does_write:
logging.info('Not registring %r because site is read-only', path_regex)
# TODO(jrobbins): register a helpful error page instead.
else:
self.routes.append(
webapp2.Route(path_regex, handler=servlet_class, methods=[method]))
def _SetupServlets(self, spec_dict, base='', post_does_write=True):
"""Register each of the given servlets."""
for get_uri, servlet_class in spec_dict.items():
self._AddRoute(base + get_uri, servlet_class, 'GET')
post_uri = get_uri + ('edit.do' if get_uri.endswith('/') else '.do')
self._AddRoute(base + post_uri, servlet_class, 'POST',
does_write=post_does_write)
def _SetupProjectServlets(self, spec_dict, post_does_write=True):
"""Register each of the given servlets in the project URI space."""
self._SetupServlets(
spec_dict, base='/p/<project_name:%s>' % self._PROJECT_NAME_REGEX,
post_does_write=post_does_write)
def _SetupUserServlets(self, spec_dict, post_does_write=True):
"""Register each of the given servlets in the user URI space."""
self._SetupServlets(
spec_dict, base='/u/<viewed_username:%s>' % self._USERNAME_REGEX,
post_does_write=post_does_write)
def _SetupGroupServlets(self, spec_dict, post_does_write=True):
"""Register each of the given servlets in the user group URI space."""
self._SetupServlets(
spec_dict, base='/g/<viewed_username:%s>' % self._USERNAME_REGEX,
post_does_write=post_does_write)
def _SetupUserHotlistServlets(self, spec_dict, post_does_write=True):
""" Register given user hotlist servlets in the user URI space."""
self._SetupServlets(
spec_dict,
base ='/u/<viewed_username:%s>/hotlists/<hotlist_id:%s>'
% (self._USERNAME_REGEX, self._HOTLIST_ID_NAME_REGEX),
post_does_write=post_does_write)
def Register(self, services):
"""Register all the monorail request handlers."""
self._RegisterFrameworkHandlers()
self._RegisterSitewideHandlers()
self._RegisterProjectHandlers()
self._RegisterIssueHandlers()
self._RegisterRedirects()
self._RegisterInboundMail()
api_service.RegisterApiHandlers(self, services)
autolink.RegisterAutolink(services)
# Error pages should be the last to register.
self._RegisterErrorPages()
logging.info('Finished registering monorail handlers.')
return self.routes
def _RegisterProjectHandlers(self):
"""Register page and form handlers that operate within a project."""
self._SetupServlets({
# Note: the following are at URLS that are not externally accessible.
urls.NOTIFY_RULES_DELETED_TASK: notify.NotifyRulesDeletedTask,
})
self._SetupProjectServlets({
urls.ADMIN_INTRO: projectsummary.ProjectSummary,
urls.PEOPLE_LIST: peoplelist.PeopleList,
urls.PEOPLE_DETAIL: peopledetail.PeopleDetail,
urls.UPDATES_LIST: projectupdates.ProjectUpdates,
urls.ADMIN_META: projectadmin.ProjectAdmin,
urls.ADMIN_ADVANCED: projectadminadvanced.ProjectAdminAdvanced,
urls.ADMIN_EXPORT: projectexport.ProjectExport,
urls.ADMIN_EXPORT_JSON: projectexport.ProjectExportJSON,
})
def _RegisterIssueHandlers(self):
"""Register page and form handlers for the issue tracker."""
self._SetupServlets({
# Note: there is both a site-wide and per-project issue list.
urls.ISSUE_LIST: issuelist.IssueList,
# Note: the following are at URLs that are not externaly accessible.
urls.BACKEND_SEARCH: backendsearch.BackendSearch,
urls.BACKEND_NONVIEWABLE: backendnonviewable.BackendNonviewable,
urls.RECOMPUTE_DERIVED_FIELDS_TASK:
filterrules.RecomputeDerivedFieldsTask,
urls.REINDEX_QUEUE_CRON: filterrules.ReindexQueueCron,
urls.NOTIFY_ISSUE_CHANGE_TASK: notify.NotifyIssueChangeTask,
urls.NOTIFY_BLOCKING_CHANGE_TASK: notify.NotifyBlockingChangeTask,
urls.NOTIFY_BULK_CHANGE_TASK: notify.NotifyBulkChangeTask,
urls.NOTIFY_APPROVAL_CHANGE_TASK: notify.NotifyApprovalChangeTask,
urls.OUTBOUND_EMAIL_TASK: notify.OutboundEmailTask,
urls.SPAM_DATA_EXPORT_TASK: spammodel.TrainingDataExportTask,
urls.DATE_ACTION_CRON: dateaction.DateActionCron,
urls.SPAM_TRAINING_CRON: spamtraining.TrainSpamModelCron,
urls.ISSUE_DATE_ACTION_TASK: dateaction.IssueDateActionTask,
urls.COMPONENT_DATA_EXPORT_CRON:
componentexport.ComponentTrainingDataExport,
urls.COMPONENT_DATA_EXPORT_TASK:
componentexport.ComponentTrainingDataExportTask,
urls.FLT_ISSUE_CONVERSION_TASK: fltconversion.FLTConvertTask,
})
self._SetupProjectServlets({
urls.ISSUE_APPROVAL: webcomponentspage.IssueDetailRedirect,
urls.ISSUE_LIST: issuelist.IssueList,
urls.ISSUE_NEW_GRID: webcomponentspage.WebComponentsPage,
urls.ISSUE_LIST_CSV: issuelistcsv.IssueListCsv,
urls.ISSUE_REINDEX: issuereindex.IssueReindex,
urls.ISSUE_DETAIL_FLIPPER_NEXT: issuedetailezt.FlipperNext,
urls.ISSUE_DETAIL_FLIPPER_PREV: issuedetailezt.FlipperPrev,
urls.ISSUE_DETAIL_FLIPPER_LIST: issuedetailezt.FlipperList,
urls.ISSUE_DETAIL_FLIPPER_INDEX: issuedetailezt.FlipperIndex,
urls.ISSUE_ENTRY: issueentry.IssueEntry,
urls.ISSUE_ENTRY_AFTER_LOGIN: issueentryafterlogin.IssueEntryAfterLogin,
urls.ISSUE_TIPS: issuetips.IssueSearchTips,
urls.ISSUE_ATTACHMENT: issueattachment.AttachmentPage,
urls.ISSUE_ATTACHMENT_TEXT: issueattachmenttext.AttachmentText,
urls.ISSUE_BULK_EDIT: issuebulkedit.IssueBulkEdit,
urls.COMPONENT_CREATE: componentcreate.ComponentCreate,
urls.COMPONENT_DETAIL: componentdetail.ComponentDetail,
urls.FIELD_CREATE: fieldcreate.FieldCreate,
urls.FIELD_DETAIL: fielddetail.FieldDetail,
urls.TEMPLATE_CREATE: templatecreate.TemplateCreate,
urls.TEMPLATE_DETAIL: templatedetail.TemplateDetail,
urls.WIKI_LIST: redirects.WikiRedirect,
urls.WIKI_PAGE: redirects.WikiRedirect,
urls.SOURCE_PAGE: redirects.SourceRedirect,
urls.ADMIN_STATUSES: issueadmin.AdminStatuses,
urls.ADMIN_LABELS: issueadmin.AdminLabels,
urls.ADMIN_RULES: issueadmin.AdminRules,
urls.ADMIN_TEMPLATES: issueadmin.AdminTemplates,
urls.ADMIN_COMPONENTS: issueadmin.AdminComponents,
urls.ADMIN_VIEWS: issueadmin.AdminViews,
urls.ISSUE_ORIGINAL: issueoriginal.IssueOriginal,
urls.ISSUE_EXPORT: issueexport.IssueExport,
urls.ISSUE_EXPORT_JSON: issueexport.IssueExportJSON,
urls.ISSUE_IMPORT: issueimport.IssueImport,
urls.SPAM_MODERATION_QUEUE: spam.ModerationQueue,
})
# GETs for /issues/detail are now handled by the polymer page.
# Whereas GETs and POSTs to /issues/detail.do and /issues/detail_ezt.*
# are handled by the EZT servlet.
base = '/p/<project_name:%s>' % self._PROJECT_NAME_REGEX
self._AddRoute(base + urls.ISSUE_DETAIL,
webcomponentspage.WebComponentsPage, 'GET')
self._AddRoute(base + urls.ISSUE_DETAIL + '.do',
issuedetailezt.IssueDetailEzt, 'POST')
self._AddRoute(base + urls.ISSUE_DETAIL_LEGACY,
issuedetailezt.IssueDetailEzt, 'GET')
self._AddRoute(base + urls.ISSUE_DETAIL_LEGACY + '.do',
issuedetailezt.IssueDetailEzt, 'POST')
self._SetupUserServlets({
urls.SAVED_QUERIES: savedqueries.SavedQueries,
urls.HOTLISTS: userhotlists.UserHotlists,
})
user_hotlists_redir = registerpages_helpers.MakeRedirectInScope(
urls.HOTLISTS, 'u', keep_qs=True)
self._SetupUserServlets({
'/hotlists/': user_hotlists_redir,
})
# These servlets accept POST, but never write to the database, so they can
# still be used when the site is read-only.
self._SetupProjectServlets({
urls.ISSUE_ADVSEARCH: issueadvsearch.IssueAdvancedSearch,
}, post_does_write=False)
list_redir = registerpages_helpers.MakeRedirectInScope(
urls.ISSUE_LIST, 'p', keep_qs=True)
self._SetupProjectServlets({
'': list_redir,
'/': list_redir,
'/issues': list_redir,
'/issues/': list_redir,
})
list_redir = registerpages_helpers.MakeRedirect(urls.ISSUE_LIST)
self._SetupServlets({
'/issues': list_redir,
'/issues/': list_redir,
})
def _RegisterFrameworkHandlers(self):
"""Register page and form handlers for framework functionality."""
self._SetupServlets({
urls.CSP_REPORT: csp_report.CSPReportPage,
# These are only shown to users if specific conditions are met.
urls.NONPROJECT_COLLISION: artifactcollision.ArtifactCollision,
urls.EXCESSIVE_ACTIVITY: excessiveactivity.ExcessiveActivity,
urls.BANNED: banned.Banned,
urls.PROJECT_MOVED: moved.ProjectMoved,
# These are not externally accessible
urls.RAMCACHE_CONSOLIDATE_CRON: cachemanager_svc.RamCacheConsolidate,
urls.REAP_CRON: reap.Reap,
urls.SPAM_DATA_EXPORT_CRON: spammodel.TrainingDataExport,
urls.LOAD_API_CLIENT_CONFIGS_CRON: (
client_config_svc.LoadApiClientConfigs),
urls.CLIENT_MON: clientmon.ClientMonitor,
urls.TRIM_VISITED_PAGES_CRON: trimvisitedpages.TrimVisitedPages,
urls.TS_MON_JS: ts_mon_js.MonorailTSMonJSHandler,
urls.WARMUP: warmup.Warmup,
urls.START: warmup.Start,
urls.STOP: warmup.Stop
})
self._SetupProjectServlets({
# Collisions can happen on artifacts within a project or outside.
urls.ARTIFACT_COLLISION: artifactcollision.ArtifactCollision,
})
def _RegisterSitewideHandlers(self):
"""Register page and form handlers that aren't associated with projects."""
self._SetupServlets({
urls.PROJECT_CREATE: projectcreate.ProjectCreate,
# The user settings page is a site-wide servlet, not under /u/.
urls.USER_SETTINGS: usersettings.UserSettings,
urls.HOSTING_HOME: hostinghome.HostingHome,
urls.GROUP_CREATE: groupcreate.GroupCreate,
urls.GROUP_LIST: grouplist.GroupList,
urls.GROUP_DELETE: grouplist.GroupList,
urls.HOTLIST_CREATE: hotlistcreate.HotlistCreate,
urls.BAN_SPAMMER_TASK: banspammer.BanSpammerTask,
urls.WIPEOUT_SYNC_CRON: deleteusers.WipeoutSyncCron,
urls.SEND_WIPEOUT_USER_LISTS_TASK: deleteusers.SendWipeoutUserListsTask,
urls.DELETE_WIPEOUT_USERS_TASK: deleteusers.DeleteWipeoutUsersTask,
urls.DELETE_USERS_TASK: deleteusers.DeleteUsersTask,
})
self._SetupUserServlets({
urls.USER_PROFILE: userprofile.UserProfile,
urls.USER_PROFILE_POLYMER: userprofile.UserProfilePolymer,
urls.BAN_USER: userprofile.BanUser,
urls.BAN_SPAMMER: banspammer.BanSpammer,
urls.USER_CLEAR_BOUNCING: userclearbouncing.UserClearBouncing,
urls.USER_UPDATES_PROJECTS: userupdates.UserUpdatesProjects,
urls.USER_UPDATES_DEVELOPERS: userupdates.UserUpdatesDevelopers,
urls.USER_UPDATES_MINE: userupdates.UserUpdatesIndividual,
})
self._SetupUserHotlistServlets({
urls.HOTLIST_ISSUES: hotlistissues.HotlistIssues,
urls.HOTLIST_ISSUES_CSV: hotlistissuescsv.HotlistIssuesCsv,
urls.HOTLIST_PEOPLE: hotlistpeople.HotlistPeopleList,
urls.HOTLIST_DETAIL: hotlistdetails.HotlistDetails,
urls.HOTLIST_RERANK_JSON: rerankhotlist.RerankHotlistIssue,
})
profile_redir = registerpages_helpers.MakeRedirectInScope(
urls.USER_PROFILE, 'u')
self._SetupUserServlets({'': profile_redir})
self._SetupGroupServlets({
urls.GROUP_DETAIL: groupdetail.GroupDetail,
urls.GROUP_ADMIN: groupadmin.GroupAdmin,
})
def _RegisterRedirects(self):
"""Register redirects among pages inside monorail."""
redirect = registerpages_helpers.MakeRedirect('/hosting/')
self._SetupServlets({
'/hosting': redirect,
'/p': redirect,
'/p/': redirect,
'/u': redirect,
'/u/': redirect,
'/': redirect,
})
redirect = registerpages_helpers.MakeRedirectInScope(
urls.PEOPLE_LIST, 'p')
self._SetupProjectServlets({
'/people': redirect,
'/people/': redirect,
})
redirect = registerpages_helpers.MakeRedirect(urls.GROUP_LIST)
self._SetupServlets({'/g': redirect})
group_redir = registerpages_helpers.MakeRedirectInScope(
urls.USER_PROFILE, 'g')
self._SetupGroupServlets({'': group_redir})
def _RegisterInboundMail(self):
"""Register a handler for inbound email and email bounces."""
self.routes.append(webapp2.Route(
'/_ah/mail/<project_addr:.+>',
handler=inboundemail.InboundEmail,
methods=['POST', 'GET']))
self.routes.append(webapp2.Route(
'/_ah/bounce',
handler=inboundemail.BouncedEmail,
methods=['POST', 'GET']))
def _RegisterErrorPages(self):
"""Register handlers for errors."""
self._AddRoute(
'/p/<project_name:%s>/<unrecognized:.+>' % self._PROJECT_NAME_REGEX,
custom_404.ErrorPage, 'GET')