def canReparent(self, item, dest): """ Access control function for item moving permission. Checks if the current user has the permission to move *item* to *dest*. The default behavior is: - If no user is logged in, any modification is generally refused. - If the user has "root" access, modification is generally allowed. - If the user has the modules "edit" permission (module-edit) enabled, moving is allowed. It should be overridden for a module-specific behavior. :param item: URL-safe key of the entry. :type item: str :param item: URL-safe key of the new parent to be moved to. :type item: float .. seealso:: :func:`reparent` :returns: True, if changing the order of entries is allowed, False otherwise. :rtype: bool """ user = utils.getCurrentUser() if not user: return False if user["access"] and "root" in user["access"]: return True if user["access"] and "%s-edit" % self.moduleName in user["access"]: return True return False
def canEdit(self): """ Access control function for modification permission. Checks if the current user has the permission to edit the singletons entry. The default behavior is: - If no user is logged in, editing is generally refused. - If the user has "root" access, editing is generally allowed. - If the user has the modules "edit" permission (module-edit) enabled, editing is allowed. It should be overridden for a module-specific behavior. .. seealso:: :func:`edit` :returns: True, if editing is allowed, False otherwise. :rtype: bool """ user = utils.getCurrentUser() if not user: return False if user["access"] and "root" in user["access"]: return True if user["access"] and "%s-edit" % self.moduleName in user["access"]: return True return False
def canView(self): """ Access control function for viewing permission. Checks if the current user has the permission to view the singletons entry. The default behavior is: - If no user is logged in, viewing is generally refused. - If the user has "root" access, viewing is generally allowed. - If the user has the modules "view" permission (module-view) enabled, viewing is allowed. It should be overridden for a module-specific behavior. .. seealso:: :func:`view` :param skel: The Skeleton that should be viewed. :type skel: :class:`server.skeleton.Skeleton` :returns: True, if viewing is allowed, False otherwise. :rtype: bool """ user = utils.getCurrentUser() if not user: return (False) if user["access"] and "root" in user["access"]: return (True) if user["access"] and "%s-view" % self.moduleName in user["access"]: return (True) return (False)
def canSetIndex(self, item, index): """ Access control function for changing order permission. Checks if the current user has the permission to change the ordering of an entry. The default behavior is: - If no user is logged in, any modification is generally refused. - If the user has "root" access, modification is generally allowed. - If the user has the modules "edit" or "add" permission (module-edit, module-add) enabled, \ modification is allowed. It should be overridden for a module-specific behavior. :param item: URL-safe key of the entry. :type item: str :param item: New sortindex for this item. :type item: float .. seealso:: :func:`setIndex` :returns: True, if changing the order of entries is allowed, False otherwise. :rtype: bool """ user = utils.getCurrentUser() if not user: return (False) if user["access"] and "root" in user["access"]: return (True) if user["access"] and ("%s-edit" % self.moduleName in user["access"] or "%s-add" % self.moduleName in user["access"]): return (True) return (False)
def canMove(self, skelType: str, node: SkeletonInstance, destNode: SkeletonInstance) -> bool: """ Access control function for moving permission. Checks if the current user has the permission to move an entry. The default behavior is: - If no user is logged in, deleting is generally refused. - If the user has "root" access, deleting is generally allowed. - If the user has the modules "edit" permission (module-edit) enabled, \ moving is allowed. It should be overridden for a module-specific behavior. :param skelType: Defines the type of the node that shall be deleted. :param node: URL-safe key of the node to be moved. :param node: URL-safe key of the node where *node* should be moved to. .. seealso:: :func:`move` :returns: True, if deleting entries is allowed, False otherwise. """ user = utils.getCurrentUser() if not user: return False if user["access"] and "root" in user["access"]: return True if user and user["access"] and "%s-edit" % self.moduleName in user[ "access"]: return True return False
def canAdd(self): """ Access control function for adding permission. Checks if the current user has the permission to add a new entry. The default behavior is: - If no user is logged in, adding is generally refused. - If the user has "root" access, adding is generally allowed. - If the user has the modules "add" permission (module-add) enabled, adding is allowed. It should be overridden for a module-specific behavior. .. seealso:: :func:`add` :returns: True, if adding entries is allowed, False otherwise. :rtype: bool """ user = utils.getCurrentUser() if not user: return False # root user is always allowed. if user["access"] and "root" in user["access"]: return True # user with add-permission is allowed. if user and user["access"] and "%s-add" % self.moduleName in user[ "access"]: return True return False
def fromClient(self, skel: 'SkeletonInstance', name: str, data: dict) -> Union[None, List[ReadFromClientError]]: """ 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: None or String """ if currentRequest.get().isDevServer: # We dont enforce captchas on dev server return None user = utils.getCurrentUser() if user and "root" in user["access"]: return None # Don't bother trusted users with this (not supported by admin/vi anyways) if not "g-recaptcha-response" in data: return [ReadFromClientError(ReadFromClientErrorSeverity.NotSet, "No Captcha given!")] data = { "secret": self.privateKey, "remoteip": currentRequest.get().request.remote_addr, "response": data["g-recaptcha-response"] } req = urllib.request.Request(url="https://www.google.com/recaptcha/api/siteverify", data=urllib.parse.urlencode(data).encode(), method="POST") response = urllib.request.urlopen(req) if json.loads(response.read()).get("success"): return None return [ReadFromClientError(ReadFromClientErrorSeverity.Invalid, "Invalid Captcha")]
def canPreview(self): """ Access control function for preview permission. Checks if the current user has the permission to preview an entry. The default behavior is: - If no user is logged in, previewing is generally refused. - If the user has "root" access, previewing is generally allowed. - If the user has the modules "add" or "edit" permission (module-add, module-edit) enabled, \ previewing is allowed. It should be overridden for module-specific behavior. .. seealso:: :func:`preview` :returns: True, if previewing entries is allowed, False otherwise. :rtype: bool """ user = utils.getCurrentUser() if not user: return False if user["access"] and "root" in user["access"]: return True if (user and user["access"] and ("%s-add" % self.moduleName in user["access"] or "%s-edit" % self.moduleName in user["access"])): return True return False
def canAccess(*args, **kwargs): user = utils.getCurrentUser() if user and ("root" in user["access"] or "admin" in user["access"]): return True pathList = currentRequest.get().pathlist if len(pathList) >= 2 and pathList[1] in ["skey", "getVersion"]: # Give the user the chance to login :) return True if (len(pathList) >= 3 and pathList[1] == "user" and (pathList[2].startswith("auth_") or pathList[2].startswith("f2_") or pathList[2] == "getAuthMethods" or pathList[2] == "logout")): # Give the user the chance to login :) return True if (len(pathList) >= 4 and pathList[1] == "user" and pathList[2] == "view" and pathList[3] == "self"): # Give the user the chance to view himself. return True return False
def _getEndpointKey(self): """ :warning: It's invalid to call _getEndpointKey if method is set to user and there's no user logged in! :return: the key associated with the current endpoint (it's IP or the key of the current user) """ if self.useUser: user = utils.getCurrentUser() assert user, "Cannot decrement usage from guest!" return user["key"] else: remoteAddr = currentRequest.get().request.remote_addr if "::" in remoteAddr: # IPv6 in shorted form remoteAddr = remoteAddr.split(":") blankIndex = remoteAddr.index("") missigParts = ["0000"] * (8 - len(remoteAddr)) remoteAddr = remoteAddr[:blankIndex] + missigParts + remoteAddr[ blankIndex + 1:] return ":".join(remoteAddr[:4]) elif ":" in remoteAddr: # It's IPv6, so we remove the last 64 bits (interface id) # as it is easily controlled by the user return ":".join(remoteAddr.split(":")[:4]) else: # It's IPv4, simply return that address return remoteAddr
def canList(self, parent): """ Access control function for listing permission. Checks if the current user has the permission to list the children of the given *parent*. The default behavior is: - If no user is logged in, listing is generally refused. - If the user has "root" access, listing is generally allowed. - If the user has the modules "view" permission (module-view) enabled, listing is allowed. It should be overridden for a module-specific behavior. .. seealso:: :func:`list` :param parent: URL-safe key of the parent. :type parent: str :returns: True, if listing is allowed, False otherwise. :rtype: bool """ user = utils.getCurrentUser() if not user: return False if user["access"] and "root" in user["access"]: return True if user["access"] and "%s-view" % self.moduleName in user["access"]: return True return False
def addSkel(self): skel = super(User, self).addSkel().clone() user = utils.getCurrentUser() if not (user and user["access"] and ("%s-add" % self.moduleName in user["access"] or "root" in user["access"])): skel.status.readOnly = True skel["status"] = 0 skel.status.visible = False skel.access.readOnly = True skel["access"] = [] skel.access.visible = False else: # An admin tries to add a new user. self.extendAccessRights(skel) skel.status.readOnly = False skel.status.visible = True skel.access.readOnly = False skel.access.visible = True # Unlock and require a password skel.password.required = True skel.password.visible = True skel.password.readOnly = False skel.name.readOnly = False # Dont enforce readonly name in user/add return skel
def canView(self, skel): """ Access control function for viewing permission. Checks if the current user has the permission to view an entry. The default behavior is: - If no user is logged in, viewing is generally refused. - If the user has "root" access, viewing is generally allowed. - If the user has the modules "view" permission (module-view) enabled, viewing is allowed. If skel is None, it's a check if the current user is allowed to retrieve the skeleton structure from this module (ie. there is or could be at least one entry that is visible to that user) It should be overridden for a module-specific behavior. .. seealso:: :func:`view` :param skel: The Skeleton that should be viewed. :type skel: :class:`server.skeleton.Skeleton` | None :returns: True, if viewing is allowed, False otherwise. :rtype: bool """ user = utils.getCurrentUser() if not user: return False if user["access"] and "root" in user["access"]: return True if user["access"] and "%s-view" % self.moduleName in user["access"]: return True return False
def canAdd(self, parent): """ Access control function for adding permission. Checks if the current user has the permission to add a new entry to *parent*. The default behavior is: - If no user is logged in, adding is generally refused. - If the user has "root" access, adding is generally allowed. - If the user has the modules "add" permission (module-add) enabled, adding is allowed. It should be overridden for a module-specific behavior. .. seealso:: :func:`add` :param parent: URL-safe key of the parent node under which the element shall be added. :type parent: str :returns: True, if adding entries is allowed, False otherwise. :rtype: bool """ user = utils.getCurrentUser() if not user: return False if user["access"] and "root" in user["access"]: return True if user["access"] and "%s-add" % self.moduleName in user["access"]: return True return False
def canDelete(self, skel: SkeletonInstance) -> bool: """ Access control function for delete permission. Checks if the current user has the permission to delete an entry. The default behavior is: - If no user is logged in, deleting is generally refused. - If the user has "root" access, deleting is generally allowed. - If the user has the modules "deleting" permission (module-delete) enabled, \ deleting is allowed. It should be overridden for a module-specific behavior. :param skel: The Skeleton that should be deleted. :type skel: :class:`viur.core.skeleton.Skeleton` .. seealso:: :func:`delete` :returns: True, if deleting entries is allowed, False otherwise. :rtype: bool """ user = utils.getCurrentUser() if not user: return False if user["access"] and "root" in user["access"]: return True if user and user["access"] and "%s-delete" % self.moduleName in user[ "access"]: return True return False
def getCurrentUser(render): """ Jinja2 global: Returns the current user from the session, or None if not logged in. :return: A dict containing user data. Returns None if no user data is available. :rtype: dict """ return utils.getCurrentUser()
def getAvailableRootNodes(self, *args, **kwargs) -> List[Dict]: if utils.getCurrentUser(): repo: db.Entity = self.ensureOwnModuleRootNode() res = [{"name": "Files", "key": repo.key}] return res return []
def getCurrentUser(render): """ Jinja2 global: Returns the current user from the session, or None if not logged in. :return: A dict containing user data. Returns None if no user data is available. :rtype: dict """ currentUser = utils.getCurrentUser() if currentUser: currentUser.renderPreparation = render.renderBoneValue return currentUser
def editSkel(self, *args, **kwargs): skel = super(User, self).editSkel().clone() skel.password = passwordBone(descr="Passwort", required=False) user = utils.getCurrentUser() lockFields = not (user and "root" in user["access"] ) # If we aren't root, make certain fields read-only skel.name.readOnly = lockFields skel.access.readOnly = lockFields skel.status.readOnly = lockFields return skel
def onItemSetIndex(self, skel): """ Hook function that is called after setting a new index an entry. It should be overridden for a module-specific behavior. The default is writing a log entry. :param skel: The Skeleton that has got a new index. :type skel: :class:`server.skeleton.Skeleton` .. seealso:: :func:`setIndex` """ logging.info("Entry has a new index: %s" % skel["key"]) user = utils.getCurrentUser() if user: logging.info("User: %s (%s)" % (user["name"], user["key"]))
def onEdited(self, skel): """ Hook function that is called after modifying the entry. It should be overridden for a module-specific behavior. The default is writing a log entry. :param skel: The Skeleton that has been modified. :type skel: :class:`server.skeleton.Skeleton` .. seealso:: :func:`edit` """ logging.info("Entry changed: %s" % skel["key"]) user = utils.getCurrentUser() if user: logging.info("User: %s (%s)" % (user["name"], user["key"]))
def onItemReparent(self, skel): """ Hook function that is called after reparenting an entry. It should be overridden for a module-specific behavior. The default is writing a log entry. :param skel: The Skeleton that has been reparented. :type skel: :class:`server.skeleton.Skeleton` .. seealso:: :func:`reparent` """ logging.debug("data: %r, %r", skel, skel.keys()) logging.info("Entry reparented: %s" % skel["key"]) user = utils.getCurrentUser() if user: logging.info("User: %s (%s)" % (user["name"], user["key"]))
def onDeleted(self, skel: SkeletonInstance): """ Hook function that is called after deleting an entry. It should be overridden for a module-specific behavior. The default is writing a log entry. :param skel: The Skeleton that has been deleted. :type skel: :class:`viur.core.skeleton.Skeleton` .. seealso:: :func:`delete`, :func:`onDelete` """ logging.info("Entry deleted: %s" % skel["key"]) flushCache(key=skel["key"]) user = utils.getCurrentUser() if user: logging.info("User: %s (%s)" % (user["name"], user["key"]))
def onAdded(self, skelType, skel): """ Hook function that is called after adding an entry. It should be overridden for a module-specific behavior. The default is writing a log entry. :param skel: The Skeleton that has been added. :type skel: :class:`server.skeleton.Skeleton` .. seealso:: :func:`add`, :func:`onAdd` """ logging.info("Entry of kind %r added: %s", skelType, skel["key"]) flushCache(kind=skel.kindName) user = utils.getCurrentUser() if user: logging.info("User: %s (%s)" % (user["name"], user["key"]))
def getAvailableRootNodes__(self, name, *args, **kwargs): thisuser = utils.getCurrentUser() if not thisuser: return [] repo = self.ensureOwnUserRootNode() res = [{"name": str("My Files"), "key": str(repo.key.id_or_name)}] if 0 and "root" in thisuser["access"]: # FIXME! # Add at least some repos from other users repos = db.Query(self.viewNodeSkel.kindName + "_rootNode").filter( "type =", "user").run(100) for repo in repos: if not "user" in repo: continue user = db.Query("user").filter("uid =", repo.user).getEntry() if not user or not "name" in user: continue res.append({"name": user["name"], "key": str(repo.key())}) return res
def onItemDeleted(self, skel): """ Hook function that is called after deleting an entry. It should be overridden for a module-specific behavior. The default is writing a log entry. ..warning: Saving the skeleton again will undo the deletion (if the skeleton was a leaf or a node with no children). :param skel: The Skeleton that has been deleted. :type skel: :class:`server.skeleton.Skeleton` .. seealso:: :func:`delete` """ logging.info("Entry deleted: %s (%s)" % (skel["key"], type(skel))) user = utils.getCurrentUser() if user: logging.info("User: %s (%s)" % (user["name"], user["key"]))
def listFilter(self, filter): """ Access control function on item listing. This function is invoked by the :func:`list` renderer and the related Jinja2 fetching function, and is used to modify the provided filter parameter to match only items that the current user is allowed to see. :param filter: Query which should be altered. :type filter: :class:`server.db.Query` :returns: The altered filter, or None if access is not granted. :type filter: :class:`server.db.Query` """ user = utils.getCurrentUser() if user and ("%s-view" % self.moduleName in user["access"] or "root" in user["access"]): return filter return None
def fromClient(self, valuesCache, name, data): """ 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: None or String """ if request.current.get( ).isDevServer: # We dont enforce captchas on dev server return None user = utils.getCurrentUser() if user and "root" in user["access"]: return None # Don't bother trusted users with this (not supported by admin/vi anyways) if not "g-recaptcha-response" in data: return [ ReadFromClientError(ReadFromClientErrorSeverity.NotSet, name, "No Captcha given!") ] data = { "secret": self.privateKey, "remoteip": request.current.get().request.remote_addr, "response": data["g-recaptcha-response"] } response = urlfetch.fetch( url="https://www.google.com/recaptcha/api/siteverify", payload=urllib.urlencode(data), method=urlfetch.POST, headers={"Content-Type": "application/x-www-form-urlencoded"}) if json.loads(response.content.decode("UTF-8")).get("success"): return None return [ ReadFromClientError(ReadFromClientErrorSeverity.Invalid, name, "Invalid Captcha") ]
def isOwnUserRootNode(self, repo): """ Checks, if the given rootNode is owned by the current user. :param repo: URL-safe key of the root-node. :type repo: str :returns: True if the user owns this root-node, False otherwise. :rtype: bool """ thisuser = utils.getCurrentUser() if not thisuser: return False repo = self.getRootNode(repo) user_repo = self.ensureOwnUserRootNode() if str(repo.key.urlsafe()) == user_repo.key.urlsafe(): return True return False
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