diff options
-rw-r--r-- | service/pixelated/resources/root_resource.py | 127 | ||||
-rw-r--r-- | service/test/unit/resources/test_root_resource.py | 107 |
2 files changed, 127 insertions, 107 deletions
diff --git a/service/pixelated/resources/root_resource.py b/service/pixelated/resources/root_resource.py index 5477dca8..d35147f5 100644 --- a/service/pixelated/resources/root_resource.py +++ b/service/pixelated/resources/root_resource.py @@ -19,6 +19,7 @@ import os from string import Template from pixelated.resources.users import UsersResource +import pixelated from pixelated.resources import BaseResource, UnAuthorizedResource, UnavailableResource from pixelated.resources import IPixelatedSession from pixelated.resources.attachments_resource import AttachmentsResource @@ -47,44 +48,66 @@ MODE_STARTUP = 1 MODE_RUNNING = 2 -class PublicRootResource(BaseResource, object): +class InboxResource(BaseResource): + isLeaf = True - def __init__(self, services_factory, assets_path, **kwargs): - super(PublicRootResource, self).__init__(services_factory) - self._child_resources = dict( - assets=File(assets_path), - login=LoginResource(services_factory, **{k: kwargs[k] for k in kwargs if k in ('provider', 'disclaimer_banner', 'authenticator')}) - ) + def __init__(self, services_factory): + BaseResource.__init__(self, services_factory) + self._templates_folder = self._get_templates_folder() + self._html_template = open(os.path.join(self._templates_folder, 'index.html')).read() + with open(os.path.join(self._templates_folder, 'Interstitial.html')) as f: + self.interstitial = f.read() + self._mode = MODE_STARTUP + + def initialize(self): + self._mode = MODE_RUNNING - def getChild(self, path, request): - return self._child_resources.get(path) or NoResource() + def _get_templates_folder(self): + path = os.path.dirname(os.path.abspath(pixelated.__file__)) + return os.path.join(path, 'assets') + + def _add_csrf_cookie(self, request): + csrf_token = hashlib.sha256(os.urandom(CSRF_TOKEN_LENGTH)).hexdigest() + request.addCookie('XSRF-TOKEN', csrf_token) + + def _is_starting(self): + return self._mode == MODE_STARTUP + + def render_GET(self, request): + self._add_csrf_cookie(request) + if self._is_starting(): + return self.interstitial + else: + account_email = self.mail_service(request).account_email + response = Template(self._html_template).safe_substitute(account_email=account_email) + return str(response) class RootResource(BaseResource): def __init__(self, services_factory): BaseResource.__init__(self, services_factory) + self._assets_folder = self._get_assets_folder() self._startup_assets_folder = self._get_startup_folder() - self._public_assets_folder = self._get_public_folder() self._static_folder = self._get_static_folder() self._html_template = open(os.path.join(self._static_folder, 'index.html')).read() self._services_factory = services_factory - self._child_resources = ChildResourcesMap() with open(os.path.join(self._startup_assets_folder, 'Interstitial.html')) as f: self.interstitial = f.read() + self._inbox_resource = InboxResource(services_factory) self._startup_mode() def _startup_mode(self): + self.putChild('assets', File(self._assets_folder)) self.putChild('startup-assets', File(self._startup_assets_folder)) - self.putChild('public-assets', File(self._public_assets_folder)) self._mode = MODE_STARTUP - def getChild(self, path, request): + def getChildWithDefault(self, path, request): if path == '': - return self + return self._inbox_resource if self._mode == MODE_STARTUP: return UnavailableResource() if self._is_xsrf_valid(request): - return self._child_resources.get(path) + return BaseResource.getChildWithDefault(self, path, request) return UnAuthorizedResource() def _is_xsrf_valid(self, request): @@ -103,40 +126,33 @@ class RootResource(BaseResource): return csrf_input and csrf_input == xsrf_token def initialize(self, provider=None, disclaimer_banner=None, authenticator=None): - self._child_resources.add('sandbox', SandboxResource(self._static_folder)) - self._child_resources.add('assets', File(self._static_folder)) - self._child_resources.add('keys', KeysResource(self._services_factory)) - self._child_resources.add(AttachmentsResource.BASE_URL, AttachmentsResource(self._services_factory)) - self._child_resources.add('contacts', ContactsResource(self._services_factory)) - self._child_resources.add('features', FeaturesResource(provider)) - self._child_resources.add('tags', TagsResource(self._services_factory)) - self._child_resources.add('mails', MailsResource(self._services_factory)) - self._child_resources.add('mail', MailResource(self._services_factory)) - self._child_resources.add('feedback', FeedbackResource(self._services_factory)) - self._child_resources.add('user-settings', UserSettingsResource(self._services_factory)) - self._child_resources.add('users', UsersResource(self._services_factory)) - self._child_resources.add(LoginResource.BASE_URL, - LoginResource(self._services_factory, provider, disclaimer_banner=disclaimer_banner, authenticator=authenticator)) - self._child_resources.add(LogoutResource.BASE_URL, LogoutResource(self._services_factory)) - + self.putChild('sandbox', SandboxResource(self._static_folder)) + self.putChild('keys', KeysResource(self._services_factory)) + self.putChild(AttachmentsResource.BASE_URL, AttachmentsResource(self._services_factory)) + self.putChild('contacts', ContactsResource(self._services_factory)) + self.putChild('features', FeaturesResource(provider)) + self.putChild('tags', TagsResource(self._services_factory)) + self.putChild('mails', MailsResource(self._services_factory)) + self.putChild('mail', MailResource(self._services_factory)) + self.putChild('feedback', FeedbackResource(self._services_factory)) + self.putChild('user-settings', UserSettingsResource(self._services_factory)) + self.putChild('users', UsersResource(self._services_factory)) + self.putChild(LoginResource.BASE_URL, + LoginResource(self._services_factory, provider, disclaimer_banner=disclaimer_banner, authenticator=authenticator)) + self.putChild(LogoutResource.BASE_URL, LogoutResource(self._services_factory)) + + self._inbox_resource.initialize() self._mode = MODE_RUNNING + def _get_assets_folder(self): + pixelated_path = os.path.dirname(os.path.abspath(pixelated.__file__)) + return os.path.join(pixelated_path, '..', '..', 'web-ui', 'public') + # TODO: use the public folder for this def _get_startup_folder(self): path = os.path.dirname(os.path.abspath(__file__)) return os.path.join(path, '..', 'assets') - def _get_public_folder(self): - public_folder = os.path.abspath(os.path.join(os.path.abspath(__file__), "..", "..", "..", "web-ui", "public")) - # this is a workaround for packaging - if not os.path.exists(public_folder): - public_folder = os.path.abspath( - os.path.join(os.path.abspath(__file__), "..", "..", "..", "..", "web-ui", "public")) - if not os.path.exists(public_folder): - # TODO: how is this packaged? - public_folder = os.path.join('/', 'usr', 'share', 'pixelated-user-agent') - return public_folder - def _get_static_folder(self): static_folder = os.path.abspath(os.path.join(os.path.abspath(__file__), "..", "..", "..", "web-ui", "app")) # this is a workaround for packaging @@ -146,30 +162,3 @@ class RootResource(BaseResource): if not os.path.exists(static_folder): static_folder = os.path.join('/', 'usr', 'share', 'pixelated-user-agent') return static_folder - - def _is_starting(self): - return self._mode == MODE_STARTUP - - def _add_csrf_cookie(self, request): - csrf_token = hashlib.sha256(os.urandom(CSRF_TOKEN_LENGTH)).hexdigest() - request.addCookie('XSRF-TOKEN', csrf_token) - - def render_GET(self, request): - self._add_csrf_cookie(request) - if self._is_starting(): - return self.interstitial - else: - account_email = self.mail_service(request).account_email - response = Template(self._html_template).safe_substitute(account_email=account_email) - return str(response) - - -class ChildResourcesMap(object): - def __init__(self): - self._registry = {} - - def add(self, path, resource): - self._registry[path] = resource - - def get(self, path): - return self._registry.get(path) or NoResource() diff --git a/service/test/unit/resources/test_root_resource.py b/service/test/unit/resources/test_root_resource.py index 9b3042a8..2c74d7b9 100644 --- a/service/test/unit/resources/test_root_resource.py +++ b/service/test/unit/resources/test_root_resource.py @@ -14,39 +14,7 @@ from twisted.trial import unittest from twisted.web.resource import IResource from twisted.web.static import File from twisted.web.test.requesthelper import DummyRequest -from pixelated.resources.root_resource import PublicRootResource, RootResource, MODE_STARTUP, MODE_RUNNING - - -class TestPublicRootResource(unittest.TestCase): - - def setUp(self): - self.portal_mock = mock() - assets_path = os.path.abspath( - os.path.join(os.path.abspath(pixelated.__file__), '..', '..', '..', 'web-ui', 'public') - ) - services_factory = mock() - self.public_root_resource = PublicRootResource(services_factory, assets_path=assets_path, provider=mock()) - self.web = DummySite(self.public_root_resource) - - def test_assets_should_be_available(self): - request = DummyRequest(['assets', 'dummy.json']) - d = self.web.get(request) - - def assert_response(_): - self.assertEqual(200, request.responseCode) - - d.addCallback(assert_response) - return d - - def test_login_should_be_available(self): - request = DummyRequest(['login']) - d = self.web.get(request) - - def assert_response(_): - self.assertEqual(200, request.responseCode) - - d.addCallback(assert_response) - return d +from pixelated.resources.root_resource import InboxResource, RootResource, MODE_STARTUP, MODE_RUNNING class TestRootResource(unittest.TestCase): @@ -63,12 +31,13 @@ class TestRootResource(unittest.TestCase): self.mail_service.account_email = self.MAIL_ADDRESS root_resource = RootResource(self.services_factory) - root_resource._html_template = "<html><head><title>$account_email</title></head></html>" - root_resource._mode = root_resource self.web = DummySite(root_resource) self.root_resource = root_resource def test_render_GET_should_template_account_email(self): + self.root_resource._inbox_resource._html_template = "<html><head><title>$account_email</title></head></html>" + self.root_resource.initialize(provider=mock(), authenticator=mock()) + request = DummyRequest(['']) request.addCookie = lambda key, value: 'stubbed' @@ -126,6 +95,8 @@ class TestRootResource(unittest.TestCase): request.requestHeaders.setRawHeaders('x-xsrf-token', [csrf_token]) def test_should_unauthorize_child_resource_ajax_requests_when_csrf_mismatch(self): + self.root_resource.initialize(provider=mock(), authenticator=mock()) + request = DummyRequest(['/child']) request.method = 'POST' self._mock_ajax_csrf(request, 'stubbed csrf token') @@ -141,10 +112,25 @@ class TestRootResource(unittest.TestCase): d.addCallback(assert_unauthorized) return d + def test_GET_should_return_503_for_uninitialized_resource(self): + request = DummyRequest(['/sandbox/']) + request.method = 'GET' + + request.getCookie = MagicMock(return_value='stubbed csrf token') + + d = self.web.get(request) + + def assert_unavailable(_): + self.assertEqual(503, request.responseCode) + + d.addCallback(assert_unavailable) + return d + def test_GET_should_return_404_for_non_existing_resource(self): + self.root_resource.initialize(provider=mock(), authenticator=mock()) + request = DummyRequest(['/non-existing-child']) request.method = 'GET' - request.getCookie = MagicMock(return_value='stubbed csrf token') d = self.web.get(request) @@ -156,10 +142,11 @@ class TestRootResource(unittest.TestCase): return d def test_should_404_non_existing_resource_with_valid_csrf(self): + self.root_resource.initialize(provider=mock(), authenticator=mock()) + request = DummyRequest(['/non-existing-child']) request.method = 'POST' self._mock_ajax_csrf(request, 'stubbed csrf token') - request.getCookie = MagicMock(return_value='stubbed csrf token') d = self.web.get(request) @@ -175,7 +162,7 @@ class TestRootResource(unittest.TestCase): request = DummyRequest(['features']) request.getCookie = MagicMock(return_value='irrelevant -- stubbed') - self.root_resource._child_resources.add('features', FeaturesResource()) + self.root_resource.putChild('features', FeaturesResource()) self.root_resource._mode = MODE_RUNNING d = self.web.get(request) @@ -187,6 +174,8 @@ class TestRootResource(unittest.TestCase): return d def test_should_unauthorize_child_resource_non_ajax_POST_requests_when_csrf_input_mismatch(self): + self.root_resource.initialize(provider=mock(), authenticator=mock()) + request = DummyRequest(['mails']) request.method = 'POST' request.addArg('csrftoken', 'some csrf token') @@ -204,3 +193,45 @@ class TestRootResource(unittest.TestCase): d.addCallback(assert_unauthorized) return d + + def test_assets_should_be_publicly_available(self): + self.root_resource.initialize(provider=mock(), authenticator=mock()) + + request = DummyRequest(['assets', 'dummy.json']) + d = self.web.get(request) + + def assert_response(_): + self.assertEqual(200, request.responseCode) + + d.addCallback(assert_response) + return d + + def test_login_should_be_publicly_available(self): + self.root_resource.initialize(provider=mock(), authenticator=mock()) + + request = DummyRequest(['login']) + d = self.web.get(request) + + def assert_response(_): + self.assertEqual(200, request.responseCode) + + d.addCallback(assert_response) + return d + + def test_root_should_be_handled_by_inbox_resource(self): + request = DummyRequest([]) + request.prepath = [''] + request.path = '/' + # TODO: setup mocked portal + + resource = self.root_resource.getChildWithDefault(request.prepath[-1], request) + self.assertIsInstance(resource, InboxResource) + + def test_inbox_should_not_be_public(self): + request = DummyRequest([]) + request.prepath = [''] + request.path = '/' + # TODO: setup mocked portal + + resource = self.root_resource.getChildWithDefault(request.prepath[-1], request) + self.assertIsInstance(resource, InboxResource) |