Example #1
0
    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("<", "&lt;").replace(
                        ">", "&gt;").replace(" ",
                                             "&nbsp;").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()
Example #2
0
def setLanguage(lang, skey):
    if not securitykey.validate(skey):
        return
    if lang in conf["viur.availableLanguages"]:
        currentLanguage.set(lang)
Example #3
0
    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
Example #4
0
    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