def getUploadURL(self, fileName, mimeType, size=None, skey=None, *args, **kwargs): node = kwargs.get("node") authData = kwargs.get("authData") authSig = kwargs.get("authSig") # Validate the the contentType from the client seems legit mimeType = mimeType.lower() assert len(mimeType.split("/")) == 2, "Invalid Mime-Type" assert all([x in string.ascii_letters + string.digits + "/-.+" for x in mimeType]), "Invalid Mime-Type" if authData and authSig: # First, validate the signature, otherwise we don't need to proceed any further if not utils.hmacVerify(authData.encode("ASCII"), authSig): raise errors.Forbidden() authData = json.loads(base64.b64decode(authData.encode("ASCII")).decode("UTF-8")) if datetime.strptime(authData["validUntil"], "%Y%m%d%H%M") < datetime.now(): raise errors.Gone() if authData["validMimeTypes"]: for validMimeType in authData["validMimeTypes"]: if validMimeType == mimeType or ( validMimeType.endswith("*") and mimeType.startswith(validMimeType[:-1])): break else: raise errors.NotAcceptable() node = authData["node"] maxSize = authData["maxSize"] else: if node: rootNode = self.getRootNode(node) if not self.canAdd("leaf", rootNode): raise errors.Forbidden() else: if not self.canAdd("leaf", None): raise errors.Forbidden() maxSize = None # The user has some file/add permissions, don't restrict fileSize if maxSize: try: size = int(size) assert size <= maxSize except: # We have a size-limit set - but no size supplied raise errors.PreconditionFailed() else: size = None if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() targetKey, uploadUrl = self.initializeUpload(fileName, mimeType.lower(), node, size) resDict = { "uploadUrl": uploadUrl, "uploadKey": targetKey, } return self.render.view(resDict)
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" # Fixme: Render with Jinja2? tplStr = open("viur/core/template/vi_user_google_login.html", "r").read() tplStr = tplStr.replace("{{ clientID }}", conf["viur.user.google.clientID"]) 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 = datetime.datetime.now() 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"])
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). """ user = session.current.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 session.current.items() } # Store all items in the current session session.current.reset() # Copy the persistent fields over for k in conf["viur.session.persistentFieldsOnLogout"]: if k in oldSession: session.current[k] = oldSession[k] del oldSession return self.render.logoutSuccess()
def delete(self, key, skey, *args, **kwargs): """ Delete an entry. The function runs several access control checks on the data before it is deleted. .. seealso:: :func:`canDelete`, :func:`editSkel`, :func:`onDeleted` :returns: The rendered, deleted object of the entry. :raises: :exc:`viur.core.errors.NotFound`, when no entry with the given *key* was found. :raises: :exc:`viur.core.errors.Unauthorized`, if the current user does not have the required permissions. :raises: :exc:`viur.core.errors.PreconditionFailed`, if the *skey* could not be verified. """ skel = self.editSkel() if not skel.fromDB(key): raise errors.NotFound() if not self.canDelete(skel): raise errors.Unauthorized() if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() self.onDelete(skel) skel.delete() self.onDeleted(skel) return self.render.deleteSuccess(skel)
def setIndex(self, item, index, skey, *args, **kwargs): """ Changes the order of the elements in the current level by changing the index of *item*. .. seealso:: :func:`canSetIndex` :param item: URL-safe key of the item which index should be changed. :type item: str :param index: New index for this item. This value must be cast-able to float. :type index: str :returns: A rendered success result generated by the default renderer. :raises: :exc:`server.errors.NotFound`, when no entry with the given *key* was found. :raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions. :raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified. """ if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() if not self.canSetIndex(item, index): raise errors.Unauthorized() fromItem = db.Get(item) fromItem["sortindex"] = float(index) db.Put(fromItem) skel = self.editSkel() assert skel.fromDB(item) self.onItemSetIndex(skel) self.onItemChanged(skel) return self.render.setIndexSuccess(obj=fromItem)
def index(self, *args, **kwargs): if not self.canUse(): raise errors.Forbidden() # Unauthorized skel = self.mailSkel() if len(kwargs) == 0: return self.render.add(skel=skel, failed=False) if not skel.fromClient(kwargs) or not "skey" in kwargs: return self.render.add(skel=skel, failed=True) if not securitykey.validate(kwargs["skey"], useSessionKey=True): raise errors.PreconditionFailed() # Allow bones to perform outstanding "magic" operations before sending the mail for key, _bone in skel.items(): if isinstance(_bone, baseBone): _bone.performMagic(skel.valuesCache, key, isAdd=True) # Get recipients rcpts = self.getRcpts(skel) # Get additional options for sendEMail opts = self.getOptions(skel) if not isinstance(opts, dict): opts = {} # Send the email! utils.sendEMail(rcpts, self.mailTemplate, skel, **opts) self.onAdded(skel) return self.render.addSuccess(skel)
def add(self, skelType, node, *args, **kwargs): """ Add a new entry with the given parent *node*, and render the entry, eventually with error notes on incorrect data. Data is taken by any other arguments in *kwargs*. The function performs several access control checks on the requested entity before it is added. .. seealso:: :func:`onItemAdded`, :func:`canAdd` :param skelType: Defines the type of the new entry and may either be "node" or "leaf". :type skelType: str :param node: URL-safe key of the parent. :type node: str :returns: The rendered, added object of the entry, eventually with error hints. :raises: :exc:`server.errors.NotAcceptable`, when no valid *skelType* was provided. :raises: :exc:`server.errors.NotFound`, when no valid *node* was found. :raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions. :raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified. """ if "skey" in kwargs: skey = kwargs["skey"] else: skey = "" if skelType == "node": skel = self.viewSkel(TreeType.Node) elif skelType == "leaf" and self.hasDistinctLeafs: skel = self.viewSkel(TreeType.Leaf) else: raise errors.NotAcceptable() # FIXME: IsValidParent? #parentNodeSkel = self.editNodeSkel() #if not parentNodeSkel.fromDB(node): # raise errors.NotFound() if not self.canAdd(skelType, node): raise errors.Unauthorized() if (len(kwargs) == 0 # no data supplied or skey == "" # no security key # or not request.current.get().isPostRequest fixme: POST-method check missing? # failure if not using POST-method or not skel.fromClient(kwargs) # failure on reading into the bones or ("bounce" in kwargs and kwargs["bounce"] == "1") # review before adding ): return self.render.add(skel) if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() skel["parentdir"] = str(node) skel["parentrepo"] = parentNodeSkel["parentrepo"] or str(node) skel.toDB() self.onItemAdded(skel) return self.render.addItemSuccess(skel)
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)
def edit(self, *args, **kwargs): """ Modify an existing entry, and render the entry, eventually with error notes on incorrect data. Data is taken by any other arguments in *kwargs*. The entry is fetched by its entity key, which either is provided via *kwargs["key"]*, or as the first parameter in *args*. The function performs several access control checks on the requested entity before it is modified. .. seealso:: :func:`editSkel`, :func:`onItemEdited`, :func:`canEdit` :returns: The rendered, edited object of the entry, eventually with error hints. :raises: :exc:`server.errors.NotAcceptable`, when no *key* is provided. :raises: :exc:`server.errors.NotFound`, when no entry with the given *key* was found. :raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions. :raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified. """ if "skey" in kwargs: skey = kwargs["skey"] else: skey = "" if len(args) == 1: key = args[0] elif "key" in kwargs: key = kwargs["key"] else: raise errors.NotAcceptable() skel = self.editSkel() if not skel.fromDB(key): raise errors.NotAcceptable() if not self.canEdit(skel): raise errors.Unauthorized() if (len(kwargs) == 0 # no data supplied or skey == "" # no security key or not request.current.get(). isPostRequest # failure if not using POST-method or not skel.fromClient( kwargs) # failure on reading into the bones or ("bounce" in kwargs and kwargs["bounce"] == "1" ) # review before changing ): return self.render.edit(skel) if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() skel.toDB() # write it! self.onItemEdited(skel) self.onItemChanged(skel) return self.render.editItemSuccess(skel)
def reparent(self, item, dest, skey, *args, **kwargs): """ Moves an entry *item* (and everything beneath it) to another parent-node *dest*. .. seealso:: :func:`canReparent` :param item: URL-safe key of the item which will be moved. :type item: str :param dest: URL-safe key of the new parent for this item. :type dest: str :returns: A rendered success result generated by the default renderer. :raises: :exc:`server.errors.NotFound`, when no entry with the given *id* was found. :raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions. :raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified. """ if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() if not self.canReparent(item, dest): raise errors.Unauthorized() if not self.isValidParent(dest) or item == dest: raise errors.NotAcceptable() ## Test for recursion isValid = False currLevel = db.Get(dest) for x in range(0, 99): if str(currLevel.key()) == item: break if currLevel.key().kind( ) == self.viewSkel().kindName + "_rootNode": # We reached a rootNode isValid = True break currLevel = db.Get(currLevel["parententry"]) if not isValid: raise errors.NotAcceptable() ## Update entry fromItem = db.Get(item) fromItem["parententry"] = dest fromItem["parentrepo"] = str(self.getRootNode(dest).key()) db.Put(fromItem) skel = self.editSkel() assert skel.fromDB(item) self.onItemReparent(skel) self.onItemChanged(skel) return self.render.reparentSuccess(obj=fromItem)
def add(self, skelType, node, *args, **kwargs): ## We can't add files directly (they need to be uploaded # if skelType != "node": # raise errors.NotAcceptable() if skelType == "leaf": # We need to handle leafs separately here skey = kwargs.get("skey") targetKey = kwargs.get("key") #if not skey or not securitykey.validate(skey, useSessionKey=True) or not targetKey: # raise errors.PreconditionFailed() skel = self.addLeafSkel() if not skel.fromDB(targetKey): raise errors.NotFound() if not skel["pending"]: raise errors.PreconditionFailed() skel["pending"] = False skel["parentdir"] = skel["pendingParentdir"] if skel["parentdir"]: rootNode = self.getRootNode(skel["parentdir"]) else: rootNode = None if not self.canAdd("leaf", rootNode): raise errors.Forbidden() blobs = list(bucket.list_blobs(prefix="%s/" % targetKey)) if len(blobs) != 1: logging.error("Invalid number of blobs in folder") logging.error(targetKey) raise errors.PreconditionFailed() blob = blobs[0] skel["mimetype"] = utils.escapeString(blob.content_type) skel["name"] = utils.escapeString( blob.name.replace("%s/source/" % targetKey, "")) skel["size"] = blob.size skel["rootnode"] = rootNode skel["weak"] = rootNode is None skel.toDB() # Add updated download-URL as the auto-generated isn't valid yet skel["downloadUrl"] = utils.downloadUrlFor(skel["dlkey"], skel["name"], derived=False) return self.render.addItemSuccess(skel) return super(File, self).add(skelType, node, *args, **kwargs)
def add(self, skelType, node, *args, **kwargs): """ Add a new entry with the given parent *node*, and render the entry, eventually with error notes on incorrect data. Data is taken by any other arguments in *kwargs*. The function performs several access control checks on the requested entity before it is added. .. seealso:: :func:`canAdd`, :func:`onAdd`, , :func:`onAdded` :param skelType: Defines the type of the new entry and may either be "node" or "leaf". :type skelType: str :param node: URL-safe key of the parent. :type node: str :returns: The rendered, added object of the entry, eventually with error hints. :raises: :exc:`server.errors.NotAcceptable`, when no valid *skelType* was provided. :raises: :exc:`server.errors.NotFound`, when no valid *node* was found. :raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions. :raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified. """ if "skey" in kwargs: skey = kwargs["skey"] else: skey = "" if not self._checkSkelType(skelType): raise errors.NotAcceptable() skel = self.addSkel(skelType) parentNodeSkel = self.editSkel("node") if not parentNodeSkel.fromDB(node): raise errors.NotFound() if not self.canAdd(skelType, parentNodeSkel): raise errors.Unauthorized() if (len(kwargs) == 0 # no data supplied or skey == "" # no security key or not skel.fromClient( kwargs) # failure on reading into the bones or not currentRequest.get().isPostRequest or ("bounce" in kwargs and kwargs["bounce"] == "1" ) # review before adding ): return self.render.add(skel) if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() skel["parententry"] = parentNodeSkel["key"] # parentrepo may not exist of parentNodeSkel as it may be an rootNode skel["parentrepo"] = parentNodeSkel["parentrepo"] or parentNodeSkel[ "key"] self.onAdd(skel) skel.toDB() self.onAdded(skel) return self.render.addSuccess(skel)
def clone(self, fromRepo, toRepo, fromParent=None, toParent=None, *args, **kwargs): """ Clones a hierarchy recursively. This function only initiates the cloning process, which is performed in the background. It states only a successful result when the clone action has been correctly initiated. :param fromRepo: URL-safe key of the key to the repository (=root-node Key) to clone from. :type fromRepo: str :param toRepo: URL-safe key of the key to the repository (=root-node Key) to clone to. :type toRepo: str :param fromParent: URL-safe key of the parent to clone from; for root nodes, this is equal \ to fromRepo, and can be omitted. :type fromParent: str :param toParent: URL-safe key of the parent to clone to; for root nodes, this is equal to \ toRepo, and can be omitted. :type toParent: str :returns: A rendered success result generated by the default renderer. :raises: :exc:`server.errors.NotAcceptable`, when no valid *parent* was found. :raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions. :raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified. """ if "skey" in kwargs: skey = kwargs["skey"] else: skey = "" if fromParent is None: fromParent = fromRepo if toParent is None: toParent = toRepo if not (self.isValidParent(fromParent) and self.isValidParent(toParent)): # Ensure the parents exists raise errors.NotAcceptable() if not self.canAdd(toParent): raise errors.Unauthorized() if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() self._clone(fromRepo, toRepo, fromParent, toParent) return self.render.cloneSuccess()
def edit(self, skelType, key, *args, **kwargs): """ Modify an existing entry, and render the entry, eventually with error notes on incorrect data. Data is taken by any other arguments in *kwargs*. The function performs several access control checks on the requested entity before it is added. .. seealso:: :func:`onAdded`, :func:`canEdit` :param skelType: Defines the type of the entry that should be modified and may either be "node" or "leaf". :type skelType: str :param key: URL-safe key of the item to be edited. :type key: str :returns: The rendered, modified object of the entry, eventually with error hints. :raises: :exc:`server.errors.NotAcceptable`, when no valid *skelType* was provided. :raises: :exc:`server.errors.NotFound`, when no valid *node* was found. :raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions. :raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified. """ if "skey" in kwargs: skey = kwargs["skey"] else: skey = "" if skelType == "node": skelType = TreeType.Node elif skelType == "leaf" and self.leafSkelCls: skelType = TreeType.Leaf else: raise errors.NotAcceptable() skel = self.addSkel(skelType) if not skel.fromDB(key): raise errors.NotFound() if not self.canEdit(skelType, skel): raise errors.Unauthorized() if (len(kwargs) == 0 # no data supplied or skey == "" # no security key or not skel.fromClient( kwargs) # failure on reading into the bones or not currentRequest.get().isPostRequest or ("bounce" in kwargs and kwargs["bounce"] == "1" ) # review before adding ): return self.render.edit(skel) if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() skel.toDB() self.onEdited(skel) return self.render.editSuccess(skel)
def markCanceled(self, key, skey, *args, **kwargs): """ Exposed function for marking an order as cancelled. :param key: The key of the order to be marked. :type key: str """ if not self.canEdit(key): raise errors.Unauthorized() if not securitykey.validate(skey): raise errors.PreconditionFailed() self.setCanceled(key) return "OKAY"
def download(self, blobKey, fileName="", download="", sig="", *args, **kwargs): """ Download a file. :param blobKey: The unique blob key of the file. :type blobKey: str :param fileName: Optional filename to provide in the header. :type fileName: str :param download: Set header to attachment retrival, set explictly to "1" if download is wanted. :type download: str """ global credentials, bucket if not sig: raise errors.PreconditionFailed() # First, validate the signature, otherwise we don't need to proceed any further if not utils.hmacVerify(blobKey.encode("ASCII"), sig): raise errors.Forbidden() # Split the blobKey into the individual fields it should contain dlPath, validUntil = urlsafe_b64decode(blobKey).decode("UTF-8").split( "\0") if validUntil != "0" and datetime.strptime( validUntil, "%Y%m%d%H%M") < datetime.now(): raise errors.Gone() # Create a signed url and redirect the user if isinstance(credentials, ServiceAccountCredentials ): # We run locally with an service-account.json blob = bucket.get_blob(dlPath) if not blob: raise errors.NotFound() signed_url = blob.generate_signed_url(datetime.now() + timedelta(seconds=60)) else: # We are inside the appengine auth_request = requests.Request() signed_blob_path = bucket.blob(dlPath) expires_at_ms = datetime.now() + timedelta(seconds=60) signing_credentials = compute_engine.IDTokenCredentials( auth_request, "", service_account_email=credentials.service_account_email) signed_url = signed_blob_path.generate_signed_url( expires_at_ms, credentials=signing_credentials, version="v4") raise errors.Redirect(signed_url)
def edit(self, *args, **kwargs): """ Modify the existing entry, and render the entry, eventually with error notes on incorrect data. The entry is fetched by its entity key, which either is provided via *kwargs["key"]*, or as the first parameter in *args*. The function performs several access control checks on the singleton's entity before it is modified. .. seealso:: :func:`editSkel`, :func:`onItemEdited`, :func:`canEdit` :returns: The rendered, edited object of the entry, eventually with error hints. :raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions. :raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified. """ if "skey" in kwargs: skey = kwargs["skey"] else: skey = "" skel = self.editSkel() if not self.canEdit(): raise errors.Unauthorized() key = db.Key.from_path(self.editSkel().kindName, self.getKey()) if not skel.fromDB( str(key)): # Its not there yet; we need to set the key again skel["key"] = str(key) if (len(kwargs) == 0 # no data supplied or skey == "" # no skey provided or not skel.fromClient( kwargs) # failure on reading into the bones or ("bounce" in kwargs and kwargs["bounce"] == "1")): # review before changing return self.render.edit(skel) if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() skel.toDB() self.onItemEdited(skel) return self.render.editItemSuccess(skel)
def add(self, parent, *args, **kwargs): """ Add a new entry with the given parent, and render the entry, eventually with error notes on incorrect data. Data is taken by any other arguments in *kwargs*. The function performs several access control checks on the requested entity before it is added. .. seealso:: :func:`addSkel`, :func:`onItemAdded`, :func:`canAdd` :param parent: URL-safe key of the parent. :type parent: str :returns: The rendered, added object of the entry, eventually with error hints. :raises: :exc:`server.errors.NotAcceptable`, when no valid *parent* was found. :raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions. :raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified. """ if "skey" in kwargs: skey = kwargs["skey"] else: skey = "" if not self.isValidParent(parent): # Ensure the parent exists raise errors.NotAcceptable() if not self.canAdd(parent): raise errors.Unauthorized() skel = self.addSkel() if (len(kwargs) == 0 or skey == "" or not request.current.get().isPostRequest or not skel.fromClient(kwargs) or ("bounce" in kwargs and kwargs["bounce"] == "1")): return self.render.add(skel) if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() skel["parententry"] = str(parent) skel["parentrepo"] = str(self.getRootNode(parent).key()) key = skel.toDB() self.onItemAdded(skel) self.onItemChanged(skel) return self.render.addItemSuccess(skel)
def delete(self, skelType, key, *args, **kwargs): """ Deletes an entry or an directory (including its contents). The function runs several access control checks on the data before it is deleted. .. seealso:: :func:`canDelete`, :func:`onItemDeleted` :param skelType: Defines the type of the entry that should be deleted and may either be "node" or "leaf". :type skelType: str :param key: URL-safe key of the item to be deleted. :type key: str :returns: The rendered, deleted object of the entry. :raises: :exc:`server.errors.NotFound`, when no entry with the given *key* was found. :raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions. :raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified. """ if skelType == "node": skel = self.viewSkel(TreeType.Node) elif skelType == "leaf" and self.hasDistinctLeafs: skel = self.viewSkel(TreeType.Leaf) else: raise errors.NotAcceptable() if "skey" in kwargs: skey = kwargs["skey"] else: skey = "" if not skel.fromDB(key): raise errors.NotFound() if not self.canDelete(skelType, skel): raise errors.Unauthorized() if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() if skelType == "node": self.deleteRecursive(key) skel.delete() self.onItemDeleted(skel) return self.render.deleteSuccess(skel, skelType=skelType)
def add(self, *args, **kwargs): """ Allows guests to register a new account if self.registrationEnabled is set to true .. seealso:: :func:`addSkel`, :func:`onAdded`, :func:`canAdd` :returns: The rendered, added object of the entry, eventually with error hints. :raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions. :raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified. """ if "skey" in kwargs: skey = kwargs["skey"] else: skey = "" if not self.canAdd(): raise errors.Unauthorized() skel = self.addSkel() if (len(kwargs) == 0 # no data supplied or skey == "" # no skey supplied or not currentRequest.get(). isPostRequest # bail out if not using POST-method or not skel.fromClient( kwargs) # failure on reading into the bones or ("bounce" in kwargs and kwargs["bounce"] == "1")): # review before adding # render the skeleton in the version it could as far as it could be read. return self.userModule.render.add(skel) if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() skel.toDB() if self.registrationEmailVerificationRequired and str( skel["status"]) == "1": # The user will have to verify his email-address. Create an skey and send it to his address skey = securitykey.create(duration=60 * 60 * 24 * 7, userKey=utils.normalizeKey(skel["key"]), name=skel["name"]) skel.skey = baseBone(descr="Skey") skel["skey"] = skey email.sendEMail(dests=[skel["name"]], tpl=self.userModule.verifyEmailAddressMail, skel=skel) self.userModule.onAdded(skel) # Call onAdded on our parent user module return self.userModule.render.addSuccess(skel)
def download(self, blobKey, fileName="", download="", sig="", *args, **kwargs): """ Download a file. :param blobKey: The unique blob key of the file. :type blobKey: str :param fileName: Optional filename to provide in the header. :type fileName: str :param download: Set header to attachment retrival, set explictly to "1" if download is wanted. :type download: str """ if not sig: raise errors.PreconditionFailed() # if download == "1": # fname = "".join( # [c for c in fileName if c in string.ascii_lowercase + string.ascii_uppercase + string.digits + ".-_"]) # request.current.get().response.headers.add_header("Content-disposition", # ("attachment; filename=%s" % (fname)).encode("UTF-8")) # First, validate the signature, otherwise we don't need to proceed any further if not utils.hmacVerify(blobKey.encode("ASCII"), sig): raise errors.Forbidden() # Split the blobKey into the individual fields it should contain dlPath, validUntil = urlsafe_b64decode(blobKey).decode("UTF-8").split( "\0") if validUntil != "0" and datetime.strptime( validUntil, "%Y%m%d%H%M") < datetime.now(): raise errors.Gone() # Create a signed url and redirect the user blob = bucket.get_blob(dlPath) if not blob: raise errors.NotFound() signed_url = blob.generate_signed_url(datetime.now() + timedelta(seconds=60)) raise errors.Redirect(signed_url)
def preview(self, skey, *args, **kwargs): """ Renders data for the entry, without reading it from the database. This function allows to preview the entry without writing it to the database. Any entity values are provided via *kwargs*. The function uses the viewTemplate of the application. :returns: The rendered representation of the supplied data. """ if not self.canPreview(): raise errors.Unauthorized() if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() skel = self.viewSkel() skel.fromClient(kwargs) return self.render.view(skel)
def add(self, *args, **kwargs): """ Add a new entry, and render the entry, eventually with error notes on incorrect data. Data is taken by any other arguments in *kwargs*. The function performs several access control checks on the requested entity before it is added. .. seealso:: :func:`addSkel`, :func:`onAdd`, :func:`onAdded`, :func:`canAdd` :returns: The rendered, added object of the entry, eventually with error hints. :raises: :exc:`viur.core.errors.Unauthorized`, if the current user does not have the required permissions. :raises: :exc:`viur.core.errors.PreconditionFailed`, if the *skey* could not be verified. """ if "skey" in kwargs: skey = kwargs["skey"] else: skey = "" if not self.canAdd(): raise errors.Unauthorized() skel = self.addSkel() if (len(kwargs) == 0 # no data supplied or skey == "" # no skey supplied or not currentRequest.get(). isPostRequest # failure if not using POST-method or not skel.fromClient( kwargs) # failure on reading into the bones or ("bounce" in kwargs and kwargs["bounce"] == "1" ) # review before adding ): # render the skeleton in the version it could as far as it could be read. return self.render.add(skel) if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() self.onAdd(skel) skel.toDB() self.onAdded(skel) return self.render.addSuccess(skel)
def getUploadURL(self, *args, **kwargs): skey = kwargs.get("skey", "") node = kwargs.get("node") if node: rootNode = self.getRootNode(node) if not self.canAdd(TreeType.Leaf, rootNode): raise errors.Forbidden() else: if not self.canAdd(TreeType.Leaf, None): raise errors.Forbidden() if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() targetKey, uploadUrl, policy = self.createUploadURL(node) resDict = { "url": uploadUrl, "params": { "key": "%s/source/file.dat" % targetKey, } } for key, value in policy.items(): resDict["params"][key] = value return self.render.view(resDict)
def execute(self, taskID, *args, **kwargs): """Queues a specific task for the next maintenance run""" global _callableTasks from viur.core import securitykey if taskID in _callableTasks: task = _callableTasks[taskID]() else: return if not task.canCall(): raise errors.Unauthorized() skel = task.dataSkel() if "skey" in kwargs: skey = kwargs["skey"] else: skey = "" if len(kwargs) == 0 or skey == "" or not skel.fromClient(kwargs) or ( "bounce" in kwargs and kwargs["bounce"] == "1"): return self.render.add(skel) if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() task.execute(**skel.accessedValues) return self.render.addSuccess(skel)
def findAndCall(self, path: str, *args, **kwargs): """ Does the actual work of sanitizing the parameter, determine which @exposed (or @internalExposed) function to call (and with witch parameters) """ # Prevent Hash-collision attacks kwargs = {} stopCount = conf["viur.maxPostParamsCount"] try: for key, value in self.request.params.iteritems(): key = unicodedata.normalize("NFC", key) value = unicodedata.normalize("NFC", value) if key.startswith( "_" ): # Ignore keys starting with _ (like VI's _unused_time_stamp) continue if key in kwargs: if isinstance(kwargs[key], list): kwargs[key].append(value) else: # Convert that key to a list kwargs[key] = [kwargs[key], value] else: kwargs[key] = value stopCount -= 1 if not stopCount: # We reached zero; maximum PostParamsCount exceeded raise errors.NotAcceptable() except UnicodeError: # We received invalid unicode data (usually happens when someone tries to exploit unicode normalisation bugs) raise errors.BadRequest() if "self" in kwargs or "return" in kwargs: # self or return is reserved for bound methods raise errors.BadRequest() # Parse the URL path = parse.urlparse(path).path self.pathlist = [ unicodedata.normalize("NFC", parse.unquote(x)) for x in path.strip("/").split("/") ] caller = conf["viur.mainResolver"] idx = 0 # Count how may items from *args we'd have consumed (so the rest can go into *args of the called func for currpath in self.pathlist: if "canAccess" in caller and not caller["canAccess"](): # We have a canAccess function guarding that object, # and it returns False... raise (errors.Unauthorized()) idx += 1 currpath = currpath.replace("-", "_").replace(".", "_") if currpath in caller: caller = caller[currpath] if (("exposed" in dir(caller) and caller.exposed) or ("internalExposed" in dir(caller) and caller.internalExposed and self.internalRequest)) and hasattr( caller, '__call__'): args = self.pathlist[idx:] + [ x for x in args ] # Prepend the rest of Path to args break elif "index" in caller: caller = caller["index"] if (("exposed" in dir(caller) and caller.exposed) or ("internalExposed" in dir(caller) and caller.internalExposed and self.internalRequest)) and hasattr( caller, '__call__'): args = self.pathlist[idx - 1:] + [x for x in args] break else: raise (errors.NotFound( "The path %s could not be found" % "/".join([("".join([ y for y in x if y.lower() in "0123456789abcdefghijklmnopqrstuvwxyz" ])) for x in self.pathlist[:idx]]))) else: raise (errors.NotFound( "The path %s could not be found" % "/".join([("".join([ y for y in x if y.lower() in "0123456789abcdefghijklmnopqrstuvwxyz" ])) for x in self.pathlist[:idx]]))) if (not callable(caller) or ((not "exposed" in dir(caller) or not caller.exposed)) and (not "internalExposed" in dir(caller) or not caller.internalExposed or not self.internalRequest)): if "index" in caller \ and (callable(caller["index"]) \ and ("exposed" in dir(caller["index"]) and caller["index"].exposed) \ or ("internalExposed" in dir( caller["index"]) and caller["index"].internalExposed and self.internalRequest)): caller = caller["index"] else: raise (errors.MethodNotAllowed()) # Check for forceSSL flag if not self.internalRequest \ and "forceSSL" in dir(caller) \ and caller.forceSSL \ and not self.request.host_url.lower().startswith("https://") \ and not self.isDevServer: raise (errors.PreconditionFailed( "You must use SSL to access this ressource!")) # Check for forcePost flag if "forcePost" in dir( caller) and caller.forcePost and not self.isPostRequest: raise (errors.MethodNotAllowed( "You must use POST to access this ressource!")) self.args = args self.kwargs = kwargs # Check if this request should bypass the caches if self.request.headers.get("X-Viur-Disable-Cache"): from viur.core import utils # No cache requested, check if the current user is allowed to do so user = utils.getCurrentUser() if user and "root" in user["access"]: logging.debug( "Caching disabled by X-Viur-Disable-Cache header") self.disableCache = True try: annotations = typing.get_type_hints(caller) if annotations and not self.internalRequest: newKwargs = { } # The dict of new **kwargs we'll pass to the caller newArgs = [] # List of new *args we'll pass to the caller argsOrder = list( caller.__code__.co_varnames)[1:caller.__code__.co_argcount] # Map args in for idx in range(0, min(len(self.args), len(argsOrder))): paramKey = argsOrder[idx] if paramKey in annotations: # We have to enforce a type-annotation for this *args parameter _, newTypeValue = self.processTypeHint( annotations[paramKey], self.args[idx], True) newArgs.append(newTypeValue) else: newArgs.append(self.args[idx]) newArgs.extend(self.args[min(len(self.args), len(argsOrder)):]) # Last, we map the kwargs in for k, v in kwargs.items(): if k in annotations: newStrValue, newTypeValue = self.processTypeHint( annotations[k], v, False) self.kwargs[k] = newStrValue newKwargs[k] = newTypeValue else: newKwargs[k] = v else: newArgs = self.args newKwargs = self.kwargs if (conf["viur.debug.traceExternalCallRouting"] and not self.internalRequest ) or conf["viur.debug.traceInternalCallRouting"]: logging.debug("Calling %s with args=%s and kwargs=%s" % (str(caller), str(newArgs), str(newKwargs))) res = caller(*newArgs, **newKwargs) res = str(res).encode("UTF-8") if not isinstance(res, bytes) else res self.response.write(res) except TypeError as e: if self.internalRequest: # We provide that "service" only for requests originating from outside raise if "viur/core/request.py\", line 5" in traceback.format_exc( ).splitlines()[-3]: # Don't raise NotAcceptable for type-errors raised deep somewhere inside caller. # We check if the last line in traceback originates from viur/core/request.py and a line starting with # 5 and only raise NotAcceptable then. Otherwise a "normal" 500 Server error will be raised. # This is kinda hackish, however this is much faster than reevaluating the args and kwargs passed # to caller as we did in ViUR2. raise errors.NotAcceptable() raise
def findAndCall(self, path, *args, **kwargs): # Do the actual work: process the request # Prevent Hash-collision attacks kwargs = {} stopCount = conf["viur.maxPostParamsCount"] for key, value in self.request.params.iteritems(): if key in kwargs: if isinstance(kwargs[key], list): kwargs[key].append(value) else: kwargs[key] = [kwargs[key], value] else: kwargs[key] = value stopCount -= 1 if not stopCount: # We reached zero; maximum PostParamsCount exceeded raise errors.NotAcceptable() if "self" in kwargs: # self is reserved for bound methods raise errors.BadRequest() # Parse the URL path = parse.urlparse(path).path self.pathlist = [parse.unquote(x) for x in path.strip("/").split("/")] caller = conf["viur.mainResolver"] idx = 0 # Count how may items from *args we'd have consumed (so the rest can go into *args of the called func for currpath in self.pathlist: if "canAccess" in caller and not caller["canAccess"](): # We have a canAccess function guarding that object, # and it returns False... raise (errors.Unauthorized()) idx += 1 currpath = currpath.replace("-", "_").replace(".", "_") if currpath in caller: caller = caller[currpath] if (("exposed" in dir(caller) and caller.exposed) or ("internalExposed" in dir(caller) and caller.internalExposed and self.internalRequest)) and hasattr( caller, '__call__'): args = self.pathlist[idx:] + [ x for x in args ] # Prepend the rest of Path to args break elif "index" in caller: caller = caller["index"] if (("exposed" in dir(caller) and caller.exposed) or ("internalExposed" in dir(caller) and caller.internalExposed and self.internalRequest)) and hasattr( caller, '__call__'): args = self.pathlist[idx - 1:] + [x for x in args] break else: raise (errors.NotFound( "The path %s could not be found" % "/".join([("".join([ y for y in x if y.lower() in "0123456789abcdefghijklmnopqrstuvwxyz" ])) for x in self.pathlist[:idx]]))) else: raise (errors.NotFound( "The path %s could not be found" % "/".join([("".join([ y for y in x if y.lower() in "0123456789abcdefghijklmnopqrstuvwxyz" ])) for x in self.pathlist[:idx]]))) if (not callable(caller) or ((not "exposed" in dir(caller) or not caller.exposed)) and (not "internalExposed" in dir(caller) or not caller.internalExposed or not self.internalRequest)): if "index" in caller \ and (callable(caller["index"]) \ and ("exposed" in dir(caller["index"]) and caller["index"].exposed) \ or ("internalExposed" in dir( caller["index"]) and caller["index"].internalExposed and self.internalRequest)): caller = caller["index"] else: raise (errors.MethodNotAllowed()) # Check for forceSSL flag if not self.internalRequest \ and "forceSSL" in dir(caller) \ and caller.forceSSL \ and not self.request.host_url.lower().startswith("https://") \ and not self.isDevServer: raise (errors.PreconditionFailed( "You must use SSL to access this ressource!")) # Check for forcePost flag if "forcePost" in dir( caller) and caller.forcePost and not self.isPostRequest: raise (errors.MethodNotAllowed( "You must use POST to access this ressource!")) self.args = [] for arg in args: if isinstance(arg, str): self.args.append(arg) else: try: self.args.append(arg.decode("UTF-8")) except: pass self.kwargs = kwargs # Check if this request should bypass the caches if self.request.headers.get("X-Viur-Disable-Cache"): from viur.core import utils # No cache requested, check if the current user is allowed to do so user = utils.getCurrentUser() if user and "root" in user["access"]: logging.debug( "Caching disabled by X-Viur-Disable-Cache header") self.disableCache = True try: if (conf["viur.debug.traceExternalCallRouting"] and not self.internalRequest ) or conf["viur.debug.traceInternalCallRouting"]: logging.debug("Calling %s with args=%s and kwargs=%s" % (str(caller), str(args), str(kwargs))) res = caller(*self.args, **self.kwargs) res = str(res).encode("UTF-8") if not isinstance(res, bytes) else res self.response.write(res) except TypeError as e: if self.internalRequest: # We provide that "service" only for requests originating from outside raise # Check if the function got too few arguments and raise a NotAcceptable error tmpRes = {} argsOrder = list( caller.__code__.co_varnames)[1:caller.__code__.co_argcount] # Map default values in reversedArgsOrder = argsOrder[::-1] for defaultValue in list(caller.__defaults__ or [])[::-1]: tmpRes[reversedArgsOrder.pop(0)] = defaultValue del reversedArgsOrder # Map args in setArgs = [] # Store a list of args already set by *args for idx in range(0, min(len(args), len(argsOrder))): setArgs.append(argsOrder[idx]) tmpRes[argsOrder[idx]] = args[idx] # Last, we map the kwargs in for k, v in kwargs.items(): if k in setArgs: # This key has already been set by *args raise ( errors.NotAcceptable() ) # We reraise that exception as we got duplicate arguments tmpRes[k] = v # Last check, that every parameter is satisfied: if not all([x in tmpRes.keys() for x in argsOrder]): raise (errors.NotAcceptable()) raise
def checkout(self, step=None, key=None, skey=None, *args, **kwargs): """ Performs the checkout process trough the state machine provided by self.steps. :param step: The current step index, None for beginning a new checkout :param key: Key of the current checkout :param skey: Server security key :return: Returns the rendered template or throws redirection exceptions. """ myKindName = self.viewSkel().kindName if step is None: logging.info("Starting new checkout process") billObj = db.Entity(myKindName) billObj["idx"] = "0000000" for state in self.states: billObj["state_%s" % state] = "0" db.Put(billObj) key = str(billObj.key()) # Copy the Cart if "amountSkel" in dir(self): cart = session.current.get("cart_products") or {} s = self.amountSkel() products = [] for prod, atts in cart.items(): for i in range(0, atts["amount"]): products.append(str(prod)) s.fromClient({"product": products}) s.toDB() session.current["order_" + myKindName] = { "key": str(key), "completedSteps": [] } session.current.markChanged() raise errors.Redirect("?step=0&key=%s" % str(key)) elif key: try: orderKey = db.Key(key) step = int(step) assert (step >= 0) assert (step < len(self.steps)) except: raise errors.NotAcceptable() sessionInfo = session.current.get("order_" + myKindName) if not sessionInfo or not sessionInfo.get("key") == str(orderKey): raise errors.Unauthorized() if step in sessionInfo["completedSteps"]: session.current["order_" + myKindName]["completedSteps"] = [ x for x in sessionInfo["completedSteps"] if x < step ] session.current.markChanged() # Make sure that no steps can be skipped if step != 0 and not step - 1 in sessionInfo["completedSteps"]: raise errors.Redirect("?step=0&key=%s" % str(str(orderKey))) currentStep = self.steps[step] if "preHandler" in currentStep.keys(): try: if isinstance(currentStep["preHandler"], list): for handler in currentStep["preHandler"]: handler(self, step, str(orderKey), *args, **kwargs) else: currentStep["preHandler"](self, step, str(orderKey), refkwargs=kwargs, *args, **kwargs) except SkipStepException: session.current["order_" + myKindName]["completedSteps"].append(step) session.current.markChanged() raise errors.Redirect("?step=%s&key=%s" % (str(step + 1), str(orderKey))) except ReturnHtmlException as e: return (e.html) if "requiresSecurityKey" in currentStep and currentStep[ "requiresSecurityKey"]: if not securitykey.validate(skey): raise errors.PreconditionFailed() pass if "mainHandler" in currentStep: if currentStep["mainHandler"]["action"] == "edit": skel = self.getSkelByName( currentStep["mainHandler"]["skeleton"], str(orderKey)) skel.fromDB(str(orderKey)) if not len(kwargs.keys()) or not skel.fromClient(kwargs): return (self.render.edit( skel, tpl=currentStep["mainHandler"]["template"], step=step)) skel.toDB() if currentStep["mainHandler"]["action"] == "view": if not "complete" in kwargs or not kwargs[ "complete"] == u"1": skel = self.getSkelByName( currentStep["mainHandler"]["skeleton"], str(orderKey)) skel.fromDB(str(orderKey)) return (self.render.view( skel, tpl=currentStep["mainHandler"]["template"], step=step)) elif currentStep["mainHandler"]["action"] == "function": res = currentStep["mainHandler"]["function"](self, step, str(orderKey), *args, **kwargs) if res: return res if "postHandler" in currentStep: currentStep["postHandler"](self, step, str(orderKey), *args, **kwargs) session.current["order_" + myKindName]["completedSteps"].append(step) session.current.markChanged() logging.info("next ?step=%s&key=%s" % (str(step + 1), str(orderKey))) raise errors.Redirect("?step=%s&key=%s" % (str(step + 1), str(orderKey)))
skel["parententry"] = parentNodeSkel["key"] # parentrepo may not exist in parentNodeSkel as it may be an rootNode skel["parentrepo"] = parentNodeSkel["parentrepo"] or parentNodeSkel[ "key"] if (len(kwargs) == 0 # no data supplied or skey == "" # no security key or not skel.fromClient( kwargs) # failure on reading into the bones or not currentRequest.get().isPostRequest or ("bounce" in kwargs and kwargs["bounce"] == "1" ) # review before adding ): return self.render.add(skel) if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() self.onAdd(skelType, skel) skel.toDB() self.onAdded(skelType, skel) return self.render.addSuccess(skel) @exposed @forceSSL def edit(self, skelType, key, *args, **kwargs): """ Modify an existing entry, and render the entry, eventually with error notes on incorrect data. Data is taken by any other arguments in *kwargs*. The function performs several access control checks on the requested entity before it is added.
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)