Exemplo n.º 1
0
    def sendUserPasswordRecoveryCode(self, userName: str,
                                     recoveryKey: str) -> None:
        """
			Sends the given recovery code to the user given in userName. This function runs deferred
			so there's no timing sidechannel that leaks if this user exists. Per default, we'll send the
			code by email (assuming we have working email delivery), but this can be overridden to send it
			by SMS or other means. We'll also update the changedate for this user, so no more than one code
			can be send to any given user in four hours.
		"""
        def updateChangeDateTxn(key):
            obj = db.Get(key)
            obj["changedate"] = utcNow()
            db.Put(obj)

        user = db.Query("user").filter("name.idx =", userName).getEntry()
        if user:
            if user.get("changedate") and user["changedate"] > utcNow(
            ) - datetime.timedelta(hours=4):
                # There is a changedate and the user has been modified in the last 4 hours - abort
                return
            # Update the changedate so no more than one email is send per 4 hours
            db.RunInTransaction(updateChangeDateTxn, user.key)
            email.sendEMail(tpl=self.passwordRecoveryMail,
                            skel={"recoveryKey": recoveryKey},
                            dests=[userName])
Exemplo n.º 2
0
def cleanOldEmailsFromLog(*args, **kwargs):
    """
		Start the QueryIter DeleteOldEmailsFromLog to remove old, successfully send emails from the queue
	"""
    qry = db.Query("viur-emails").filter("isSend =", True) \
     .filter("creationDate <", utils.utcNow() - conf["viur.email.logRetention"])
    DeleteEntitiesIter.startIterOnQuery(qry)
Exemplo n.º 3
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
Exemplo n.º 4
0
 def wrapF(self, *args, **kwargs) -> Union[str, bytes]:
     currReq = currentRequest.get()
     if conf["viur.disableCache"] or currReq.disableCache:
         # Caching disabled
         if conf["viur.disableCache"]:
             logging.debug("Caching is disabled by config")
         return f(self, *args, **kwargs)
     # How many arguments are part of the way to the function called (and how many are just *args)
     offset = -len(currReq.args) or len(currReq.pathlist)
     path = "/" + "/".join(currReq.pathlist[:offset])
     if not path in urls:
         # This path (possibly a sub-render) should not be cached
         logging.debug("Not caching for %s" % path)
         return f(self, *args, **kwargs)
     key = keyFromArgs(f, userSensitive, languageSensitive, evaluatedArgs,
                       path, args, kwargs)
     if not key:
         # Something is wrong (possibly the parameter-count)
         # Let's call f, but we knew already that this will clash
         return f(self, *args, **kwargs)
     dbRes = db.Get(db.Key(viurCacheName, key))
     if dbRes is not None:
         if not maxCacheTime \
          or dbRes["creationtime"] > utils.utcNow() - timedelta(seconds=maxCacheTime):
             # We store it unlimited or the cache is fresh enough
             logging.debug("This request was served from cache.")
             currReq.response.headers['Content-Type'] = dbRes[
                 "content-type"]
             return dbRes["data"]
     # If we made it this far, the request wasn't cached or too old; we need to rebuild it
     oldAccessLog = db.startDataAccessLog()
     try:
         res = f(self, *args, **kwargs)
     finally:
         accessedEntries = db.endDataAccessLog(oldAccessLog)
     dbEntity = db.Entity(db.Key(viurCacheName, key))
     dbEntity["data"] = res
     dbEntity["creationtime"] = utils.utcNow()
     dbEntity["path"] = path
     dbEntity["content-type"] = currReq.response.headers['Content-Type']
     dbEntity["accessedEntries"] = list(accessedEntries)
     dbEntity.exclude_from_indexes = ["data", "content-type"
                                      ]  # We can save 2 DB-Writs :)
     db.Put(dbEntity)
     logging.debug("This request was a cache-miss. Cache has been updated.")
     return res
Exemplo n.º 5
0
 def updateTxn(cacheKey):
     key = db.Key(self.rateLimitKind, cacheKey)
     obj = db.Get(key)
     if obj is None:
         obj = db.Entity(key)
         obj["value"] = 0
     obj["value"] += 1
     obj["expires"] = utils.utcNow() + timedelta(minutes=2 *
                                                 self.minutes)
     db.Put(obj)
