Guard cron handlers. (#2229)
This commit is contained in:
Родитель
e7eb6b255d
Коммит
772225dbb3
|
@ -427,6 +427,22 @@ class FlaskHandler(BaseHandler):
|
|||
self.abort(403, msg=('Lacking X-AppEngine-QueueName or '
|
||||
'incorrect X-Appengine-Inbound-Appid headers'))
|
||||
|
||||
def require_cron_header(self):
|
||||
"""Abort if this is not a GAE cron request or from a site admin."""
|
||||
if settings.UNIT_TEST_MODE or settings.DEV_MODE:
|
||||
return
|
||||
if 'X-AppEngine-Cron' in self.request.headers:
|
||||
return
|
||||
user = self.get_current_user(required=True)
|
||||
if permissions.can_admin_site(user):
|
||||
return
|
||||
|
||||
logging.info('non-admin and headers lack X-AppEngine-Cron:')
|
||||
for k, v in self.request.headers:
|
||||
logging.info('%r: %r', k, v)
|
||||
|
||||
self.abort(403, msg='Lacking X-AppEngine-Cron or admin account')
|
||||
|
||||
def split_input(self, field_name, delim='\\r?\\n'):
|
||||
"""Split the input lines, strip whitespace, and skip blank lines."""
|
||||
input_text = flask.request.form.get(field_name) or ''
|
||||
|
|
|
@ -666,6 +666,50 @@ class FlaskHandlerTests(testing_config.CustomTestCase):
|
|||
with self.assertRaises(werkzeug.exceptions.Forbidden):
|
||||
self.handler.require_task_header()
|
||||
|
||||
def test_require_cron_header__while_testing(self):
|
||||
"""During unit testing of cron handlers, we allow it."""
|
||||
with test_app.test_request_context('/test'):
|
||||
self.handler.require_cron_header()
|
||||
|
||||
@mock.patch('settings.UNIT_TEST_MODE', False)
|
||||
def test_require_cron_header__normal(self):
|
||||
"""If the incoming request is from GCT, we allow it."""
|
||||
headers = {'X-AppEngine-Cron': 'true'}
|
||||
with test_app.test_request_context('/test', headers=headers):
|
||||
self.handler.require_cron_header()
|
||||
|
||||
@mock.patch('settings.UNIT_TEST_MODE', False)
|
||||
@mock.patch('framework.basehandlers.BaseHandler.get_current_user')
|
||||
@mock.patch('framework.permissions.can_admin_site')
|
||||
def test_require_cron_header__admin(
|
||||
self, mock_can_admin_site, mock_get_current_user):
|
||||
"""If the incoming request is from an admin, we allow it."""
|
||||
mock_can_admin_site.return_value = True
|
||||
# Also mock get_current_user because it will not use the usual
|
||||
# unit test configuration if we have UNIT_TEST_MODE == False.
|
||||
mock_get_current_user.return_value = users.User('admin@example.com', 111)
|
||||
with test_app.test_request_context('/test'):
|
||||
self.handler.require_cron_header()
|
||||
|
||||
@mock.patch('settings.UNIT_TEST_MODE', False)
|
||||
@mock.patch('framework.basehandlers.BaseHandler.get_current_user')
|
||||
def test_require_cron_header__missing_nonadmin(self, mock_get_current_user):
|
||||
"""If the incoming request is not from GAE, abort."""
|
||||
# Also mock get_current_user because it will not use the usual
|
||||
# unit test configuration if we have UNIT_TEST_MODE == False.
|
||||
mock_get_current_user.return_value = users.User('user1@example.com', 111)
|
||||
with test_app.test_request_context('/test'):
|
||||
with self.assertRaises(werkzeug.exceptions.Forbidden):
|
||||
self.handler.require_cron_header()
|
||||
|
||||
@mock.patch('settings.UNIT_TEST_MODE', False)
|
||||
def test_require_cron_header__missing_anon(self):
|
||||
"""If the incoming request is not from GAE, abort."""
|
||||
testing_config.sign_out()
|
||||
with test_app.test_request_context('/test'):
|
||||
with self.assertRaises(werkzeug.exceptions.Forbidden):
|
||||
self.handler.require_cron_header()
|
||||
|
||||
@mock.patch('settings.UNIT_TEST_MODE', False)
|
||||
@mock.patch('framework.users.get_current_user')
|
||||
def test_require_xsrf_token__normal(self, mock_get_user):
|
||||
|
|
|
@ -33,6 +33,7 @@ class BackupExportHandler(basehandlers.FlaskHandler):
|
|||
"""Triggers a new Datastore export."""
|
||||
|
||||
def get_template_data(self):
|
||||
self.require_cron_header()
|
||||
bucket = f'gs://{settings.BACKUP_BUCKET}'
|
||||
# The default cache (file_cache) is unavailable when using oauth2client >= 4.0.0 or google-auth,
|
||||
# and it will log worrisome messages unless given another interface to use.
|
||||
|
|
|
@ -8,6 +8,7 @@ class WriteStandardMaturityHandler(FlaskHandler):
|
|||
|
||||
def get_template_data(self):
|
||||
"""Writes standard_maturity field from the old standardization field."""
|
||||
self.require_cron_header()
|
||||
q = Feature.query()
|
||||
features = q.fetch()
|
||||
update_count = 0
|
||||
|
@ -17,7 +18,7 @@ class WriteStandardMaturityHandler(FlaskHandler):
|
|||
update_count += 1
|
||||
feature.standard_maturity = STANDARD_MATURITY_BACKFILL[feature.standardization]
|
||||
feature.put(notify=False)
|
||||
|
||||
|
||||
logging.info(
|
||||
f'{update_count} features updated with standard_maturity field.')
|
||||
return 'Success'
|
||||
|
|
|
@ -186,6 +186,7 @@ class YesterdayHandler(basehandlers.FlaskHandler):
|
|||
filename: The filename for the data file to be loaded.
|
||||
today: date passed in for testing, defaults to today.
|
||||
"""
|
||||
self.require_cron_header()
|
||||
days = []
|
||||
date_str = self.request.args.get('date')
|
||||
if date_str:
|
||||
|
@ -244,6 +245,7 @@ class HistogramsHandler(basehandlers.FlaskHandler):
|
|||
)
|
||||
|
||||
def get_template_data(self):
|
||||
self.require_cron_header()
|
||||
# Attempt to fetch enums mapping file.
|
||||
response = requests.get(HISTOGRAMS_URL, timeout=60)
|
||||
|
||||
|
@ -278,5 +280,6 @@ class HistogramsHandler(basehandlers.FlaskHandler):
|
|||
class BlinkComponentHandler(basehandlers.FlaskHandler):
|
||||
"""Updates the list of Blink components in the db."""
|
||||
def get_template_data(self):
|
||||
self.require_cron_header()
|
||||
user_models.BlinkComponent.update_db()
|
||||
return 'Blink components updated'
|
||||
|
|
|
@ -279,7 +279,7 @@ class FeatureAccuracyHandler(basehandlers.FlaskHandler):
|
|||
|
||||
def get_template_data(self):
|
||||
"""Sends notifications to users requesting feature updates for accuracy."""
|
||||
|
||||
self.require_cron_header()
|
||||
features_to_notify = self._determine_features_to_notify()
|
||||
email_tasks = self._build_email_tasks(features_to_notify)
|
||||
send_emails(email_tasks)
|
||||
|
|
Загрузка…
Ссылка в новой задаче