示例#1
0
def validate(key: str, useSessionKey: bool) -> Union[bool, db.Entity]:
    """
		Validates a onetime securitykey

		:type key: str
		:param key: The key to validate
		:type useSessionKey: Bool
		:param useSessionKey: If True, we validate against the session's skey, otherwise we'll lookup an unbound key
		:returns: False if the key was not valid for whatever reasons, the data (given during createSecurityKey) as dictionary or True if the dict is empty.
	"""
    if useSessionKey:
        if key == "staticSessionKey":
            skeyHeaderValue = currentRequest.get().request.headers.get(
                "Sec-X-ViUR-StaticSKey")
            if skeyHeaderValue and currentSession.get(
            ).validateStaticSecurityKey(skeyHeaderValue):
                return True
        elif currentSession.get().validateSecurityKey(key):
            return True
        return False
    if not key:
        return False
    dbKey = db.Key(securityKeyKindName, key)
    dbObj = db.Get(dbKey)
    if dbObj:
        db.Delete(dbKey)
        if dbObj["until"] < datetime.now():  # This key has expired
            return False
        del dbObj["until"]
        if not dbObj:
            return True
        return dbObj
    return False
示例#2
0
def validate(key: str, useSessionKey: bool) -> Union[bool, db.Entity]:
    """
		Validates a security key. If useSessionKey is true, the key is expected to be the sessions current security key
		(or it's static security key). Otherwise it must be a key created with a duration (so it's not session
		dependent)

		:param key: The key to validate
		:param useSessionKey: If True, we validate against the session's skey, otherwise we'll lookup an unbound key
		:returns: False if the key was not valid for whatever reasons, the data (given during createSecurityKey) as
			dictionary or True if the dict is empty (or :param:useSessionKey was true).
	"""
    if useSessionKey:
        if key == "staticSessionKey":
            skeyHeaderValue = currentRequest.get().request.headers.get(
                "Sec-X-ViUR-StaticSKey")
            if skeyHeaderValue and currentSession.get(
            ).validateStaticSecurityKey(skeyHeaderValue):
                return True
        elif currentSession.get().validateSecurityKey(key):
            return True
        return False
    if not key:
        return False
    dbKey = db.Key(securityKeyKindName, key)
    dbObj = db.Get(dbKey)
    if dbObj:
        db.Delete(dbKey)
        until = dbObj["until"]
        if until < utcNow():  # This key has expired
            return False
        del dbObj["until"]
        if not dbObj:
            return True
        return dbObj
    return False
示例#3
0
	def startProcessing(self, userKey):
		user = db.Get(userKey)
		if all([(x in user and user[x]) for x in ["otpid", "otpkey"]]):
			logging.info("OTP wanted for user")
			currentSession.get()["_otp_user"] = {"uid": str(userKey),
											"otpid": user["otpid"],
											"otpkey": user["otpkey"],
											"otptimedrift": user["otptimedrift"],
											"timestamp": time(),
											"failures": 0}
			currentSession.get().markChanged()
			return self.userModule.render.loginSucceeded(msg="X-VIUR-2FACTOR-TimeBasedOTP")

		return None
示例#4
0
    def authenticateUser(self, userKey, **kwargs):
        """
			Performs Log-In for the current session and the given userKey.

			This resets the current session: All fields not explicitly marked as persistent
			by conf["viur.session.persistentFieldsOnLogin"] are gone afterwards.

			:param authProvider: Which authentication-provider issued the authenticateUser request
			:type authProvider: object
			:param userKey: The (DB-)Key of the user we shall authenticate
			:type userKey: db.Key
		"""
        currSess = currentSession.get()
        res = db.Get(userKey)
        assert res, "Unable to authenticate unknown user %s" % userKey
        oldSession = {k: v
                      for k, v in currSess.items()
                      }  # Store all items in the current session
        currSess.reset()
        # Copy the persistent fields over
        for k in conf["viur.session.persistentFieldsOnLogin"]:
            if k in oldSession:
                currSess[k] = oldSession[k]
        del oldSession
        currSess["user"] = res
        currSess.markChanged()
        currentRequest.get().response.headers[
            "Sec-X-ViUR-StaticSKey"] = currSess.staticSecurityKey
        self.onLogin()
        return self.render.loginSucceeded(**kwargs)