Exemplo n.º 6
0
    def _getCurrentTimeKey(self):
        """
		:return: the current lockperiod used in second position of the memcache key
		"""
        dateTime = utils.utcNow()
        key = dateTime.strftime("%Y-%m-%d-%%s")
        secsinceMidnight = (dateTime - dateTime.replace(
            hour=0, minute=0, second=0, microsecond=0)).total_seconds()
        currentStep = int(secsinceMidnight / self.secondsPerStep)
        return key % currentStep
Exemplo n.º 7
0
 def secondFactorSucceeded(self, secondFactor, userKey):
     currSess = currentSession.get()
     logging.debug("Got SecondFactorSucceeded call from %s." % secondFactor)
     if currSess["_mayBeUserKey"] != userKey.id_or_name:
         raise errors.Forbidden()
     # Assert that the second factor verification finished in time
     if utils.utcNow(
     ) - currSess["_secondFactorStart"] > self.secondFactorTimeWindow:
         raise errors.RequestTimeout()
     return self.authenticateUser(userKey)
Exemplo n.º 8
0
    def ensureOwnModuleRootNode(self):
        """
		Ensures, that general root-node for the current module exists.
		If no root-node exists yet, it will be created.

		:returns: The entity of the root-node.
		:rtype: :class:`server.db.Entity`
		"""
        key = "rep_module_repo"
        kindName = self.viewSkel("node").kindName
        return db.GetOrInsert(db.Key(kindName, key),
                              creationdate=utils.utcNow(),
                              rootNode=1)
Exemplo n.º 9
0
 def continueAuthenticationFlow(self, caller, userKey):
     currSess = currentSession.get()
     currSess["_mayBeUserKey"] = userKey.id_or_name
     currSess["_secondFactorStart"] = utils.utcNow()
     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...
Exemplo n.º 10
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
Exemplo n.º 11
0
def sendEmailDeferred(emailKey: db.KeyClass):
    """
		Callback from the Taskqueue to send the given Email
		:param emailKey: Database-Key of the email we should send
	"""
    logging.debug("Sending deferred email: %s" % str(emailKey))
    queueEntity = db.Get(emailKey)
    assert queueEntity, "Email queue object went missing!"
    if queueEntity["isSend"]:
        return True
    elif queueEntity["errorCount"] > 3:
        raise ChildProcessError("Error-Count exceeded")
    transportClass = conf[
        "viur.email.transportClass"]  # First, ensure we're able to send email at all
    assert issubclass(
        transportClass,
        EmailTransport), "No or invalid email transportclass specified!"
    try:
        resultData = transportClass.deliverEmail(
            dests=queueEntity["dests"],
            sender=queueEntity["sender"],
            cc=queueEntity["cc"],
            bcc=queueEntity["bcc"],
            subject=queueEntity["subject"],
            body=queueEntity["body"],
            headers=queueEntity["headers"],
            attachments=queueEntity["attachments"])
    except Exception:
        # Increase the errorCount and bail out
        queueEntity["errorCount"] += 1
        db.Put(queueEntity)
        raise
    # If that transportFunction did not raise an error that email has been successfully send
    queueEntity["isSend"] = True
    queueEntity["sendDate"] = utils.utcNow()
    queueEntity["transportFuncResult"] = resultData
    queueEntity.exclude_from_indexes.add("transportFuncResult")
    db.Put(queueEntity)
    try:
        transportClass.transportSuccessfulCallback(queueEntity)
    except Exception as e:
        logging.exception(e)
Exemplo n.º 12
0
    def isQuotaAvailable(self):
        """
		Checks if there's currently quota available for the current user/ip
		:return: True if there's quota available, False otherwise
		:rtype: bool
		"""
        endPoint = self._getEndpointKey()
        currentDateTime = utils.utcNow()
        secSinceMidnight = (currentDateTime - currentDateTime.replace(
            hour=0, minute=0, second=0, microsecond=0)).total_seconds()
        currentStep = int(secSinceMidnight / self.secondsPerStep)
        keyBase = currentDateTime.strftime("%Y-%m-%d-%%s")
        cacheKeys = []
        for x in range(0, self.steps):
            cacheKeys.append(
                db.Key(
                    self.rateLimitKind, "%s-%s-%s" %
                    (self.resource, endPoint, keyBase % (currentStep - x))))
        tmpRes = db.Get(cacheKeys)
        return sum([
            x["value"] for x in tmpRes if currentDateTime < x["expires"]
        ]) <= self.maxRate
