summaryrefslogtreecommitdiff
path: root/gae/webapp/src/handlers/base.py
blob: 2862ecb91a40f38267b21970fd0c818cb76264d2 (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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
#
# Copyright (C) 2018 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import datetime
import httplib
import logging
import os
import urlparse

from google.appengine.api import users
import stripe
import webapp2
from webapp2_extras import jinja2 as wa2_jinja2
from webapp2_extras import sessions

import errors
from webapp.src.utils import datetime_util


class BaseHandler(webapp2.RequestHandler):
    """BaseHandler for all requests."""

    def initialize(self, request, response):
        """Initializes this request handler."""
        webapp2.RequestHandler.initialize(self, request, response)
        self.session_backend = 'datastore'

    def verify_origin(self):
        """This function will check the request is comming from the same domain."""
        server_host = os.environ.get('ENDPOINTS_SERVICE_NAME')
        request_host = self.request.headers.get('Host')
        request_referer = self.request.headers.get('Referer')
        if request_referer:
            request_referer = urlparse.urlsplit(request_referer)[1]
        else:
            request_referer = request_host
        logging.info('server: %s, request: %s', server_host, request_referer)
        if server_host and request_referer and server_host != request_referer:
            raise errors.Error(httplib.FORBIDDEN)

    def dispatch(self):
        """Dispatch the request.

        This will first check if there's a handler_method defined
        in the matched route, and if not it'll use the method correspondent to
        the request method (get(), post() etc).
        """
        self.session_store = sessions.get_store(request=self.request)
        # Forwards the method for RESTful support.
        self.forward_method()
        # Security headers.
        # https://www.owasp.org/index.php/List_of_useful_HTTP_headers
        self.response.headers['x-content-type-options'] = 'nosniff'
        self.response.headers['x-frame-options'] = 'SAMEORIGIN'
        self.response.headers['x-xss-protection'] = '1; mode=block'
        try:
            webapp2.RequestHandler.dispatch(self)
        finally:
            self.session_store.save_sessions(self.response)
        # Disabled for now because host is appspot.com in production.
        #self.verify_origin()

    @webapp2.cached_property
    def session(self):
        # Returns a session using the default cookie key.
        return self.session_store.get_session()

    def handle_exception(self, exception, debug=False):
        """Render the exception as HTML."""
        logging.exception(exception)

        # Create response dictionary and status defaults.
        tpl = 'error.html'
        status = httplib.INTERNAL_SERVER_ERROR
        resp_dict = {
            'message': 'A server error occurred.',
        }
        url_parts = self.urlsplit()
        redirect_url = '%s?%s' % (url_parts[2], url_parts[4])

        # Use error code if a HTTPException, or generic 500.
        if isinstance(exception, webapp2.HTTPException):
            status = exception.code
            resp_dict['message'] = exception.detail
        elif isinstance(exception, errors.FormValidationError):
            status = exception.code
            resp_dict['message'] = exception.msg
            resp_dict['errors'] = exception.errors
            self.session['form_errors'] = exception.errors
            # Redirect user to current view URL.
            return self.redirect(redirect_url)
        elif isinstance(exception, stripe.StripeError):
            status = exception.http_status
            resp_dict['errors'] = exception.json_body['error']['message']
            self.session['form_errors'] = [
                exception.json_body['error']['message']
            ]
            return self.redirect(redirect_url)
        elif isinstance(exception, (errors.Error, errors.AclError)):
            status = exception.code
            resp_dict['message'] = exception.msg

        resp_dict['status'] = status

        # Render output.
        self.response.status_int = status
        self.response.status_message = httplib.responses[status]
        # Render the exception response into the error template.
        self.response.write(self.jinja2.render_template(tpl, **resp_dict))

    # @Override
    def get(self, *args, **kwargs):
        self.abort(httplib.NOT_IMPLEMENTED)

    # @Override
    def post(self, *args, **kwargs):
        self.abort(httplib.NOT_IMPLEMENTED)

    # @Override
    def put(self, *args, **kwargs):
        self.abort(httplib.NOT_IMPLEMENTED)

    # @Override
    def delete(self, *args, **kwargs):
        self.abort(httplib.NOT_IMPLEMENTED)

    # @Override
    def head(self, *args, **kwargs):
        pass

    def urlsplit(self):
        """Return a tuple of the URL."""
        return urlparse.urlsplit(self.request.url)

    def path(self):
        """Returns the path of the current URL."""
        return self.urlsplit()[2]

    def forward_method(self):
        """Check for a method override param and change in the request."""
        valid = (None, 'get', 'post', 'put', 'delete', 'head', 'options')
        method = self.request.POST.get('__method__')
        if not method:  # Backbone's _method parameter.
            method = self.request.POST.get('_method')
        if method not in valid:
            logging.debug('Invalid method %s requested!', method)
            method = None
        logging.debug('Method being changed from %s to %s by request',
                      self.request.route.handler_method, method)
        self.request.route.handler_method = method

    def render(self, resp, status=httplib.OK):
        """Render the response as HTML."""
        user = users.get_current_user()
        if user:
            url = users.create_logout_url(self.request.uri)
            url_linktext = "Logout"
        else:
            url = users.create_login_url(self.request.uri)
            url_linktext = "Login"

        resp.update({
            # Defaults go here.
            'now': datetime.datetime.now(),
            'dest_url': str(self.request.get('dest_url', '')),
            'form_errors': self.session.pop('form_errors', []),
            'user': user,
            'url': url,
            'url_linktext': url_linktext,
            "convert_time": datetime_util.GetTimeWithTimezone
        })

        if 'preload' not in resp:
            resp['preload'] = {}

        self.response.status_int = status
        self.response.status_message = httplib.responses[status]
        self.response.write(self.jinja2.render_template(self.template, **resp))

    @webapp2.cached_property
    def jinja2(self):
        """Returns a Jinja2 renderer cached in the app registry."""
        jinja_config = {
            'template_path':
            os.path.join(os.path.dirname(__file__), "../../static"),
            'compiled_path':
            None,
            'force_compiled':
            False,
            'environment_args': {
                'autoescape': True,
                'extensions': [
                    'jinja2.ext.autoescape',
                    'jinja2.ext.with_',
                ],
            },
            'globals':
            None,
            'filters':
            None,
        }
        return wa2_jinja2.Jinja2(app=self.app, config=jinja_config)