[feature] allow to pass a static_folder to RootResource
[pixelated-user-agent.git] / service / pixelated / resources / root_resource.py
1 #
2 # Copyright (c) 2016 ThoughtWorks, Inc.
3 #
4 # Pixelated is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # Pixelated is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU Affero General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
16 import hashlib
17 import json
18 import os
19 from string import Template
20 from pixelated.resources.users import UsersResource
21
22 from pixelated.resources import BaseResource, UnAuthorizedResource, UnavailableResource
23 from pixelated.resources import get_public_static_folder, get_protected_static_folder
24 from pixelated.resources.attachments_resource import AttachmentsResource
25 from pixelated.resources.sandbox_resource import SandboxResource
26 from pixelated.resources.account_recovery_resource import AccountRecoveryResource
27 from pixelated.resources.backup_account_resource import BackupAccountResource
28 from pixelated.resources.contacts_resource import ContactsResource
29 from pixelated.resources.features_resource import FeaturesResource
30 from pixelated.resources.feedback_resource import FeedbackResource
31 from pixelated.resources.login_resource import LoginResource, LoginStatusResource
32 from pixelated.resources.logout_resource import LogoutResource
33 from pixelated.resources.user_settings_resource import UserSettingsResource
34 from pixelated.resources.mail_resource import MailResource
35 from pixelated.resources.mails_resource import MailsResource
36 from pixelated.resources.tags_resource import TagsResource
37 from pixelated.resources.keys_resource import KeysResource
38 from twisted.web.resource import NoResource
39 from twisted.web.static import File
40
41 from twisted.logger import Logger
42
43 log = Logger()
44
45
46 CSRF_TOKEN_LENGTH = 32
47
48 MODE_STARTUP = 1
49 MODE_RUNNING = 2
50
51
52 class RootResource(BaseResource):
53     def __init__(self, services_factory, static_folder=None):
54         BaseResource.__init__(self, services_factory)
55         self._public_static_folder = get_public_static_folder(static_folder)
56         self._protected_static_folder = get_protected_static_folder(static_folder)
57         self._html_template = open(os.path.join(self._protected_static_folder, 'index.html')).read()
58         self._services_factory = services_factory
59         self._child_resources = ChildResourcesMap()
60         with open(os.path.join(self._public_static_folder, 'interstitial.html')) as f:
61             self.interstitial = f.read()
62         self._startup_mode()
63
64     def _startup_mode(self):
65         self.putChild('public', File(self._public_static_folder))
66         self.putChild('status', LoginStatusResource(self._services_factory))
67         self._mode = MODE_STARTUP
68
69     def getChild(self, path, request):
70         if path == '':
71             return self
72         if self._mode == MODE_STARTUP:
73             return UnavailableResource()
74         if self._is_xsrf_valid(request):
75             return self._child_resources.get(path)
76         return UnAuthorizedResource()
77
78     def _is_xsrf_valid(self, request):
79         get_request = (request.method == 'GET')
80         if get_request:
81             return True
82
83         xsrf_token = request.getCookie('XSRF-TOKEN')
84
85         ajax_request = (request.getHeader('x-requested-with') == 'XMLHttpRequest')
86         if ajax_request:
87             xsrf_header = request.getHeader('x-xsrf-token')
88             return xsrf_header and xsrf_header == xsrf_token
89
90         csrf_input = request.args.get('csrftoken', [None])[0] or json.loads(request.content.read()).get('csrftoken', [None])[0]
91         return csrf_input and csrf_input == xsrf_token
92
93     def initialize(self, provider=None, disclaimer_banner=None, authenticator=None):
94         self._child_resources.add('assets', File(self._protected_static_folder))
95         self._child_resources.add(AccountRecoveryResource.BASE_URL, AccountRecoveryResource(self._services_factory))
96         self._child_resources.add('backup-account', BackupAccountResource(self._services_factory, authenticator, provider))
97         self._child_resources.add('sandbox', SandboxResource(self._protected_static_folder))
98         self._child_resources.add('keys', KeysResource(self._services_factory))
99         self._child_resources.add(AttachmentsResource.BASE_URL, AttachmentsResource(self._services_factory))
100         self._child_resources.add('contacts', ContactsResource(self._services_factory))
101         self._child_resources.add('features', FeaturesResource(provider))
102         self._child_resources.add('tags', TagsResource(self._services_factory))
103         self._child_resources.add('mails', MailsResource(self._services_factory))
104         self._child_resources.add('mail', MailResource(self._services_factory))
105         self._child_resources.add('feedback', FeedbackResource(self._services_factory))
106         self._child_resources.add('user-settings', UserSettingsResource(self._services_factory))
107         self._child_resources.add('users', UsersResource(self._services_factory))
108         self._child_resources.add(LoginResource.BASE_URL,
109                                   LoginResource(self._services_factory, provider, disclaimer_banner=disclaimer_banner, authenticator=authenticator))
110         self._child_resources.add(LogoutResource.BASE_URL, LogoutResource(self._services_factory))
111
112         self._mode = MODE_RUNNING
113
114     def _is_starting(self):
115         return self._mode == MODE_STARTUP
116
117     def _add_csrf_cookie(self, request):
118         csrf_token = hashlib.sha256(os.urandom(CSRF_TOKEN_LENGTH)).hexdigest()
119         request.addCookie('XSRF-TOKEN', csrf_token)
120
121     def render_GET(self, request):
122         self._add_csrf_cookie(request)
123         if self._is_starting():
124             return self.interstitial
125         else:
126             account_email = self.mail_service(request).account_email
127             response = Template(self._html_template).safe_substitute(account_email=account_email)
128             return str(response)
129
130
131 class ChildResourcesMap(object):
132     def __init__(self):
133         self._registry = {}
134
135     def add(self, path, resource):
136         self._registry[path] = resource
137
138     def get(self, path):
139         return self._registry.get(path) or NoResource()