Exemplo n.º 13
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)
Exemplo n.º 14
0
 def performMagic(self, valuesCache, name, isAdd):
     if (self.creationMagic and isAdd) or self.updateMagic:
         valuesCache[name] = utcNow()
Exemplo n.º 15
0
def sendEMail(*,
              tpl: str = None,
              stringTemplate: str = None,
              skel: Union[None, Dict, "SkeletonInstance",
                          List["SkeletonInstance"]] = None,
              sender: str = None,
              dests: Union[str, List[str]] = None,
              cc: Union[str, List[str]] = None,
              bcc: Union[str, List[str]] = None,
              headers: Dict[str, str] = None,
              attachments: List[Dict[str, Any]] = None,
              context: Union[db.DATASTORE_BASE_TYPES,
                             List[db.DATASTORE_BASE_TYPES], db.Entity] = None,
              **kwargs) -> Any:
    """
	General purpose function for sending e-mail.
	This function allows for sending e-mails, also with generated content using the Jinja2 template engine.
	Your have to implement a method which should be called to send the prepared email finally. For this you have
	to allocate *viur.email.transportClass* in conf.

	:param tpl: The name of a template from the deploy/emails directory.
	:param stringTemplate: This string is interpreted as the template contents. Alternative to load from template file.
		:param skel: The data made available to the template. In case of a Skeleton or SkelList, its parsed the usual way;\
		Dictionaries are passed unchanged.
	:param sender: The address sending this mail.
	:param dests: A list of addresses to send this mail to. A bare string will be treated as a list with 1 address.
	:param cc: Carbon-copy recipients. A bare string will be treated as a list with 1 address.
	:param bcc: Blind carbon-copy recipients. A bare string will be treated as a list with 1 address.
	:param headers: Specify headers for this email.
	:param attachments: List of files to be sent within the mail as attachments. Each attachment must be a dictionary
		with these keys:
			filename (string): Name of the file that's attached. Always required
			content (bytes): Content of the attachment as bytes. Required for the send in blue API.
			mimetype (string): Mimetype of the file. Suggested parameter for other implementations (not used by SIB)
			gcsfile (string): Link to a GCS-File to include instead of content. Not supported by the current
				SIB implementation
	:param context: Arbitrary data that can be stored along the queue entry to be evaluated in
		transportSuccessfulCallback (useful for tracking delivery / opening events etc).

	.. Warning:  As emails will be queued (and not send directly) you cannot exceed 1MB in total
				(for all text and attachments combined)!
	"""
    # First, ensure we're able to send email at all
    transportClass = conf[
        "viur.email.transportClass"]  # First, ensure we're able to send email at all
    assert issubclass(
        transportClass,
        EmailTransport), "No or invalid email transportclass specified!"
    # Ensure that all recipient parameters (dest, cc, bcc) are a list
    dests = normalizeToList(dests)
    cc = normalizeToList(cc)
    bcc = normalizeToList(bcc)
    assert dests or cc or bcc, "No destination address given"
    attachments = normalizeToList(attachments)
    if not (bool(stringTemplate) ^ bool(tpl)):
        raise ValueError(
            "You have to set the params 'tpl' xor a 'stringTemplate'.")
    if attachments:
        # Ensure each attachment has the filename key and rewrite each dict to db.Entity so we can exclude
        # it from being indexed
        for _ in range(0, len(attachments)):
            attachment = attachments.pop(0)
            assert "filename" in attachment
            entity = db.Entity()
            for k, v in attachment.items():
                entity[k] = v
                entity.exclude_from_indexes.add(k)
            attachments.append(entity)
        assert all(["filename" in x for x in attachments
                    ]), "Attachment is missing the filename key"
    # If conf["viur.email.recipientOverride"] is set we'll redirect any email to these address(es)
    if conf["viur.email.recipientOverride"]:
        logging.warning("Overriding destination %s with %s", dests,
                        conf["viur.email.recipientOverride"])
        oldDests = dests
        newDests = normalizeToList(conf["viur.email.recipientOverride"])
        dests = []
        for newDest in newDests:
            if newDest.startswith("@"):
                for oldDest in oldDests:
                    dests.append(
                        oldDest.replace(".", "_dot_").replace("@", "_at_") +
                        newDest)
            else:
                dests.append(newDest)
        cc = bcc = []
    elif conf["viur.email.recipientOverride"] is False:
        logging.warning(
            "Sending emails disabled by config[viur.email.recipientOverride]")
        return False
    if conf["viur.email.senderOverride"]:
        sender = conf["viur.email.senderOverride"]
    elif sender is None:
        sender = f"viur@{projectID}.appspotmail.com"
    subject, body = conf["viur.emailRenderer"](dests, tpl, stringTemplate,
                                               skel, **kwargs)
    # Push that email to the outgoing queue
    queueEntity = db.Entity(db.Key("viur-emails"))
    queueEntity["isSend"] = False
    queueEntity["errorCount"] = 0
    queueEntity["creationDate"] = utils.utcNow()
    queueEntity["sender"] = sender
    queueEntity["dests"] = dests
    queueEntity["cc"] = cc
    queueEntity["bcc"] = bcc
    queueEntity["subject"] = subject
    queueEntity["body"] = body
    queueEntity["headers"] = headers
    queueEntity["attachments"] = attachments
    queueEntity["context"] = context
    queueEntity.exclude_from_indexes = ["body", "attachments", "context"]
    transportClass.validateQueueEntity(
        queueEntity)  # Will raise an exception if the entity is not valid
    if utils.isLocalDevelopmentServer and not conf[
            "viur.email.sendFromLocalDevelopmentServer"]:
        logging.info("Not sending email from local development server")
        logging.info("Subject: %s", queueEntity["subject"])
        logging.info("Body: %s", queueEntity["body"])
        logging.info("Recipients: %s", queueEntity["dests"])
        return False
    db.Put(queueEntity)
    sendEmailDeferred(queueEntity.key, _queue="viur-emails")
    return True