示例#5
0
	def secondFactorSucceeded(self, secondFactor, userKey):
		currSess = currentSession.get()
		logging.debug("Got SecondFactorSucceeded call from %s." % secondFactor)
		if str(currSess["_mayBeUserKey"]) != str(userKey):
			raise errors.Forbidden()
		# Assert that the second factor verification finished in time
		if datetime.datetime.now() - currSess["_secondFactorStart"] > self.secondFactorTimeWindow:
			raise errors.RequestTimeout()
		return self.authenticateUser(userKey)
示例#6
0
 def getCurrentUser(self, *args, **kwargs):
     session = currentSession.get()
     if not session:  # May be a deferred task
         return None
     userData = session.get("user")
     if userData:
         skel = self.viewSkel()
         skel.setEntity(userData)
         return skel
     return None
示例#7
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
示例#8
0
def getSession(render):
    """
	Jinja2 global: Allows templates to store variables server-side inside the session.

	Note: This is done in a separated part of the session for security reasons.

	:returns: A dictionary of session variables.
	:rtype: dict
	"""
    currSess = currentSession.get()
    if not currSess.get("JinjaSpace"):
        currSess["JinjaSpace"] = {}
    return currSess.get("JinjaSpace")
示例#9
0
def setSession(render, name, value):
    """
	Jinja2 global: Allows templates to store variables on server-side inside the session.

	Note: This is done in a separated part of the session for security reasons.

	:param name: Name of the key
	:type name: str

	:param value: Value to store with name.
	:type value: any
	"""
    sessionData = getSession(render)
    sessionData[name] = value
    currSess = currentSession.get()
    currSess["JinjaSpace"] = sessionData
    currSess.markChanged()
示例#10
0
	def continueAuthenticationFlow(self, caller, userKey):
		currSess = currentSession.get()
		currSess["_mayBeUserKey"] = str(userKey)
		currSess["_secondFactorStart"] = datetime.datetime.now()
		currSess.markChanged()
		for authProvider, secondFactor in self.validAuthenticationMethods:
			if isinstance(caller, authProvider):
				if secondFactor is None:
					# We allow sign-in without a second factor
					return self.authenticateUser(userKey)
				# This Auth-Request was issued from this authenticationProvider
				secondFactorProvider = self.secondFactorProviderByClass(secondFactor)
				if secondFactorProvider.canHandle(userKey):
					# We choose the first second factor provider which claims it can verify that user
					return secondFactorProvider.startProcessing(userKey)
		# Whoops.. This user logged in successfully - but we have no second factor provider willing to confirm it
		raise errors.NotAcceptable("There are no more authentication methods to try")  # Sorry...
示例#11
0
    def otp(self, otptoken=None, skey=None, *args, **kwargs):
        currSess = currentSession.get()
        token = currSess.get("_otp_user")
        if not token:
            raise errors.Forbidden()
        if otptoken is None:
            self.userModule.render.edit(self.otpSkel())
        if not securitykey.validate(skey, useSessionKey=True):
            raise errors.PreconditionFailed()
        if token["failures"] > 3:
            raise errors.Forbidden(
                "Maximum amount of authentication retries exceeded")
        if len(token["otpkey"]) % 2 == 1:
            raise errors.PreconditionFailed(
                "The otp secret stored for this user is invalid (uneven length)"
            )
        validTokens = self.generateOtps(token["otpkey"], token["otptimedrift"])
        try:
            otptoken = int(otptoken)
        except:
            # We got a non-numeric token - this cant be correct
            self.userModule.render.edit(self.otpSkel(), tpl=self.otpTemplate)

        if otptoken in validTokens:
            userKey = currSess["_otp_user"]["uid"]

            del currSess["_otp_user"]
            currSess.markChanged()

            idx = validTokens.index(int(otptoken))

            if abs(idx - self.windowSize) > 2:
                # The time-drift accumulates to more than 2 minutes, update our
                # clock-drift value accordingly
                self.updateTimeDrift(userKey, idx - self.windowSize)

            return self.userModule.secondFactorSucceeded(self, userKey)
        else:
            token["failures"] += 1
            currSess["_otp_user"] = token
            currSess.markChanged()
            return self.userModule.render.edit(self.otpSkel(),
                                               loginFailed=True,
                                               tpl=self.otpTemplate)
