summaryrefslogtreecommitdiff
path: root/service/pixelated/resources/root_resource.py
blob: 109dc08e8ed726d0d68df45191d1c9606c685a4d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#
# Copyright (c) 2016 ThoughtWorks, Inc.
#
# Pixelated is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pixelated is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
import hashlib
import json
import os
from string import Template

from pixelated.resources import BaseResource, UnAuthorizedResource
from pixelated.resources.attachments_resource import AttachmentsResource
from pixelated.resources.sandbox_resource import SandboxResource
from pixelated.resources.contacts_resource import ContactsResource
from pixelated.resources.features_resource import FeaturesResource
from pixelated.resources.feedback_resource import FeedbackResource
from pixelated.resources.login_resource import LoginResource
from pixelated.resources.logout_resource import LogoutResource
from pixelated.resources.user_settings_resource import UserSettingsResource
from pixelated.resources.mail_resource import MailResource
from pixelated.resources.mails_resource import MailsResource
from pixelated.resources.tags_resource import TagsResource
from pixelated.resources.keys_resource import KeysResource
from twisted.web.static import File

CSRF_TOKEN_LENGTH = 32

MODE_STARTUP = 1
MODE_RUNNING = 2


class RootResource(BaseResource):
    def __init__(self, services_factory):
        BaseResource.__init__(self, services_factory)
        self._startup_assets_folder = self._get_startup_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()
        self._startup_mode()

    def _startup_mode(self):
        self.putChild('startup-assets', File(self._startup_assets_folder))
        self._mode = MODE_STARTUP

    def getChild(self, path, request):
        if path == '':
            return self
        if self._is_xsrf_valid(request):
            return self._child_resources.get(path)
        return UnAuthorizedResource()

    def _is_xsrf_valid(self, request):
        xsrf_token = request.getCookie('XSRF-TOKEN')

        ajax_request = (request.getHeader('x-requested-with') == 'XMLHttpRequest')
        if ajax_request:
            xsrf_header = request.getHeader('x-xsrf-token')
            return xsrf_header and xsrf_header == xsrf_token

        get_request = (request.method == 'GET')
        if get_request:
            return True

        csrf_input = request.args.get('csrftoken', [None])[0] or json.loads(request.content.read()).get('csrftoken', [None])[0]
        return csrf_input and csrf_input == xsrf_token

    def initialize(self, portal=None, disclaimer_banner=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(portal))
        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(LoginResource.BASE_URL,
                                  LoginResource(self._services_factory, portal, disclaimer_banner=disclaimer_banner))
        self._child_resources.add(LogoutResource.BASE_URL, LogoutResource(self._services_factory))

        self._mode = MODE_RUNNING

    def _get_startup_folder(self):
        path = os.path.dirname(os.path.abspath(__file__))
        return os.path.join(path, '..', 'assets')

    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
        if not os.path.exists(static_folder):
            static_folder = os.path.abspath(
                os.path.join(os.path.abspath(__file__), "..", "..", "..", "..", "web-ui", "app"))
        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 open(os.path.join(self._startup_assets_folder, 'Interstitial.html')).read()
        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)