Exemplo n.º 16
0
 def performMagic(self, valuesCache, name, isAdd):
     if (self.creationMagic and isAdd) or self.updateMagic:
         valuesCache[name] = utcNow().replace(microsecond=0).astimezone(
             self.guessTimeZone())
Exemplo n.º 17
0
    def singleValueFromClient(self, value, skel, name, origData):
        """
			Reads a value from the client.
			If this value is valid for this bone,
			store this value and return None.
			Otherwise our previous value is
			left unchanged and an error-message
			is returned.

			:param name: Our name in the skeleton
			:type name: str
			:param data: *User-supplied* request-data
			:type data: dict
			:returns: str or None
		"""
        rawValue = value
        if str(rawValue).replace("-", "", 1).replace(".", "", 1).isdigit():
            if int(rawValue) < -1 * (2**30) or int(rawValue) > (2**31) - 2:
                value = False  # its invalid
            else:
                value = datetime.fromtimestamp(float(rawValue))
        elif not self.date and self.time:
            try:
                if str(rawValue).count(":") > 1:
                    (hour, minute, second) = [
                        int(x.strip()) for x in str(rawValue).split(":")
                    ]
                    value = time(hour=hour, minute=minute, second=second)
                elif str(rawValue).count(":") > 0:
                    (hour, minute) = [
                        int(x.strip()) for x in str(rawValue).split(":")
                    ]
                    value = time(hour=hour, minute=minute)
                elif str(rawValue).replace("-", "", 1).isdigit():
                    value = time(second=int(rawValue))
                else:
                    value = False  # its invalid
            except:
                value = False
        elif str(rawValue).lower().startswith("now"):
            tmpRes = utcNow().astimezone(self.guessTimeZone())
            if len(str(rawValue)) > 4:
                try:
                    tmpRes += timedelta(seconds=int(str(rawValue)[3:]))
                except:
                    pass
            value = tmpRes
        else:
            try:
                timeZone = self.guessTimeZone()
                if " " in rawValue:  # Date with time
                    try:  # Times with seconds
                        if "-" in rawValue:  # ISO Date
                            value = datetime.strptime(str(rawValue),
                                                      "%Y-%m-%d %H:%M:%S")
                        elif "/" in rawValue:  # Ami Date
                            value = datetime.strptime(str(rawValue),
                                                      "%m/%d/%Y %H:%M:%S")
                        else:  # European Date
                            value = datetime.strptime(str(rawValue),
                                                      "%d.%m.%Y %H:%M:%S")
                    except:
                        if "-" in rawValue:  # ISO Date
                            value = datetime.strptime(str(rawValue),
                                                      "%Y-%m-%d %H:%M")
                        elif "/" in rawValue:  # Ami Date
                            value = datetime.strptime(str(rawValue),
                                                      "%m/%d/%Y %H:%M")
                        else:  # European Date
                            value = datetime.strptime(str(rawValue),
                                                      "%d.%m.%Y %H:%M")
                else:
                    if "-" in rawValue:  # ISO (Date only)
                        value = datetime.strptime(str(rawValue), "%Y-%m-%d")
                    elif "/" in rawValue:  # Ami (Date only)
                        value = datetime.strptime(str(rawValue), "%m/%d/%Y")
                    else:  # European (Date only)
                        value = datetime.strptime(str(rawValue), "%d.%m.%Y")
                value = datetime(value.year,
                                 value.month,
                                 value.day,
                                 value.hour,
                                 value.minute,
                                 value.second,
                                 tzinfo=timeZone)
            except:
                value = False  # its invalid
        if value is False:
            return self.getEmptyValue(), [
                ReadFromClientError(ReadFromClientErrorSeverity.Invalid, name,
                                    "Invalid value entered")
            ]
        err = self.isInvalid(value)
        if err:
            return self.getEmptyValue(), [
                ReadFromClientError(ReadFromClientErrorSeverity.Invalid, name,
                                    err)
            ]
        return value, None