示例#12
0
def create(duration: Union[None, int] = None, **kwargs) -> str:
    """
		Creates a new onetime Securitykey or returns the current sessions csrf-token.
		The custom data (given as keyword arguments) that can be stored with the key if :param:duration is set must
		be serializable by the datastore.

		:param duration: Make this key valid for a fixed timeframe (and independent of the current session)
		:returns: The new onetime key
	"""
    if not duration:
        return currentSession.get().getSecurityKey()
    key = generateRandomString()
    duration = int(duration)
    dbObj = db.Entity(db.Key(securityKeyKindName, key))
    for k, v in kwargs.items():
        dbObj[k] = v
    dbObj["until"] = utcNow() + timedelta(seconds=duration)
    db.Put(dbObj)
    return key
示例#13
0
	def logout(self, skey="", *args, **kwargs):
		"""
			Implements the logout action. It also terminates the current session (all keys not listed
			in viur.session.persistentFieldsOnLogout will be lost).
		"""
		currSess = currentSession.get()
		user = currSess.get("user")
		if not user:
			raise errors.Unauthorized()
		if not securitykey.validate(skey, useSessionKey=True):
			raise errors.PreconditionFailed()
		self.onLogout(user)
		oldSession = {k: v for k, v in currSess.items()}  # Store all items in the current session
		currSess.reset()
		# Copy the persistent fields over
		for k in conf["viur.session.persistentFieldsOnLogout"]:
			if k in oldSession:
				currSess[k] = oldSession[k]
		del oldSession
		return self.render.logoutSuccess()
示例#14
0
def create(duration: Union[None, int] = None, **kwargs):
    """
		Creates a new onetime Securitykey for the current session
		If duration is not set, this key is valid only for the current session.
		Otherwise, the key and its data is serialized and saved inside the datastore
		for up to duration-seconds

		:param duration: Make this key valid for a fixed timeframe (and independend of the current session)
		:type duration: int or None
		:returns: The new onetime key
	"""
    if not duration:
        return currentSession.get().getSecurityKey()
    key = generateRandomString()
    duration = int(duration)
    dbObj = db.Entity(securityKeyKindName, name=key)
    for k, v in kwargs.items():
        dbObj[k] = v
    dbObj["until"] = datetime.now() + timedelta(seconds=duration)
    db.Put(dbObj)
    return key
示例#15
0
 def edit(self, *args, **kwargs):
     currSess = currentSession.get()
     if len(args) == 0 and not "key" in kwargs and currSess.get("user"):
         kwargs["key"] = currSess.get("user")["key"]
     return super(User, self).edit(*args, **kwargs)
