def view(self, skelType, key, *args, **kwargs): """ Prepares and renders a single entry for viewing. The entry is fetched by its *key* and its *skelType*. The function performs several access control checks on the requested entity before it is rendered. .. seealso:: :func:`canView`, :func:`onView` :returns: The rendered representation of the requested entity. :param skelType: May either be "node" or "leaf". :type skelType: str :param node: URL-safe key of the parent. :type node: str :raises: :exc:`server.errors.NotAcceptable`, when an incorrect *skelType* 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. """ if not self._checkSkelType(skelType): raise errors.NotAcceptable() skel = self.viewSkel(skelType) if not key: raise errors.NotAcceptable() # We return a single entry for viewing if not skel.fromDB(key): raise errors.NotFound() if not self.canView(skelType, skel): raise errors.Unauthorized() self.onView(skel) return self.render.view(skel)
def getBill(self, key, *args, **kwargs): """ Returns the Bill for the given order. """ skel = self.viewSkel() if "canView" in dir(self): if not self.canView(key): raise errors.Unauthorized() else: queryObj = self.viewSkel().all().mergeExternalFilter({"key": key}) queryObj = self.listFilter(queryObj) # Access control if queryObj is None: raise errors.Unauthorized() bill = self.getBillPdf(key) if not bill: raise errors.NotFound() request.current.get( ).response.headers['Content-Type'] = "application/pdf" return (bill)
def view(self, *args, **kwargs): """ Prepares and renders a single entry for viewing. 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 rendered. .. seealso:: :func:`viewSkel`, :func:`canView`, :func:`onView` :returns: The rendered representation of the requested entity. :raises: :exc:`viur.core.errors.NotAcceptable`, when no *key* is provided. :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. """ if "key" in kwargs: key = kwargs["key"] elif len(args) >= 1: key = args[0] else: raise errors.NotAcceptable() if not key: raise errors.NotAcceptable() # We return a single entry for viewing skel = self.viewSkel() if not skel.fromDB(key): raise errors.NotFound() if not self.canView(skel): raise errors.Forbidden() self.onView(skel) return self.render.view(skel)
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 list(self, parent, *args, **kwargs): """ List the entries which are direct children of the given *parent*. Any other supplied parameters are interpreted as filters for the elements displayed. .. seealso:: :func:`canList`, :func:`server.db.mergeExternalFilter` :param parent: URL-safe key of the parent. :type parent: str :returns: The rendered list objects for the matching entries. :raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions. :raises: :exc:`server.errors.NotFound`, if *parent* could not be found. """ if not parent or not self.canList(parent): raise errors.Unauthorized() parentSkel = self.viewSkel() if not parentSkel.fromDB(parent): if not str(parent) in [ str(x["key"]) for x in self.getAvailableRootNodes() ]: # It isn't a rootNode either raise errors.NotFound() else: parentSkel = None query = self.viewSkel().all() query.mergeExternalFilter(kwargs) query.filter("parententry", parent) return self.render.list(query.fetch(), parent=parent, parentSkel=parentSkel)
def view(self, *args, **kwargs): """ Prepares and renders the singleton entry for viewing. The function performs several access control checks on the requested entity before it is rendered. .. seealso:: :func:`viewSkel`, :func:`canView`, :func:`onViewed` :returns: The rendered representation of the entity. :raises: :exc:`server.errors.NotFound`, if there is no singleton entry existing, yet. :raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions. """ skel = self.viewSkel() if not self.canView(): raise errors.Unauthorized() key = db.Key(self.editSkel().kindName, self.getKey()) if not skel.fromDB(key): raise errors.NotFound() self.onViewed(skel) return self.render.view(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:`onAdded`, :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": skelType = TreeType.Node elif skelType == "leaf" and self.leafSkelCls: skelType = TreeType.Leaf else: raise errors.NotAcceptable() skel = self.addSkel(skelType) parentNodeSkel = self.editSkel(TreeType.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"] if "parentrepo" in parentNodeSkel else parentNodeSkel[ "key"] skel.toDB() self.onAdded(skel) return self.render.addSuccess(skel)
def index(self, *args, **kwargs): """ The first two lines of code here ensure that requesting a non-existent module or template will throw a 404 instead of referring to index. Remove them if you wish to alter this behaviour. """ if len(args) > 1 or kwargs: raise errors.NotFound() template = self.render.getEnv().get_template("index.html") return template.render(start=True)
def getTemplateFileName(self, template, ignoreStyle=False): """ Returns the filename of the template. This function decides in which language and which style a given template is rendered. The style is provided as get-parameters for special-case templates that differ from their usual way. It is advised to override this function in case that :func:`server.render.jinja2.default.Render.getLoaders` is redefined. :param template: The basename of the template to use. :type template: str :param ignoreStyle: Ignore any maybe given style hints. :type ignoreStyle: bool :returns: Filename of the template :rtype: str """ validChars = "abcdefghijklmnopqrstuvwxyz1234567890-" if "htmlpath" in dir(self): htmlpath = self.htmlpath else: htmlpath = "html" currReq = currentRequest.get() if not ignoreStyle \ and "style" in currReq.kwargs \ and all([x in validChars for x in currReq.kwargs["style"].lower()]): stylePostfix = "_" + currReq.kwargs["style"] else: stylePostfix = "" lang = currentLanguage.get() # session.current.getLanguage() fnames = [template + stylePostfix + ".html", template + ".html"] if lang: fnames = [ os.path.join(lang, template + stylePostfix + ".html"), template + stylePostfix + ".html", os.path.join(lang, template + ".html"), template + ".html" ] for fn in fnames: # check subfolders prefix = template.split("_")[0] if os.path.isfile( os.path.join(utils.projectBasePath, htmlpath, prefix, fn)): return ("%s/%s" % (prefix, fn)) for fn in fnames: # Check the templatefolder of the application if os.path.isfile(os.path.join(utils.projectBasePath, htmlpath, fn)): return fn for fn in fnames: # Check the fallback if os.path.isfile( os.path.join(utils.projectBasePath, "viur", "core", "template", fn)): return fn raise errors.NotFound("Template %s not found." % template)
def index(*args, **kwargs): from viur.core.render import isAdminAvailable, isViAvailable if not isAdminAvailable(): if isViAvailable(): # The admin is not available, the Vi however is, so redirect there raise errors.Redirect("/vi") raise errors.NotFound() if currentRequest.get().isDevServer or currentRequest.get().isSSLConnection: raise errors.Redirect("/admin/s/admin.html") else: appVersion = currentRequest.get().request.host raise errors.Redirect("https://%s/admin/s/admin.html" % appVersion)
def index(self, template="index", *arg, **kwargs): if ".." in template or "/" in template: return try: template = self.render.getEnv().get_template( self.render.getTemplateFileName("sites/" + template)) except: raise errors.NotFound() return template.render()
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:`onEdit`, :func:`onEdited`, :func:`canEdit` :returns: The rendered, edited object of the entry, eventually with error hints. :raises: :exc:`viur.core.errors.NotAcceptable`, when no *key* is provided. :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. """ if "skey" in kwargs: skey = kwargs["skey"] else: skey = "" if "key" in kwargs: key = kwargs["key"] elif len(args) == 1: key = args[0] else: raise errors.NotAcceptable() skel = self.editSkel() if not skel.fromDB(key): raise errors.NotFound() if not self.canEdit(skel): raise errors.Unauthorized() if (len(kwargs) == 0 # no data supplied or skey == "" # no security key 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 changing ): # render the skeleton in the version it could as far as it could be read. return self.render.edit(skel) if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() self.onEdit(skel) skel.toDB() # write it! self.onEdited(skel) return self.render.editSuccess(skel)
def edit(self, skelType, key, skey="", *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:`onItemAdded`, :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 skelType == "node": skel = self.viewSkel(TreeType.Node) elif skelType == "leaf" and self.hasDistinctLeafs: skel = self.viewSkel(TreeType.Leaf) else: raise errors.NotAcceptable() 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 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.edit(skel) if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() skel.toDB() self.onItemEdited(skel) return self.render.editItemSuccess(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 """ 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 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, 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 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 view(self, *args, **kwargs): """ Prepares and renders a single entry for viewing. 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 rendered. .. seealso:: :func:`viewSkel`, :func:`canView`, :func:`onItemViewed` :returns: The rendered representation of the requested entity. :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. """ if "key" in kwargs: key = kwargs["key"] elif len(args) >= 1: key = args[0] else: raise errors.NotAcceptable() if not len(key): raise errors.NotAcceptable() skel = self.viewSkel() if key == u"structure": # We dump just the structure of that skeleton, including it's default values if not self.canView(skel): raise errors.Unauthorized() else: # We return a single entry for viewing # We probably have a Database or SEO-Key here #seoKey = "viur.viurActiveSeoKeys =" #skel = self.viewSkel().all().filter(seoKey, args[0]).getSkel() skel = self.viewSkel() if not skel.fromDB(key): raise errors.NotFound() if not self.canView(skel): raise errors.Forbidden() self.onItemViewed(skel) return self.render.view(skel)
def view(self, skelType, key, *args, **kwargs): """ Prepares and renders a single entry for viewing. The entry is fetched by its *key* and its *skelType*. The function performs several access control checks on the requested entity before it is rendered. .. seealso:: :func:`canView`, :func:`onItemViewed` :returns: The rendered representation of the requested entity. :param skelType: May either be "node" or "leaf". :type skelType: str :param node: URL-safe key of the parent. :type node: str :raises: :exc:`server.errors.NotAcceptable`, when an incorrect *skelType* 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. """ if skelType == "node": skel = self.viewSkel(TreeType.Node) elif skelType == "leaf" and self.hasDistinctLeafs: skel = self.viewSkel(TreeType.Leaf) else: raise errors.NotAcceptable() if not key: raise errors.NotAcceptable() if key == u"structure": # We dump just the structure of that skeleton, including it's default values if not self.canView(skelType, None): raise errors.Unauthorized() else: # We return a single entry for viewing if not skel.fromDB(key): raise errors.NotFound() if not self.canView(skelType, skel): raise errors.Unauthorized() self.onItemViewed(skel) return self.render.view(skel)
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 upload(self, node=None, *args, **kwargs): try: canAdd = self.canAdd("leaf", node) except: canAdd = False if not canAdd: for upload in self.getUploads(): upload.delete() raise errors.Forbidden() try: res = [] if node: # The file is uploaded into a rootNode nodeSkel = self.editNodeSkel() if not nodeSkel.fromDB(node): for upload in self.getUploads(): upload.delete() raise errors.NotFound() else: weak = False parentDir = str(node) parentRepo = nodeSkel["parentrepo"] else: weak = True parentDir = None parentRepo = None # Handle the actual uploads for upload in self.getUploads(): fileName = decodeFileName(upload.filename) height = width = 0 if str(upload.content_type).startswith("image/"): try: servingURL = images.get_serving_url(upload.key()) if request.current.get().isDevServer: # NOTE: changed for Ticket ADMIN-37 servingURL = urlparse(servingURL).path elif servingURL.startswith("http://"): # Rewrite Serving-URLs to https if we are live servingURL = servingURL.replace( "http://", "https://") except: servingURL = "" try: # only fetching the file header or all if the file is smaller than 1M data = blobstore.fetch_data(upload.key(), 0, min(upload.size, 1000000)) image = images.Image(image_data=data) height = image.height width = image.width except Exception as err: logging.error( "some error occurred while trying to fetch the image header with dimensions" ) logging.exception(err) else: servingURL = "" fileSkel = self.addLeafSkel() fileSkel.setValues({ "name": utils.escapeString(fileName), "size": upload.size, "mimetype": utils.escapeString(upload.content_type), "dlkey": str(upload.key()), "servingurl": servingURL, "parentdir": parentDir, "parentrepo": parentRepo, "weak": weak, "width": width, "height": height }) fileSkel.toDB() res.append(fileSkel) self.onItemUploaded(fileSkel) # Uploads stored successfully, generate response to the client for r in res: logging.info("Upload successful: %s (%s)" % (r["name"], r["dlkey"])) user = utils.getCurrentUser() if user: logging.info("User: %s (%s)" % (user["name"], user["key"])) return self.render.addItemSuccess(res) except Exception as err: logging.exception(err) for upload in self.getUploads(): upload.delete() utils.markFileForDeletion(str(upload.key())) raise errors.InternalServerError()
def index(self, *args, **kwargs): if len(args) > 1 or kwargs: raise errors.NotFound() template = self.render.getEnv().get_template("index.html") return template.render(start=True)
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 move(self, skelType: str, key: str, parentNode: str, *args, **kwargs) -> str: """ Move a node (including its contents) or a leaf to another node. .. seealso:: :func:`canMove` :param skelType: Defines the type of the entry that should be moved and may either be "node" or "leaf". :param key: URL-safe key of the item to be moved. :param parentNode: URL-safe key of the destination node, which must be a node. :returns: The rendered, edited 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. """ if not self._checkSkelType(skelType): raise errors.NotAcceptable() skel = self.addSkel(skelType) # srcSkel - the skeleton to be moved parentNodeSkel = self.editSkel( "node") # destSkel - the node it should be moved into if not skel.fromDB(key) or not parentNodeSkel.fromDB(parentNode): # Could not find one of the entities raise errors.NotFound() if not self.canMove(skelType, skel, parentNodeSkel): raise errors.Unauthorized() if skel["key"] == parentNodeSkel["key"]: # Cannot move a node into itself raise errors.NotAcceptable() ## Test for recursion currLevel = db.Get(parentNodeSkel["key"]) for x in range(0, 99): if currLevel.key == skel["key"]: break if "rootNode" in currLevel and currLevel["rootNode"] == 1: # We reached a rootNode, so this is okay break currLevel = db.Get(currLevel["parententry"]) else: # We did not "break" - recursion-level exceeded or loop detected raise errors.NotAcceptable() # Test if we try to move a rootNode tmp = skel.dbEntity if "rootNode" in tmp and tmp["rootNode"] == 1: # Cant move a rootNode away.. raise errors.NotAcceptable() if not securitykey.validate(kwargs.get("skey", ""), useSessionKey=True): raise errors.PreconditionFailed() currentParentRepo = skel["parentrepo"] skel["parententry"] = parentNodeSkel["key"] skel["parentrepo"] = parentNodeSkel[ "parentrepo"] # Fixme: Need to recursive fixing to parentrepo? if "sortindex" in kwargs: try: skel["sortindex"] = float(kwargs["sortindex"]) except: raise errors.PreconditionFailed() skel.toDB() # Ensure a changed parentRepo get's proagated if currentParentRepo != parentNodeSkel["parentrepo"]: self.updateParentRepo(key, parentNodeSkel["parentrepo"]) return self.render.editSuccess( skel ) # new Sig, has no args and kwargs , skelType = skelType, action = "move", destNode = parentNodeSkel )
:type skelType: str :param node: URL-safe key of the parent. :type node: str :raises: :exc:`server.errors.NotAcceptable`, when an incorrect *skelType* 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. """ if not (skelType := self._checkSkelType(skelType)): raise errors.NotAcceptable(f"Invalid skelType provided.") skel = self.viewSkel(skelType) if not key: raise errors.NotAcceptable() # We return a single entry for viewing if not skel.fromDB(key): raise errors.NotFound() if not self.canView(skelType, skel): raise errors.Unauthorized() self.onView(skelType, skel) return self.render.view(skel) @exposed @forceSSL 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`
def pathToKey(self, key=None): """ Returns the recursively expanded path through the Hierarchy from the root-node to a requested node. :param key: URL-safe key of the destination entity. :type key: str :returns: An nested dictionary with information about all nodes in the path from root \ to the requested node. :rtype: dict """ def getName(obj): """ Tries to return a suitable name for the given object. """ if "name" in obj: return obj["name"] skel = self.viewSkel() if "name" in skel: nameBone = getattr(skel, "name") if (isinstance(nameBone, baseBone) and "languages" in dir(nameBone) and nameBone.languages): skel.setValues(obj) return str(skel["name"]) return None availableRepos = self.getAvailableRootNodes() if not key: try: key = availableRepos[0]["key"] except: raise errors.NotFound() keylist = [] else: if str(key).isdigit(): key = str(db.Key.from_path(self.viewSkel().kindName, long(key))) keylist = [key] if not self.canList(key): raise errors.Unauthorized() res = [] lastChildren = [] for x in range(0, 99): q = db.Query(self.viewSkel().kindName) q.filter("parententry =", str(key)) q.order("sortindex") entryObjs = q.run(100) lastChildren = res[:] res = [] for obj in entryObjs: if "parententry" in obj: parent = str(obj["parententry"]) else: parent = None r = { "name": getName(obj), "key": str(obj.key()), "parent": parent, "hrk": obj["hrk"] if "hrk" in obj else None, "active": (str(obj.key()) in keylist) } if r["active"]: r["children"] = lastChildren res.append(r) if key in [x["key"] for x in availableRepos]: break else: item = db.Get(str(key)) if item and "parententry" in item: keylist.append(key) key = item["parententry"] else: break return res