Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion montage/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import logging

from clastic import Application, StaticFileRoute, MetaApplication
from clastic.meta import DEFAULT_PERIPHERALS, ResourcePeripheral

from clastic.static import StaticApplication
from clastic.middleware import HTTPCacheMiddleware
Expand Down Expand Up @@ -46,6 +47,26 @@
'templates')


class ResourceNamesPeripheral(ResourcePeripheral):
"""/meta's resource section with names and types only. clastic's
default shows the first 70 characters of each resource's repr(),
which for 'config' showed config values to anyone."""

def get_context(self, _application):
resources = []
for key, val in _application.resources.items():
desc = type(val).__name__
if isinstance(val, (dict, list, tuple, set)):
desc += ' (%s items)' % len(val)
resources.append({'key': key, 'value': desc})
return {'resources': resources}


META_PERIPHERALS = [ResourceNamesPeripheral()
if isinstance(peri, ResourcePeripheral) else peri
for peri in DEFAULT_PERIPHERALS]


def set_mysql_session_charset_and_collation(connection, branch):
# https://dev.mysql.com/doc/refman/8.0/en/set-names.html
connection.execute("SET NAMES 'utf8mb4' COLLATE 'utf8mb4_unicode_ci'")
Expand Down Expand Up @@ -197,7 +218,7 @@ def get_engine():
('/', static_app),
('/', ui_app),
('/v1/', api_app),
('/meta', MetaApplication())],
('/meta', MetaApplication(base_peripherals=META_PERIPHERALS))],
resources={'config': config},
middlewares=root_mws)

Expand Down
6 changes: 4 additions & 2 deletions montage/meta_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,10 @@ def post_frontend_error_log(user, config, request_dict):
return


def get_frontend_error_log(config, request_dict):
# TODO
def get_frontend_error_log(config, user, request_dict):
# usernames and error payloads: maintainers only, like the other logs
if not user or not user.is_maintainer:
raise Forbidden()
request_dict = request_dict or dict()
count = int(request_dict.get('count', DEFAULT_LINE_COUNT))
feel_path = config.get('feel_log_path', None)
Expand Down
33 changes: 33 additions & 0 deletions montage/tests/test_web_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,39 @@ def test_home_client(base_client, api_client, mock_external_apis):
#resp = base_client.fetch('public: logout', '/logout')


def test_meta_shows_no_config_values(montage_app, base_client):
"""/meta and /meta/json/ are public; they list the app's resources by
name, but must not show config values (clastic's default showed the
first 70 characters of repr(config))."""
config = montage_app.resources['config']
values = [config.get(key) for key in ('cookie_secret', 'db_url',
'oauth_client_id',
'oauth_client_secret')]
values = [v for v in values if isinstance(v, str) and len(v) >= 6]
assert values # the devtest config has some of these

for url in ('/meta/', '/meta/json/'):
resp = base_client._test_client.get(url)
assert resp.status_code == 200
body = resp.get_data(as_text=True)
assert 'config' in body
for value in values:
assert value not in body, '%s shows a config value' % url
assert "{'" not in body.replace(''', "'")


def test_frontend_error_log_maintainers_only(montage_app, api_client,
monkeypatch):
from montage import rdb
# every test user is a maintainer; make LilyOfTheWest a plain coordinator
monkeypatch.setattr(rdb, 'MAINTAINERS',
[m for m in rdb.MAINTAINERS if m != 'LilyOfTheWest'])

api_client.fetch('coordinator: read frontend error log', '/logs/feel',
as_user='LilyOfTheWest', error_code=403)
api_client.fetch('maintainer: read frontend error log', '/logs/feel')


def test_multiple_jurors(api_client, mock_external_apis):
# This is copied from above. What's the best way to break up the tests into
# various stages? Should I use a pytest.fixture?
Expand Down
Loading