示例#16
0
    def pwrecover(self, *args, **kwargs):
        """
			This implements the password recovery process which let them set a new password for their account
			after validating a code send to them by email. The process is as following:

			- The user enters his email adress
			- We'll generate a random code, store it in his session and call sendUserPasswordRecoveryCode
			- sendUserPasswordRecoveryCode will run in the background, check if we have a user with that name
			  and send the code. It runs as a deferredTask so we don't leak the information if a user account exists.
			- If the user received his code, he can paste the code and set a new password for his account.

			To prevent automated attacks, the fist step is guarded by a captcha and we limited calls to this function
			to 10 actions per 15 minutes. (One complete recovery process consists of two calls).
		"""
        if not self.passwordRecoveryRateLimit.isQuotaAvailable():
            raise errors.Forbidden()  # Quota exhausted, bail out
        session = currentSession.get()
        request = currentRequest.get()
        recoverStep = session.get("user.auth_userpassword.pwrecover")
        if not recoverStep:
            # This is the first step, where we ask for the username of the account we'll going to reset the password on
            skel = self.lostPasswordStep1Skel()
            if not request.isPostRequest or not skel.fromClient(kwargs):
                return self.userModule.render.edit(
                    skel, self.passwordRecoveryStep1Template)
            if not securitykey.validate(kwargs.get("skey"),
                                        useSessionKey=True):
                raise errors.PreconditionFailed()
            self.passwordRecoveryRateLimit.decrementQuota()
            recoveryKey = utils.generateRandomString(
                13)  # This is the key the user will have to Copy&Paste
            self.sendUserPasswordRecoveryCode(
                skel["name"].lower(),
                recoveryKey)  # Send the code in the background
            session["user.auth_userpassword.pwrecover"] = {
                "name": skel["name"].lower(),
                "recoveryKey": recoveryKey,
                "creationdate": utcNow(),
                "errorCount": 0
            }
            del recoveryKey
            return self.pwrecover(
            )  # Fall through to the second step as that key in the session is now set
        else:
            if request.isPostRequest and kwargs.get("abort") == "1" \
             and securitykey.validate(kwargs.get("skey"), useSessionKey=True):
                # Allow a user to abort the process if a wrong email has been used
                session["user.auth_userpassword.pwrecover"] = None
                return self.pwrecover()
            # We're in the second step - the code has been send and is waiting for confirmation from the user
            if utcNow() - session["user.auth_userpassword.pwrecover"][
                    "creationdate"] > datetime.timedelta(minutes=15):
                # This recovery-process is expired; reset the session and start over
                session["user.auth_userpassword.pwrecover"] = None
                return self.userModule.render.view(
                    skel=None,
                    tpl=self.passwordRecoveryFailedTemplate,
                    reason=self.passwordRecoveryKeyExpired)
            skel = self.lostPasswordStep2Skel()
            if not skel.fromClient(kwargs) or not request.isPostRequest:
                return self.userModule.render.edit(
                    skel, self.passwordRecoveryStep2Template)
            if not securitykey.validate(kwargs.get("skey"),
                                        useSessionKey=True):
                raise errors.PreconditionFailed()
            self.passwordRecoveryRateLimit.decrementQuota()
            if not hmac.compare_digest(
                    session["user.auth_userpassword.pwrecover"]["recoveryKey"],
                    skel["recoveryKey"]):
                # The key was invalid, increase error-count or abort this recovery process altogether
                session["user.auth_userpassword.pwrecover"]["errorCount"] += 1
                if session["user.auth_userpassword.pwrecover"][
                        "errorCount"] > 3:
                    session["user.auth_userpassword.pwrecover"] = None
                    return self.userModule.render.view(
                        skel=None,
                        tpl=self.passwordRecoveryFailedTemplate,
                        reason=self.passwordRecoveryKeyInvalid)
                return self.userModule.render.edit(
                    skel,
                    self.passwordRecoveryStep2Template)  # Let's try again
            # If we made it here, the key was correct, so we'd hopefully have a valid user for this
            uSkel = userSkel().all().filter(
                "name.idx =",
                session["user.auth_userpassword.pwrecover"]["name"]).getSkel()
            if not uSkel:  # This *should* never happen - if we don't have a matching account we'll not send the key.
                session["user.auth_userpassword.pwrecover"] = None
                return self.userModule.render.view(
                    skel=None,
                    tpl=self.passwordRecoveryFailedTemplate,
                    reason=self.passwordRecoveryUserNotFound)
            if uSkel["status"] != 10:  # The account is locked or not yet validated. Abort the process
                session["user.auth_userpassword.pwrecover"] = None
                return self.userModule.render.view(
                    skel=None,
                    tpl=self.passwordRecoveryFailedTemplate,
                    reason=self.passwordRecoveryAccountLocked)
            # Update the password, save the user, reset his session and show the success-template
            uSkel["password"] = skel["password"]
            uSkel.toDB()
            session["user.auth_userpassword.pwrecover"] = None
            return self.userModule.render.view(
                None, self.passwordRecoverySuccessTemplate)