Exemplo n.º 18
0
def cleanOldRateLocks(*args, **kwargs):
    DeleteEntitiesIter.startIterOnQuery(
        db.Query(RateLimit.rateLimitKind).filter("expires <", utils.utcNow()))
Exemplo n.º 19
0
 def login(self, skey="", token="", *args, **kwargs):
     # FIXME: Check if already logged in
     if not conf.get("viur.user.google.clientID"):
         raise errors.PreconditionFailed(
             "Please configure 'viur.user.google.clientID' in your conf!")
     if not skey or not token:
         currentRequest.get().response.headers["Content-Type"] = "text/html"
         if currentRequest.get().response.headers.get(
                 "cross-origin-opener-policy") == "same-origin":
             # We have to allow popups here
             currentRequest.get().response.headers[
                 "cross-origin-opener-policy"] = "same-origin-allow-popups"
         # Fixme: Render with Jinja2?
         tplStr = open(
             os.path.join(utils.coreBasePath,
                          "viur/core/template/vi_user_google_login.html"),
             "r").read()
         tplStr = tplStr.replace("{{ clientID }}",
                                 conf["viur.user.google.clientID"])
         extendCsp({
             "script-src":
             ["sha256-JpzaUIxV/gVOQhKoDLerccwqDDIVsdn1JclA6kRNkLw="],
             "style-src":
             ["sha256-FQpGSicYMVC5jxKGS5sIEzrRjSJmkxKPaetUc7eamqc="]
         })
         return tplStr
     if not securitykey.validate(skey, useSessionKey=True):
         raise errors.PreconditionFailed()
     userInfo = id_token.verify_oauth2_token(
         token, requests.Request(), conf["viur.user.google.clientID"])
     if userInfo['iss'] not in {
             'accounts.google.com', 'https://accounts.google.com'
     }:
         raise ValueError('Wrong issuer.')
     # Token looks valid :)
     uid = userInfo['sub']
     email = userInfo['email']
     addSkel = skeletonByKind(self.userModule.addSkel().kindName
                              )  # Ensure that we have the full skeleton
     userSkel = addSkel().all().filter("uid =", uid).getSkel()
     if not userSkel:
         # We'll try again - checking if there's already an user with that email
         userSkel = addSkel().all().filter("name.idx =",
                                           email.lower()).getSkel()
         if not userSkel:  # Still no luck - it's a completely new user
             if not self.registrationEnabled:
                 if userInfo.get("hd") and userInfo["hd"] in conf[
                         "viur.user.google.gsuiteDomains"]:
                     print("User is from domain - adding account")
                 else:
                     logging.warning("Denying registration of %s", email)
                     raise errors.Forbidden(
                         "Registration for new users is disabled")
             userSkel = addSkel()  # We'll add a new user
         userSkel["uid"] = uid
         userSkel["name"] = email
         isAdd = True
     else:
         isAdd = False
     now = utils.utcNow()
     if isAdd or (now -
                  userSkel["lastlogin"]) > datetime.timedelta(minutes=30):
         # Conserve DB-Writes: Update the user max once in 30 Minutes
         userSkel["lastlogin"] = now
         # if users.is_current_user_admin():
         #	if not userSkel["access"]:
         #		userSkel["access"] = []
         #	if not "root" in userSkel["access"]:
         #		userSkel["access"].append("root")
         #	userSkel["gaeadmin"] = True
         # else:
         #	userSkel["gaeadmin"] = False
         assert userSkel.toDB()
     return self.userModule.continueAuthenticationFlow(
         self, userSkel["key"])
