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