示例#17
0
    def mkDefered(func, self=__undefinedFlag_, *args, **kwargs):
        if not queueRegion:
            # Run tasks inline
            logging.info("Running inline: %s" % func)
            if self is __undefinedFlag_:
                task = lambda: func(*args, **kwargs)
            else:
                task = lambda: func(self, *args, **kwargs)
            req = currentRequest.get()
            if req:
                req.pendingTasks.append(
                    task
                )  # < This property will be only exist on development server!
            else:
                # Warmup request or something - we have to call it now as we can't defer it :/
                task()

            return  # Ensure no result gets passed back

        from viur.core.utils import getCurrentUser
        try:
            req = currentRequest.get()
        except:  # This will fail for warmup requests
            req = None
        if req is not None and req.request.headers.get("X-Appengine-Taskretrycount") \
          and "DEFERED_TASK_CALLED" not in dir(req):
            # This is the deferred call
            req.DEFERED_TASK_CALLED = True  # Defer recursive calls to an deferred function again.
            if self is __undefinedFlag_:
                return func(*args, **kwargs)
            else:
                return func(self, *args, **kwargs)
        else:
            try:
                if self.__class__.__name__ == "index":
                    funcPath = func.__name__
                else:
                    funcPath = "%s/%s" % (self.modulePath, func.__name__)
                command = "rel"
            except:
                funcPath = "%s.%s" % (func.__name__, func.__module__)
                if self != __undefinedFlag_:
                    args = (
                        self,
                    ) + args  # Reappend self to args, as this function is (hopefully) unbound
                command = "unb"
            taskargs = dict((x, kwargs.pop(("_%s" % x), None))
                            for x in ("countdown", "eta", "name", "target",
                                      "retry_options"))
            taskargs["url"] = "/_tasks/deferred"
            transactional = kwargs.pop("_transactional", False)
            taskargs["headers"] = {"Content-Type": "application/octet-stream"}
            queue = kwargs.pop("_queue", "default")
            # Try to preserve the important data from the current environment
            try:  # We might get called inside a warmup request without session
                usr = currentSession.get().get("user")
                if "password" in usr:
                    del usr["password"]
            except:
                usr = None
            env = {"user": usr}
            logging.error(env)
            try:
                env["lang"] = currentRequest.get().language
            except AttributeError:  # This isn't originating from a normal request
                pass
            if db.IsInTransaction():
                # We have to ensure transaction guarantees for that task also
                env["transactionMarker"] = db.acquireTransactionSuccessMarker()
                # We move that task at least 90 seconds into the future so the transaction has time to settle
                taskargs["countdown"] = max(
                    90, (taskargs.get("countdown")
                         or 0))  # Countdown can be set to None
            if conf["viur.tasks.customEnvironmentHandler"]:
                # Check if this project relies on additional environmental variables and serialize them too
                assert isinstance(conf["viur.tasks.customEnvironmentHandler"], tuple) \
                    and len(conf["viur.tasks.customEnvironmentHandler"]) == 2 \
                    and callable(conf["viur.tasks.customEnvironmentHandler"][0]), \
                 "Your customEnvironmentHandler must be a tuple of two callable if set!"
                env["custom"] = conf["viur.tasks.customEnvironmentHandler"][0](
                )
            pickled = json.dumps((command, (funcPath, args, kwargs, env)),
                                 cls=JsonKeyEncoder).encode("UTF-8")

            project = utils.projectID
            location = queueRegion
            parent = taskClient.queue_path(project, location, queue)
            task = {
                'app_engine_http_request': {  # Specify the type of request.
                    'http_method': tasks_v2.HttpMethod.POST,
                    'relative_uri': '/_tasks/deferred'
                }
            }
            if taskargs.get("countdown"):
                # We must send a Timestamp Protobuf instead of a date-string
                timestamp = timestamp_pb2.Timestamp()
                timestamp.FromDatetime(utils.utcNow() + timedelta(
                    seconds=taskargs["countdown"]))
                task['schedule_time'] = timestamp
            task['app_engine_http_request']['body'] = pickled

            # Use the client to build and send the task.
            response = taskClient.create_task(parent=parent, task=task)

            print('Created task {}'.format(response.name))
示例#18
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()
示例#19
0
 def saveSession(self):
     currentSession.get().save(self)
示例#20
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:
            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:
                utils.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"]:
                currentRequest.get().language = 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)
            try:
                _deferedTasks[funcPath](*args, **kwargs)
            except PermanentTaskFailure:
                pass
            except Exception as e:
                logging.exception(e)
                raise errors.RequestTimeout()  # Task-API should retry