Exemplo n.º 20
0
 def updateChangeDateTxn(key):
     obj = db.Get(key)
     obj["changedate"] = utcNow()
     db.Put(obj)
Exemplo n.º 21
0
 def cron(self, cronName="default", *args, **kwargs):
     global _callableTasks, _periodicTasks, _appengineServiceIPs
     req = currentRequest.get()
     if not req.isDevServer:
         if 'X-Appengine-Cron' not in req.request.headers:
             logging.critical(
                 'Detected an attempted XSRF attack. The header "X-AppEngine-Cron" was not set.'
             )
             raise errors.Forbidden()
         if req.request.environ.get(
                 "HTTP_X_APPENGINE_USER_IP") not in _appengineServiceIPs:
             logging.critical(
                 'Detected an attempted XSRF attack. This request did not originate from Cron.'
             )
             raise errors.Forbidden()
     if cronName not in _periodicTasks:
         logging.warning(
             "Got Cron request '%s' which doesn't have any tasks" %
             cronName)
     for task, interval in _periodicTasks[cronName].items(
     ):  # Call all periodic tasks bound to that queue
         periodicTaskName = task.periodicTaskName.lower()
         if interval:  # Ensure this task doesn't get called to often
             lastCall = db.Get(
                 db.Key("viur-task-interval", periodicTaskName))
             if lastCall and utils.utcNow() - lastCall["date"] < timedelta(
                     minutes=interval):
                 logging.debug(
                     "Skipping task %s - Has already run recently." %
                     periodicTaskName)
                 continue
         res = self.findBoundTask(task)
         try:
             if res:  # Its bound, call it this way :)
                 res[0]()
             else:
                 task(
                 )  # It seems it wasn't bound - call it as a static method
         except Exception as e:
             logging.error("Error calling periodic task %s",
                           periodicTaskName)
             logging.exception(e)
         else:
             logging.debug("Successfully called task %s", periodicTaskName)
         if interval:
             # Update its last-call timestamp
             entry = db.Entity(
                 db.Key("viur-task-interval", periodicTaskName))
             entry["date"] = utils.utcNow()
             db.Put(entry)
     logging.debug("Periodic tasks complete")
     for currentTask in db.Query(
             "viur-queued-tasks").iter():  # Look for queued tasks
         db.Delete(currentTask.key())
         if currentTask["taskid"] in _callableTasks:
             task = _callableTasks[currentTask["taskid"]]()
             tmpDict = {}
             for k in currentTask.keys():
                 if k == "taskid":
                     continue
                 tmpDict[k] = json.loads(currentTask[k])
             try:
                 task.execute(**tmpDict)
             except Exception as e:
                 logging.error("Error executing Task")
                 logging.exception(e)
     logging.debug("Scheduled tasks complete")
Exemplo n.º 22
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))