Home | History | Annotate | Download | only in handlers
      1 #
      2 # Copyright (C) 2018 The Android Open Source Project
      3 #
      4 # Licensed under the Apache License, Version 2.0 (the "License");
      5 # you may not use this file except in compliance with the License.
      6 # You may obtain a copy of the License at
      7 #
      8 #      http://www.apache.org/licenses/LICENSE-2.0
      9 #
     10 # Unless required by applicable law or agreed to in writing, software
     11 # distributed under the License is distributed on an "AS IS" BASIS,
     12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 # See the License for the specific language governing permissions and
     14 # limitations under the License.
     15 #
     16 
     17 import httplib
     18 import logging
     19 import os
     20 import urlparse
     21 
     22 import arrow
     23 import stripe
     24 import webapp2
     25 from google.appengine.api import users
     26 from webapp2_extras import jinja2 as wa2_jinja2
     27 from webapp2_extras import sessions
     28 
     29 import errors
     30 
     31 
     32 class BaseHandler(webapp2.RequestHandler):
     33     """BaseHandler for all requests."""
     34 
     35     def initialize(self, request, response):
     36         """Initializes this request handler."""
     37         webapp2.RequestHandler.initialize(self, request, response)
     38         self.session_backend = 'datastore'
     39 
     40     def verify_origin(self):
     41         """This function will check the request is comming from the same domain."""
     42         server_host = os.environ.get('ENDPOINTS_SERVICE_NAME')
     43         request_host = self.request.headers.get('Host')
     44         request_referer = self.request.headers.get('Referer')
     45         if request_referer:
     46             request_referer = urlparse.urlsplit(request_referer)[1]
     47         else:
     48             request_referer = request_host
     49         logging.info('server: %s, request: %s', server_host, request_referer)
     50         if server_host and request_referer and server_host != request_referer:
     51             raise errors.Error(httplib.FORBIDDEN)
     52 
     53     def dispatch(self):
     54         """Dispatch the request.
     55 
     56         This will first check if there's a handler_method defined
     57         in the matched route, and if not it'll use the method correspondent to
     58         the request method (get(), post() etc).
     59         """
     60         self.session_store = sessions.get_store(request=self.request)
     61         # Forwards the method for RESTful support.
     62         self.forward_method()
     63         # Security headers.
     64         # https://www.owasp.org/index.php/List_of_useful_HTTP_headers
     65         self.response.headers['x-content-type-options'] = 'nosniff'
     66         self.response.headers['x-frame-options'] = 'SAMEORIGIN'
     67         self.response.headers['x-xss-protection'] = '1; mode=block'
     68         try:
     69             webapp2.RequestHandler.dispatch(self)
     70         finally:
     71             self.session_store.save_sessions(self.response)
     72         # Disabled for now because host is appspot.com in production.
     73         #self.verify_origin()
     74 
     75     @webapp2.cached_property
     76     def session(self):
     77         # Returns a session using the default cookie key.
     78         return self.session_store.get_session()
     79 
     80     def handle_exception(self, exception, debug=False):
     81         """Render the exception as HTML."""
     82         logging.exception(exception)
     83 
     84         # Create response dictionary and status defaults.
     85         tpl = 'error.html'
     86         status = httplib.INTERNAL_SERVER_ERROR
     87         resp_dict = {
     88             'message': 'A server error occurred.',
     89         }
     90         url_parts = self.urlsplit()
     91         redirect_url = '%s?%s' % (url_parts[2], url_parts[4])
     92 
     93         # Use error code if a HTTPException, or generic 500.
     94         if isinstance(exception, webapp2.HTTPException):
     95             status = exception.code
     96             resp_dict['message'] = exception.detail
     97         elif isinstance(exception, errors.FormValidationError):
     98             status = exception.code
     99             resp_dict['message'] = exception.msg
    100             resp_dict['errors'] = exception.errors
    101             self.session['form_errors'] = exception.errors
    102             # Redirect user to current view URL.
    103             return self.redirect(redirect_url)
    104         elif isinstance(exception, stripe.StripeError):
    105             status = exception.http_status
    106             resp_dict['errors'] = exception.json_body['error']['message']
    107             self.session['form_errors'] = [
    108                 exception.json_body['error']['message']
    109             ]
    110             return self.redirect(redirect_url)
    111         elif isinstance(exception, (errors.Error, errors.AclError)):
    112             status = exception.code
    113             resp_dict['message'] = exception.msg
    114 
    115         resp_dict['status'] = status
    116 
    117         # Render output.
    118         self.response.status_int = status
    119         self.response.status_message = httplib.responses[status]
    120         # Render the exception response into the error template.
    121         self.response.write(self.jinja2.render_template(tpl, **resp_dict))
    122 
    123     # @Override
    124     def get(self, *args, **kwargs):
    125         self.abort(httplib.NOT_IMPLEMENTED)
    126 
    127     # @Override
    128     def post(self, *args, **kwargs):
    129         self.abort(httplib.NOT_IMPLEMENTED)
    130 
    131     # @Override
    132     def put(self, *args, **kwargs):
    133         self.abort(httplib.NOT_IMPLEMENTED)
    134 
    135     # @Override
    136     def delete(self, *args, **kwargs):
    137         self.abort(httplib.NOT_IMPLEMENTED)
    138 
    139     # @Override
    140     def head(self, *args, **kwargs):
    141         pass
    142 
    143     def urlsplit(self):
    144         """Return a tuple of the URL."""
    145         return urlparse.urlsplit(self.request.url)
    146 
    147     def path(self):
    148         """Returns the path of the current URL."""
    149         return self.urlsplit()[2]
    150 
    151     def forward_method(self):
    152         """Check for a method override param and change in the request."""
    153         valid = (None, 'get', 'post', 'put', 'delete', 'head', 'options')
    154         method = self.request.POST.get('__method__')
    155         if not method:  # Backbone's _method parameter.
    156             method = self.request.POST.get('_method')
    157         if method not in valid:
    158             logging.debug('Invalid method %s requested!', method)
    159             method = None
    160         logging.debug('Method being changed from %s to %s by request',
    161                       self.request.route.handler_method, method)
    162         self.request.route.handler_method = method
    163 
    164     def render(self, resp, status=httplib.OK):
    165         """Render the response as HTML."""
    166         user = users.get_current_user()
    167         if user:
    168             url = users.create_logout_url(self.request.uri)
    169             url_linktext = "Logout"
    170         else:
    171             url = users.create_login_url(self.request.uri)
    172             url_linktext = "Login"
    173 
    174         resp.update({
    175             # Defaults go here.
    176             'now': arrow.utcnow(),
    177             'dest_url': str(self.request.get('dest_url', '')),
    178             'form_errors': self.session.pop('form_errors', []),
    179             'user': user,
    180             'url': url,
    181             'url_linktext': url_linktext,
    182         })
    183 
    184         if 'preload' not in resp:
    185             resp['preload'] = {}
    186 
    187         self.response.status_int = status
    188         self.response.status_message = httplib.responses[status]
    189         self.response.write(self.jinja2.render_template(self.template, **resp))
    190 
    191     @webapp2.cached_property
    192     def jinja2(self):
    193         """Returns a Jinja2 renderer cached in the app registry."""
    194         jinja_config = {
    195             'template_path':
    196             os.path.join(os.path.dirname(__file__), "../../static"),
    197             'compiled_path':
    198             None,
    199             'force_compiled':
    200             False,
    201             'environment_args': {
    202                 'autoescape': True,
    203                 'extensions': [
    204                     'jinja2.ext.autoescape',
    205                     'jinja2.ext.with_',
    206                 ],
    207             },
    208             'globals':
    209             None,
    210             'filters':
    211             None,
    212         }
    213         return wa2_jinja2.Jinja2(app=self.app, config=jinja_config)
    214