def processRequest(self): """ Bring up the enviroment for this request, start processing and handle errors """ # Check if it's a HTTP-Method we support reqestMethod = self.request.method.lower() if reqestMethod not in ["get", "post", "head"]: logging.error("Not supported") return self.isPostRequest = reqestMethod == "post" # Configure some basic parameters for this request self.internalRequest = False self.isDevServer = os.environ[ 'GAE_ENV'] == "localdev" # Were running on development Server self.isSSLConnection = self.request.host_url.lower().startswith( "https://") # We have an encrypted channel currentLanguage.set(conf["viur.defaultLanguage"]) self.disableCache = False # Shall this request bypass the caches? self.args = [] self.kwargs = {} # Check if we should process or abort the request for validator, reqValidatorResult in [(x, x.validate(self)) for x in self.requestValidators]: if reqValidatorResult is not None: logging.warning("Request rejected by validator %s" % validator.name) statusCode, statusStr, statusDescr = reqValidatorResult self.response.status = '%d %s' % (statusCode, statusStr) self.response.write(statusDescr) return path = self.request.path if self.isDevServer: # We'll have to emulate the task-queue locally as far as possible until supported by dev_appserver again self.pendingTasks = [] # Add CSP headers early (if any) if conf["viur.security.contentSecurityPolicy"] and conf[ "viur.security.contentSecurityPolicy"]["_headerCache"]: for k, v in conf["viur.security.contentSecurityPolicy"][ "_headerCache"].items(): self.response.headers[k] = v if self.isSSLConnection: # Check for HTST and PKP headers only if we have a secure channel. if conf["viur.security.strictTransportSecurity"]: self.response.headers["Strict-Transport-Security"] = conf[ "viur.security.strictTransportSecurity"] # Check for X-Security-Headers we shall emit if conf["viur.security.xContentTypeOptions"]: self.response.headers["X-Content-Type-Options"] = "nosniff" if conf["viur.security.xXssProtection"] is not None: if conf["viur.security.xXssProtection"]: self.response.headers["X-XSS-Protection"] = "1; mode=block" elif conf["viur.security.xXssProtection"] is False: self.response.headers["X-XSS-Protection"] = "0" if conf["viur.security.xFrameOptions"] is not None and isinstance( conf["viur.security.xFrameOptions"], tuple): mode, uri = conf["viur.security.xFrameOptions"] if mode in ["deny", "sameorigin"]: self.response.headers["X-Frame-Options"] = mode elif mode == "allow-from": self.response.headers[ "X-Frame-Options"] = "allow-from %s" % uri if conf["viur.security.xPermittedCrossDomainPolicies"] is not None: self.response.headers["X-Permitted-Cross-Domain-Policies"] = conf[ "viur.security.xPermittedCrossDomainPolicies"] if conf["viur.security.referrerPolicy"]: self.response.headers["Referrer-Policy"] = conf[ "viur.security.referrerPolicy"] if conf["viur.security.permissionsPolicy"].get("_headerCache"): self.response.headers["Permissions-Policy"] = conf[ "viur.security.permissionsPolicy"]["_headerCache"] if conf["viur.security.enableCOEP"]: self.response.headers[ "Cross-Origin-Embedder-Policy"] = "require-corp" if conf["viur.security.enableCOOP"]: self.response.headers["Cross-Origin-Opener-Policy"] = conf[ "viur.security.enableCOOP"] if conf["viur.security.enableCORP"]: self.response.headers["Cross-Origin-Resource-Policy"] = conf[ "viur.security.enableCORP"] # Ensure that TLS is used if required if conf["viur.forceSSL"] and not self.isSSLConnection and not self.isDevServer: isWhitelisted = False reqPath = self.request.path for testUrl in conf["viur.noSSLCheckUrls"]: if testUrl.endswith("*"): if reqPath.startswith(testUrl[:-1]): isWhitelisted = True break else: if testUrl == reqPath: isWhitelisted = True break if not isWhitelisted: # Some URLs need to be whitelisted (as f.e. the Tasks-Queue doesn't call using https) # Redirect the user to the startpage (using ssl this time) host = self.request.host_url.lower() host = host[host.find("://") + 3:].strip( " /") # strip http(s):// self.response.status = "302 Found" self.response.headers['Location'] = "https://%s/" % host return if path.startswith("/_ah/warmup"): self.response.write("okay") return try: currentSession.get().load(self) # self.request.cookies ) path = self.selectLanguage(path)[1:] if conf["viur.requestPreprocessor"]: path = conf["viur.requestPreprocessor"](path) self.findAndCall(path) except errors.Redirect as e: if conf["viur.debug.traceExceptions"]: raise self.response.status = '%d %s' % (e.status, e.name) url = e.url if url.startswith(('.', '/')): url = str(urljoin(self.request.url, url)) self.response.headers['Location'] = url except errors.HTTPException as e: if conf["viur.debug.traceExceptions"]: raise self.response.body = b"" self.response.status = '%d %s' % (e.status, e.name) # Set machine-readable x-viur-error response header in case there is an exception description. if e.descr: self.response.headers["x-viur-error"] = e.descr.replace( "\n", "") res = None if conf["viur.errorHandler"]: try: res = conf["viur.errorHandler"](e) except Exception as newE: logging.error("viur.errorHandler failed!") logging.exception(newE) res = None if not res: tpl = Template( open( os.path.join(utils.coreBasePath, conf["viur.errorTemplate"]), "r").read()) res = tpl.safe_substitute({ "error_code": e.status, "error_name": e.name, "error_descr": e.descr }) extendCsp({ "style-src": ['sha256-Lwf7c88gJwuw6L6p6ILPSs/+Ui7zCk8VaIvp8wLhQ4A='] }) self.response.write(res.encode("UTF-8")) except Exception as e: # Something got really wrong logging.error("Viur caught an unhandled exception!") logging.exception(e) self.response.body = b"" self.response.status = 500 res = None if conf["viur.errorHandler"]: try: res = conf["viur.errorHandler"](e) except Exception as newE: logging.error("viur.errorHandler failed!") logging.exception(newE) res = None if not res: tpl = Template( open( os.path.join(utils.coreBasePath, conf["viur.errorTemplate"]), "r").read()) descr = "The server encountered an unexpected error and is unable to process your request." if self.isDevServer: # Were running on development Server strIO = StringIO() traceback.print_exc(file=strIO) descr = strIO.getvalue() descr = descr.replace("<", "<").replace( ">", ">").replace(" ", " ").replace("\n", "<br />") res = tpl.safe_substitute({ "error_code": "500", "error_name": "Internal Server Error", "error_descr": descr }) extendCsp({ "style-src": ['sha256-Lwf7c88gJwuw6L6p6ILPSs/+Ui7zCk8VaIvp8wLhQ4A='] }) self.response.write(res.encode("UTF-8")) finally: self.saveSession() SEVERITY = "DEBUG" if self.maxLogLevel >= 50: SEVERITY = "CRITICAL" elif self.maxLogLevel >= 40: SEVERITY = "ERROR" elif self.maxLogLevel >= 30: SEVERITY = "WARNING" elif self.maxLogLevel >= 20: SEVERITY = "INFO" TRACE = "projects/{}/traces/{}".format(loggingClient.project, self._traceID) REQUEST = { 'requestMethod': self.request.method, 'requestUrl': self.request.url, 'status': self.response.status_code, 'userAgent': self.request.headers.get('USER-AGENT'), 'responseSize': self.response.content_length, 'latency': "%0.3fs" % (time() - self.startTime), 'remoteIp': self.request.environ.get("HTTP_X_APPENGINE_USER_IP") } requestLogger.log_text("", client=loggingClient, severity=SEVERITY, http_request=REQUEST, trace=TRACE, resource=requestLoggingRessource) if self.isDevServer: while self.pendingTasks: task = self.pendingTasks.pop() logging.info("Running task directly after request: %s" % str(task)) task()
def setLanguage(lang, skey): if not securitykey.validate(skey): return if lang in conf["viur.availableLanguages"]: currentLanguage.set(lang)
def selectLanguage(self, path: str): """ Tries to select the best language for the current request. Depending on the value of conf["viur.languageMethod"], we'll either try to load it from the session, determine it by the domain or extract it from the URL. """ sessionReference = currentSession.get() if not conf["viur.availableLanguages"]: # This project doesn't use the multi-language feature, nothing to do here return path if conf["viur.languageMethod"] == "session": # We store the language inside the session, try to load it from there if "lang" not in sessionReference: if "X-Appengine-Country" in self.request.headers: lng = self.request.headers["X-Appengine-Country"].lower() if lng in conf["viur.availableLanguages"] + list( conf["viur.languageAliasMap"].keys()): sessionReference["lang"] = lng currentLanguage.set(lng) else: sessionReference["lang"] = conf["viur.defaultLanguage"] else: currentLanguage.set(sessionReference["lang"]) elif conf["viur.languageMethod"] == "domain": host = self.request.host_url.lower() host = host[host.find("://") + 3:].strip(" /") # strip http(s):// if host.startswith("www."): host = host[4:] if host in conf["viur.domainLanguageMapping"]: currentLanguage.set(conf["viur.domainLanguageMapping"][host]) else: # We have no language configured for this domain, try to read it from session if "lang" in sessionReference: currentLanguage.set(sessionReference["lang"]) elif conf["viur.languageMethod"] == "url": tmppath = urlparse(path).path tmppath = [ unquote(x) for x in tmppath.lower().strip("/").split("/") ] if len(tmppath) > 0 and tmppath[ 0] in conf["viur.availableLanguages"] + list( conf["viur.languageAliasMap"].keys()): currentLanguage.set(tmppath[0]) return (path[len(tmppath[0]) + 1:] ) # Return the path stripped by its language segment else: # This URL doesnt contain an language prefix, try to read it from session if "lang" in sessionReference: currentLanguage.set(sessionReference["lang"]) elif "X-Appengine-Country" in self.request.headers.keys(): lng = self.request.headers["X-Appengine-Country"].lower() if lng in conf["viur.availableLanguages"] or lng in conf[ "viur.languageAliasMap"]: currentLanguage.set(lng) return path
def deferred(self, *args, **kwargs): """ This catches one deferred call and routes it to its destination """ global _deferedTasks, _appengineServiceIPs req = currentRequest.get().request if 'X-AppEngine-TaskName' not in req.headers: logging.critical( 'Detected an attempted XSRF attack. The header "X-AppEngine-Taskname" was not set.' ) raise errors.Forbidden() if req.environ.get("HTTP_X_APPENGINE_USER_IP") not in _appengineServiceIPs \ and (not utils.isLocalDevelopmentServer or os.getenv("TASKS_EMULATOR") is None): logging.critical( 'Detected an attempted XSRF attack. This request did not originate from Task Queue.' ) raise errors.Forbidden() # Check if the retry count exceeds our warning threshold retryCount = req.headers.get("X-Appengine-Taskretrycount", None) if retryCount: if int(retryCount) == self.retryCountWarningThreshold: from viur.core import email email.sendEMailToAdmins( "Deferred task retry count exceeded warning threshold", "Task %s will now be retried for the %sth time." % (req.headers.get("X-Appengine-Taskname", ""), retryCount)) cmd, data = json.loads(req.body, object_hook=jsonDecodeObjectHook) funcPath, args, kwargs, env = data if env: if "user" in env and env["user"]: currentSession.get()["user"] = env["user"] if "lang" in env and env["lang"]: currentLanguage.set(env["lang"]) if "transactionMarker" in env: marker = db.Get( db.Key("viur-transactionmarker", env["transactionMarker"])) if not marker: logging.info( "Dropping task, transaction %s did not apply" % env["transactionMarker"]) return else: logging.info("Executing task, transaction %s did succeed" % env["transactionMarker"]) if "custom" in env and conf["viur.tasks.customEnvironmentHandler"]: # Check if we need to restore additional environmental data assert isinstance(conf["viur.tasks.customEnvironmentHandler"], tuple) \ and len(conf["viur.tasks.customEnvironmentHandler"]) == 2 \ and callable(conf["viur.tasks.customEnvironmentHandler"][1]), \ "Your customEnvironmentHandler must be a tuple of two callable if set!" conf["viur.tasks.customEnvironmentHandler"][1](env["custom"]) if cmd == "rel": caller = conf["viur.mainApp"] pathlist = [x for x in funcPath.split("/") if x] for currpath in pathlist: if currpath not in dir(caller): logging.error( "ViUR missed a deferred task! Could not resolve the path %s. " "Failed segment was %s", funcPath, currpath) return caller = getattr(caller, currpath) try: caller(*args, **kwargs) except PermanentTaskFailure: pass except Exception as e: logging.exception(e) raise errors.RequestTimeout() # Task-API should retry elif cmd == "unb": if not funcPath in _deferedTasks: logging.error("ViUR missed a deferred task! %s(%s,%s)", funcPath, args, kwargs) # We call the deferred function *directly* (without walking through the mkDeferred lambda), so ensure # that any hit to another deferred function will defer again req.DEFERED_TASK_CALLED = True try: _deferedTasks[funcPath](*args, **kwargs) except PermanentTaskFailure: pass except Exception as e: logging.exception(e) raise errors.RequestTimeout() # Task-API should retry