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