Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
    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
Ejemplo n.º 3
0
    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)
Ejemplo n.º 4
0
    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)
Ejemplo n.º 5
0
    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
Ejemplo n.º 6
0
    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
Ejemplo n.º 7
0
	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")]
Ejemplo n.º 8
0
    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
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
    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
Ejemplo n.º 11
0
    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
Ejemplo n.º 12
0
 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
Ejemplo n.º 13
0
    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
Ejemplo n.º 14
0
    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
Ejemplo n.º 15
0
    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
Ejemplo n.º 16
0
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()
Ejemplo n.º 17
0
	def getAvailableRootNodes(self, *args, **kwargs) -> List[Dict]:
		if utils.getCurrentUser():
			repo: db.Entity = self.ensureOwnModuleRootNode()

			res = [{"name": "Files", "key": repo.key}]
			return res

		return []
Ejemplo n.º 18
0
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
Ejemplo n.º 19
0
    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
Ejemplo n.º 20
0
    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"]))
Ejemplo n.º 21
0
    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"]))
Ejemplo n.º 22
0
    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"]))
Ejemplo n.º 23
0
    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"]))
Ejemplo n.º 24
0
    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"]))
Ejemplo n.º 25
0
 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
Ejemplo n.º 26
0
	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"]))
Ejemplo n.º 27
0
    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
Ejemplo n.º 28
0
    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")
        ]
Ejemplo n.º 29
0
    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
Ejemplo n.º 30
0
    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