Пример #1
0
	def getUploadURL(self, fileName, mimeType, size=None, skey=None, *args, **kwargs):
		node = kwargs.get("node")
		authData = kwargs.get("authData")
		authSig = kwargs.get("authSig")
		# Validate the the contentType from the client seems legit
		mimeType = mimeType.lower()
		assert len(mimeType.split("/")) == 2, "Invalid Mime-Type"
		assert all([x in string.ascii_letters + string.digits + "/-.+" for x in mimeType]), "Invalid Mime-Type"
		if authData and authSig:
			# First, validate the signature, otherwise we don't need to proceed any further
			if not utils.hmacVerify(authData.encode("ASCII"), authSig):
				raise errors.Forbidden()
			authData = json.loads(base64.b64decode(authData.encode("ASCII")).decode("UTF-8"))
			if datetime.strptime(authData["validUntil"], "%Y%m%d%H%M") < datetime.now():
				raise errors.Gone()
			if authData["validMimeTypes"]:
				for validMimeType in authData["validMimeTypes"]:
					if validMimeType == mimeType or (
						validMimeType.endswith("*") and mimeType.startswith(validMimeType[:-1])):
						break
				else:
					raise errors.NotAcceptable()
			node = authData["node"]
			maxSize = authData["maxSize"]
		else:
			if node:
				rootNode = self.getRootNode(node)
				if not self.canAdd("leaf", rootNode):
					raise errors.Forbidden()
			else:
				if not self.canAdd("leaf", None):
					raise errors.Forbidden()
			maxSize = None  # The user has some file/add permissions, don't restrict fileSize
		if maxSize:
			try:
				size = int(size)
				assert size <= maxSize
			except:  # We have a size-limit set - but no size supplied
				raise errors.PreconditionFailed()
		else:
			size = None

		if not securitykey.validate(skey, useSessionKey=True):
			raise errors.PreconditionFailed()
		targetKey, uploadUrl = self.initializeUpload(fileName, mimeType.lower(), node, size)
		resDict = {
			"uploadUrl": uploadUrl,
			"uploadKey": targetKey,
		}
		return self.render.view(resDict)
Пример #2
0
	def login(self, skey="", token="", *args, **kwargs):
		# FIXME: Check if already logged in
		if not conf.get("viur.user.google.clientID"):
			raise errors.PreconditionFailed("Please configure 'viur.user.google.clientID' in your conf!")
		if not skey or not token:
			currentRequest.get().response.headers["Content-Type"] = "text/html"
			# Fixme: Render with Jinja2?
			tplStr = open("viur/core/template/vi_user_google_login.html", "r").read()
			tplStr = tplStr.replace("{{ clientID }}", conf["viur.user.google.clientID"])
			return tplStr
		if not securitykey.validate(skey, useSessionKey=True):
			raise errors.PreconditionFailed()
		userInfo = id_token.verify_oauth2_token(token, requests.Request(), conf["viur.user.google.clientID"])
		if userInfo['iss'] not in {'accounts.google.com', 'https://accounts.google.com'}:
			raise ValueError('Wrong issuer.')
		# Token looks valid :)
		uid = userInfo['sub']
		email = userInfo['email']
		addSkel = skeletonByKind(self.userModule.addSkel().kindName)  # Ensure that we have the full skeleton
		userSkel = addSkel().all().filter("uid =", uid).getSkel()
		if not userSkel:
			# We'll try again - checking if there's already an user with that email
			userSkel = addSkel().all().filter("name.idx =", email.lower()).getSkel()
			if not userSkel:  # Still no luck - it's a completely new user
				if not self.registrationEnabled:
					if userInfo.get("hd") and userInfo["hd"] in conf["viur.user.google.gsuiteDomains"]:
						print("User is from domain - adding account")
					else:
						logging.warning("Denying registration of %s", email)
						raise errors.Forbidden("Registration for new users is disabled")
				userSkel = addSkel()  # We'll add a new user
			userSkel["uid"] = uid
			userSkel["name"] = email
			isAdd = True
		else:
			isAdd = False
		now = datetime.datetime.now()
		if isAdd or (now - userSkel["lastlogin"]) > datetime.timedelta(minutes=30):
			# Conserve DB-Writes: Update the user max once in 30 Minutes
			userSkel["lastlogin"] = now
			#if users.is_current_user_admin():
			#	if not userSkel["access"]:
			#		userSkel["access"] = []
			#	if not "root" in userSkel["access"]:
			#		userSkel["access"].append("root")
			#	userSkel["gaeadmin"] = True
			#else:
			#	userSkel["gaeadmin"] = False
			assert userSkel.toDB()
		return self.userModule.continueAuthenticationFlow(self, userSkel["key"])
Пример #3
0
    def logout(self, skey="", *args, **kwargs):
        """
			Implements the logout action. It also terminates the current session (all keys not listed
			in viur.session.persistentFieldsOnLogout will be lost).
		"""
        user = session.current.get("user")
        if not user:
            raise errors.Unauthorized()
        if not securitykey.validate(skey, useSessionKey=True):
            raise errors.PreconditionFailed()

        self.onLogout(user)

        oldSession = {k: v
                      for k, v in session.current.items()
                      }  # Store all items in the current session
        session.current.reset()

        # Copy the persistent fields over
        for k in conf["viur.session.persistentFieldsOnLogout"]:
            if k in oldSession:
                session.current[k] = oldSession[k]

        del oldSession

        return self.render.logoutSuccess()
Пример #4
0
    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)
Пример #5
0
    def setIndex(self, item, index, skey, *args, **kwargs):
        """
		Changes the order of the elements in the current level by changing the index of *item*.

		.. seealso:: :func:`canSetIndex`

		:param item: URL-safe key of the item which index should be changed.
		:type item: str

		:param index: New index for this item. This value must be cast-able to float.
		:type index: str

		:returns: A rendered success result generated by the default renderer.

		:raises: :exc:`server.errors.NotFound`, when no entry with the given *key* was found.
		:raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions.
		:raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified.
		"""
        if not securitykey.validate(skey, useSessionKey=True):
            raise errors.PreconditionFailed()

        if not self.canSetIndex(item, index):
            raise errors.Unauthorized()

        fromItem = db.Get(item)
        fromItem["sortindex"] = float(index)
        db.Put(fromItem)
        skel = self.editSkel()
        assert skel.fromDB(item)
        self.onItemSetIndex(skel)
        self.onItemChanged(skel)

        return self.render.setIndexSuccess(obj=fromItem)
Пример #6
0
    def index(self, *args, **kwargs):
        if not self.canUse():
            raise errors.Forbidden()  # Unauthorized

        skel = self.mailSkel()

        if len(kwargs) == 0:
            return self.render.add(skel=skel, failed=False)

        if not skel.fromClient(kwargs) or not "skey" in kwargs:
            return self.render.add(skel=skel, failed=True)

        if not securitykey.validate(kwargs["skey"], useSessionKey=True):
            raise errors.PreconditionFailed()

        # Allow bones to perform outstanding "magic" operations before sending the mail
        for key, _bone in skel.items():
            if isinstance(_bone, baseBone):
                _bone.performMagic(skel.valuesCache, key, isAdd=True)

        # Get recipients
        rcpts = self.getRcpts(skel)

        # Get additional options for sendEMail
        opts = self.getOptions(skel)
        if not isinstance(opts, dict):
            opts = {}

        # Send the email!
        utils.sendEMail(rcpts, self.mailTemplate, skel, **opts)
        self.onAdded(skel)

        return self.render.addSuccess(skel)
Пример #7
0
	def add(self, skelType, node, *args, **kwargs):
		"""
		Add a new entry with the given parent *node*, and render the entry, eventually with error notes
		on incorrect data. Data is taken by any other arguments in *kwargs*.

		The function performs several access control checks on the requested entity before it is added.

		.. seealso:: :func:`onItemAdded`, :func:`canAdd`

		:param skelType: Defines the type of the new entry and may either be "node" or "leaf".
		:type skelType: str
		:param node: URL-safe key of the parent.
		:type node: str

		:returns: The rendered, added object of the entry, eventually with error hints.

		:raises: :exc:`server.errors.NotAcceptable`, when no valid *skelType* was provided.
		:raises: :exc:`server.errors.NotFound`, when no valid *node* was found.
		:raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions.
		:raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified.
		"""
		if "skey" in kwargs:
			skey = kwargs["skey"]
		else:
			skey = ""

		if skelType == "node":
			skel = self.viewSkel(TreeType.Node)
		elif skelType == "leaf" and self.hasDistinctLeafs:
			skel = self.viewSkel(TreeType.Leaf)
		else:
			raise errors.NotAcceptable()

		# FIXME: IsValidParent?
		#parentNodeSkel = self.editNodeSkel()
		#if not parentNodeSkel.fromDB(node):
		#	raise errors.NotFound()

		if not self.canAdd(skelType, node):
			raise errors.Unauthorized()

		if (len(kwargs) == 0  # no data supplied
				or skey == ""  # no security key
				# or not request.current.get().isPostRequest fixme: POST-method check missing? # failure if not using POST-method
				or not skel.fromClient(kwargs)  # failure on reading into the bones
				or ("bounce" in kwargs and kwargs["bounce"] == "1")  # review before adding
		):
			return self.render.add(skel)

		if not securitykey.validate(skey, useSessionKey=True):
			raise errors.PreconditionFailed()

		skel["parentdir"] = str(node)
		skel["parentrepo"] = parentNodeSkel["parentrepo"] or str(node)

		skel.toDB()
		self.onItemAdded(skel)

		return self.render.addItemSuccess(skel)
Пример #8
0
    def otp(self, otptoken=None, skey=None, *args, **kwargs):
        currSess = currentSession.get()
        token = currSess.get("_otp_user")
        if not token:
            raise errors.Forbidden()
        if otptoken is None:
            self.userModule.render.edit(self.otpSkel())
        if not securitykey.validate(skey, useSessionKey=True):
            raise errors.PreconditionFailed()
        if token["failures"] > 3:
            raise errors.Forbidden(
                "Maximum amount of authentication retries exceeded")
        if len(token["otpkey"]) % 2 == 1:
            raise errors.PreconditionFailed(
                "The otp secret stored for this user is invalid (uneven length)"
            )
        validTokens = self.generateOtps(token["otpkey"], token["otptimedrift"])
        try:
            otptoken = int(otptoken)
        except:
            # We got a non-numeric token - this cant be correct
            self.userModule.render.edit(self.otpSkel(), tpl=self.otpTemplate)

        if otptoken in validTokens:
            userKey = currSess["_otp_user"]["uid"]

            del currSess["_otp_user"]
            currSess.markChanged()

            idx = validTokens.index(int(otptoken))

            if abs(idx - self.windowSize) > 2:
                # The time-drift accumulates to more than 2 minutes, update our
                # clock-drift value accordingly
                self.updateTimeDrift(userKey, idx - self.windowSize)

            return self.userModule.secondFactorSucceeded(self, userKey)
        else:
            token["failures"] += 1
            currSess["_otp_user"] = token
            currSess.markChanged()
            return self.userModule.render.edit(self.otpSkel(),
                                               loginFailed=True,
                                               tpl=self.otpTemplate)
Пример #9
0
    def edit(self, *args, **kwargs):
        """
		Modify an existing entry, and render the entry, eventually with error notes on incorrect data.
		Data is taken by any other arguments in *kwargs*.

		The entry is fetched by its entity key, which either is provided via *kwargs["key"]*,
		or as the first parameter in *args*. The function performs several access control checks
		on the requested entity before it is modified.

		.. seealso:: :func:`editSkel`, :func:`onItemEdited`, :func:`canEdit`

		:returns: The rendered, edited object of the entry, eventually with error hints.

		:raises: :exc:`server.errors.NotAcceptable`, when no *key* is provided.
		:raises: :exc:`server.errors.NotFound`, when no entry with the given *key* was found.
		:raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions.
		:raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified.
		"""
        if "skey" in kwargs:
            skey = kwargs["skey"]
        else:
            skey = ""

        if len(args) == 1:
            key = args[0]
        elif "key" in kwargs:
            key = kwargs["key"]
        else:
            raise errors.NotAcceptable()

        skel = self.editSkel()
        if not skel.fromDB(key):
            raise errors.NotAcceptable()

        if not self.canEdit(skel):
            raise errors.Unauthorized()

        if (len(kwargs) == 0  # no data supplied
                or skey == ""  # no security key
                or not request.current.get().
                isPostRequest  # failure if not using POST-method
                or not skel.fromClient(
                    kwargs)  # failure on reading into the bones
                or ("bounce" in kwargs and kwargs["bounce"] == "1"
                    )  # review before changing
            ):
            return self.render.edit(skel)

        if not securitykey.validate(skey, useSessionKey=True):
            raise errors.PreconditionFailed()

        skel.toDB()  # write it!
        self.onItemEdited(skel)
        self.onItemChanged(skel)

        return self.render.editItemSuccess(skel)
Пример #10
0
    def reparent(self, item, dest, skey, *args, **kwargs):
        """
		Moves an entry *item* (and everything beneath it) to another parent-node *dest*.

		.. seealso:: :func:`canReparent`

		:param item: URL-safe key of the item which will be moved.
		:type item: str
		:param dest: URL-safe key of the new parent for this item.
		:type dest: str

		:returns: A rendered success result generated by the default renderer.

		:raises: :exc:`server.errors.NotFound`, when no entry with the given *id* was found.
		:raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions.
		:raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified.
		"""
        if not securitykey.validate(skey, useSessionKey=True):
            raise errors.PreconditionFailed()

        if not self.canReparent(item, dest):
            raise errors.Unauthorized()

        if not self.isValidParent(dest) or item == dest:
            raise errors.NotAcceptable()

        ## Test for recursion
        isValid = False
        currLevel = db.Get(dest)

        for x in range(0, 99):
            if str(currLevel.key()) == item:
                break

            if currLevel.key().kind(
            ) == self.viewSkel().kindName + "_rootNode":
                # We reached a rootNode
                isValid = True
                break

            currLevel = db.Get(currLevel["parententry"])

        if not isValid:
            raise errors.NotAcceptable()

        ## Update entry
        fromItem = db.Get(item)
        fromItem["parententry"] = dest
        fromItem["parentrepo"] = str(self.getRootNode(dest).key())
        db.Put(fromItem)
        skel = self.editSkel()
        assert skel.fromDB(item)
        self.onItemReparent(skel)
        self.onItemChanged(skel)

        return self.render.reparentSuccess(obj=fromItem)
Пример #11
0
    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)
Пример #12
0
    def add(self, skelType, node, *args, **kwargs):
        """
		Add a new entry with the given parent *node*, and render the entry, eventually with error notes
		on incorrect data. Data is taken by any other arguments in *kwargs*.

		The function performs several access control checks on the requested entity before it is added.

		.. seealso:: :func:`canAdd`, :func:`onAdd`, , :func:`onAdded`

		:param skelType: Defines the type of the new entry and may either be "node" or "leaf".
		:type skelType: str
		:param node: URL-safe key of the parent.
		:type node: str

		:returns: The rendered, added object of the entry, eventually with error hints.

		:raises: :exc:`server.errors.NotAcceptable`, when no valid *skelType* was provided.
		:raises: :exc:`server.errors.NotFound`, when no valid *node* was found.
		:raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions.
		:raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified.
		"""
        if "skey" in kwargs:
            skey = kwargs["skey"]
        else:
            skey = ""
        if not self._checkSkelType(skelType):
            raise errors.NotAcceptable()
        skel = self.addSkel(skelType)
        parentNodeSkel = self.editSkel("node")
        if not parentNodeSkel.fromDB(node):
            raise errors.NotFound()
        if not self.canAdd(skelType, parentNodeSkel):
            raise errors.Unauthorized()
        if (len(kwargs) == 0  # no data supplied
                or skey == ""  # no security key
                or not skel.fromClient(
                    kwargs)  # failure on reading into the bones
                or not currentRequest.get().isPostRequest
                or ("bounce" in kwargs and kwargs["bounce"] == "1"
                    )  # review before adding
            ):
            return self.render.add(skel)
        if not securitykey.validate(skey, useSessionKey=True):
            raise errors.PreconditionFailed()
        skel["parententry"] = parentNodeSkel["key"]
        # parentrepo may not exist of parentNodeSkel as it may be an rootNode
        skel["parentrepo"] = parentNodeSkel["parentrepo"] or parentNodeSkel[
            "key"]
        self.onAdd(skel)
        skel.toDB()
        self.onAdded(skel)
        return self.render.addSuccess(skel)
Пример #13
0
    def clone(self,
              fromRepo,
              toRepo,
              fromParent=None,
              toParent=None,
              *args,
              **kwargs):
        """
		Clones a hierarchy recursively.

		This function only initiates the cloning process, which is performed in the background.
		It states only a successful result when the clone action has been correctly initiated.

		:param fromRepo: URL-safe key of the key to the repository (=root-node Key) to clone from.
		:type fromRepo: str
		:param toRepo: URL-safe key of the key to the repository (=root-node Key) to clone to.
		:type toRepo: str
		:param fromParent: URL-safe key of the parent to clone from; for root nodes, this is equal \
		 to fromRepo, and can be omitted.
		:type fromParent: str
		:param toParent: URL-safe key of the parent to clone to; for root nodes, this is equal to \
		toRepo, and can be omitted.
		:type toParent: str

		:returns: A rendered success result generated by the default renderer.

		:raises: :exc:`server.errors.NotAcceptable`, when no valid *parent* was found.
		:raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions.
		:raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified.
		"""
        if "skey" in kwargs:
            skey = kwargs["skey"]
        else:
            skey = ""

        if fromParent is None:
            fromParent = fromRepo
        if toParent is None:
            toParent = toRepo

        if not (self.isValidParent(fromParent)
                and self.isValidParent(toParent)):  # Ensure the parents exists
            raise errors.NotAcceptable()

        if not self.canAdd(toParent):
            raise errors.Unauthorized()
        if not securitykey.validate(skey, useSessionKey=True):
            raise errors.PreconditionFailed()

        self._clone(fromRepo, toRepo, fromParent, toParent)
        return self.render.cloneSuccess()
Пример #14
0
    def edit(self, skelType, key, *args, **kwargs):
        """
		Modify an existing entry, and render the entry, eventually with error notes on incorrect data.
		Data is taken by any other arguments in *kwargs*.

		The function performs several access control checks on the requested entity before it is added.

		.. seealso:: :func:`onAdded`, :func:`canEdit`

		:param skelType: Defines the type of the entry that should be modified and may either be "node" or "leaf".
		:type skelType: str
		:param key: URL-safe key of the item to be edited.
		:type key: str

		:returns: The rendered, modified object of the entry, eventually with error hints.

		:raises: :exc:`server.errors.NotAcceptable`, when no valid *skelType* was provided.
		:raises: :exc:`server.errors.NotFound`, when no valid *node* was found.
		:raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions.
		:raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified.
		"""
        if "skey" in kwargs:
            skey = kwargs["skey"]
        else:
            skey = ""
        if skelType == "node":
            skelType = TreeType.Node
        elif skelType == "leaf" and self.leafSkelCls:
            skelType = TreeType.Leaf
        else:
            raise errors.NotAcceptable()
        skel = self.addSkel(skelType)
        if not skel.fromDB(key):
            raise errors.NotFound()
        if not self.canEdit(skelType, skel):
            raise errors.Unauthorized()
        if (len(kwargs) == 0  # no data supplied
                or skey == ""  # no security key
                or not skel.fromClient(
                    kwargs)  # failure on reading into the bones
                or not currentRequest.get().isPostRequest
                or ("bounce" in kwargs and kwargs["bounce"] == "1"
                    )  # review before adding
            ):
            return self.render.edit(skel)
        if not securitykey.validate(skey, useSessionKey=True):
            raise errors.PreconditionFailed()
        skel.toDB()
        self.onEdited(skel)
        return self.render.editSuccess(skel)
Пример #15
0
    def markCanceled(self, key, skey, *args, **kwargs):
        """
		Exposed function for marking an order as cancelled.

		:param key: The key of the order to be marked.
		:type key: str
		"""
        if not self.canEdit(key):
            raise errors.Unauthorized()

        if not securitykey.validate(skey):
            raise errors.PreconditionFailed()

        self.setCanceled(key)
        return "OKAY"
Пример #16
0
    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)
Пример #17
0
    def edit(self, *args, **kwargs):
        """
		Modify the existing entry, and render the entry, eventually with error notes on incorrect data.

		The entry is fetched by its entity key, which either is provided via *kwargs["key"]*,
		or as the first parameter in *args*. The function performs several access control checks
		on the singleton's entity before it is modified.

		.. seealso:: :func:`editSkel`, :func:`onItemEdited`, :func:`canEdit`

		:returns: The rendered, edited object of the entry, eventually with error hints.

		:raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions.
		:raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified.
		"""

        if "skey" in kwargs:
            skey = kwargs["skey"]
        else:
            skey = ""

        skel = self.editSkel()

        if not self.canEdit():
            raise errors.Unauthorized()

        key = db.Key.from_path(self.editSkel().kindName, self.getKey())

        if not skel.fromDB(
                str(key)):  # Its not there yet; we need to set the key again
            skel["key"] = str(key)

        if (len(kwargs) == 0  # no data supplied
                or skey == ""  # no skey provided
                or not skel.fromClient(
                    kwargs)  # failure on reading into the bones
                or ("bounce" in kwargs
                    and kwargs["bounce"] == "1")):  # review before changing
            return self.render.edit(skel)

        if not securitykey.validate(skey, useSessionKey=True):
            raise errors.PreconditionFailed()

        skel.toDB()
        self.onItemEdited(skel)
        return self.render.editItemSuccess(skel)
Пример #18
0
    def add(self, parent, *args, **kwargs):
        """
		Add a new entry with the given parent, and render the entry, eventually with error notes on incorrect data.
		Data is taken by any other arguments in *kwargs*.

		The function performs several access control checks on the requested entity before it is added.

		.. seealso:: :func:`addSkel`, :func:`onItemAdded`, :func:`canAdd`

		:param parent: URL-safe key of the parent.
		:type parent: str

		:returns: The rendered, added object of the entry, eventually with error hints.

		:raises: :exc:`server.errors.NotAcceptable`, when no valid *parent* was found.
		:raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions.
		:raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified.
		"""
        if "skey" in kwargs:
            skey = kwargs["skey"]
        else:
            skey = ""

        if not self.isValidParent(parent):  # Ensure the parent exists
            raise errors.NotAcceptable()

        if not self.canAdd(parent):
            raise errors.Unauthorized()

        skel = self.addSkel()

        if (len(kwargs) == 0 or skey == ""
                or not request.current.get().isPostRequest
                or not skel.fromClient(kwargs)
                or ("bounce" in kwargs and kwargs["bounce"] == "1")):
            return self.render.add(skel)

        if not securitykey.validate(skey, useSessionKey=True):
            raise errors.PreconditionFailed()
        skel["parententry"] = str(parent)
        skel["parentrepo"] = str(self.getRootNode(parent).key())
        key = skel.toDB()
        self.onItemAdded(skel)
        self.onItemChanged(skel)
        return self.render.addItemSuccess(skel)
Пример #19
0
	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)
Пример #20
0
    def add(self, *args, **kwargs):
        """
			Allows guests to register a new account if self.registrationEnabled is set to true

			.. seealso:: :func:`addSkel`, :func:`onAdded`, :func:`canAdd`

			:returns: The rendered, added object of the entry, eventually with error hints.

			:raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions.
			:raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified.
		"""
        if "skey" in kwargs:
            skey = kwargs["skey"]
        else:
            skey = ""
        if not self.canAdd():
            raise errors.Unauthorized()
        skel = self.addSkel()
        if (len(kwargs) == 0  # no data supplied
                or skey == ""  # no skey supplied
                or not currentRequest.get().
                isPostRequest  # bail out if not using POST-method
                or not skel.fromClient(
                    kwargs)  # failure on reading into the bones
                or ("bounce" in kwargs
                    and kwargs["bounce"] == "1")):  # review before adding
            # render the skeleton in the version it could as far as it could be read.
            return self.userModule.render.add(skel)
        if not securitykey.validate(skey, useSessionKey=True):
            raise errors.PreconditionFailed()
        skel.toDB()
        if self.registrationEmailVerificationRequired and str(
                skel["status"]) == "1":
            # The user will have to verify his email-address. Create an skey and send it to his address
            skey = securitykey.create(duration=60 * 60 * 24 * 7,
                                      userKey=utils.normalizeKey(skel["key"]),
                                      name=skel["name"])
            skel.skey = baseBone(descr="Skey")
            skel["skey"] = skey
            email.sendEMail(dests=[skel["name"]],
                            tpl=self.userModule.verifyEmailAddressMail,
                            skel=skel)
        self.userModule.onAdded(skel)  # Call onAdded on our parent user module
        return self.userModule.render.addSuccess(skel)
Пример #21
0
    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)
Пример #22
0
    def preview(self, skey, *args, **kwargs):
        """
		Renders data for the entry, without reading it from the database.
		This function allows to preview the entry without writing it to the database.

		Any entity values are provided via *kwargs*.

		The function uses the viewTemplate of the application.

		:returns: The rendered representation of the supplied data.
		"""
        if not self.canPreview():
            raise errors.Unauthorized()

        if not securitykey.validate(skey, useSessionKey=True):
            raise errors.PreconditionFailed()

        skel = self.viewSkel()
        skel.fromClient(kwargs)

        return self.render.view(skel)
Пример #23
0
    def add(self, *args, **kwargs):
        """
			Add a new entry, and render the entry, eventually with error notes on incorrect data.
			Data is taken by any other arguments in *kwargs*.

			The function performs several access control checks on the requested entity before it is added.

			.. seealso:: :func:`addSkel`, :func:`onAdd`, :func:`onAdded`, :func:`canAdd`

			:returns: The rendered, added object of the entry, eventually with error hints.

			:raises: :exc:`viur.core.errors.Unauthorized`, if the current user does not have the required permissions.
			:raises: :exc:`viur.core.errors.PreconditionFailed`, if the *skey* could not be verified.
		"""
        if "skey" in kwargs:
            skey = kwargs["skey"]
        else:
            skey = ""
        if not self.canAdd():
            raise errors.Unauthorized()
        skel = self.addSkel()
        if (len(kwargs) == 0  # no data supplied
                or skey == ""  # no skey supplied
                or not currentRequest.get().
                isPostRequest  # failure if not using POST-method
                or not skel.fromClient(
                    kwargs)  # failure on reading into the bones
                or ("bounce" in kwargs and kwargs["bounce"] == "1"
                    )  # review before adding
            ):
            # render the skeleton in the version it could as far as it could be read.
            return self.render.add(skel)
        if not securitykey.validate(skey, useSessionKey=True):
            raise errors.PreconditionFailed()

        self.onAdd(skel)
        skel.toDB()
        self.onAdded(skel)

        return self.render.addSuccess(skel)
Пример #24
0
 def getUploadURL(self, *args, **kwargs):
     skey = kwargs.get("skey", "")
     node = kwargs.get("node")
     if node:
         rootNode = self.getRootNode(node)
         if not self.canAdd(TreeType.Leaf, rootNode):
             raise errors.Forbidden()
     else:
         if not self.canAdd(TreeType.Leaf, None):
             raise errors.Forbidden()
     if not securitykey.validate(skey, useSessionKey=True):
         raise errors.PreconditionFailed()
     targetKey, uploadUrl, policy = self.createUploadURL(node)
     resDict = {
         "url": uploadUrl,
         "params": {
             "key": "%s/source/file.dat" % targetKey,
         }
     }
     for key, value in policy.items():
         resDict["params"][key] = value
     return self.render.view(resDict)
Пример #25
0
 def execute(self, taskID, *args, **kwargs):
     """Queues a specific task for the next maintenance run"""
     global _callableTasks
     from viur.core import securitykey
     if taskID in _callableTasks:
         task = _callableTasks[taskID]()
     else:
         return
     if not task.canCall():
         raise errors.Unauthorized()
     skel = task.dataSkel()
     if "skey" in kwargs:
         skey = kwargs["skey"]
     else:
         skey = ""
     if len(kwargs) == 0 or skey == "" or not skel.fromClient(kwargs) or (
             "bounce" in kwargs and kwargs["bounce"] == "1"):
         return self.render.add(skel)
     if not securitykey.validate(skey, useSessionKey=True):
         raise errors.PreconditionFailed()
     task.execute(**skel.accessedValues)
     return self.render.addSuccess(skel)
Пример #26
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
Пример #27
0
    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
Пример #28
0
    def checkout(self, step=None, key=None, skey=None, *args, **kwargs):
        """
		Performs the checkout process trough the state machine provided by self.steps.

		:param step: The current step index, None for beginning a new checkout
		:param key: Key of the current checkout
		:param skey: Server security key

		:return: Returns the rendered template or throws redirection exceptions.
		"""
        myKindName = self.viewSkel().kindName

        if step is None:
            logging.info("Starting new checkout process")
            billObj = db.Entity(myKindName)
            billObj["idx"] = "0000000"
            for state in self.states:
                billObj["state_%s" % state] = "0"
            db.Put(billObj)
            key = str(billObj.key())

            # Copy the Cart
            if "amountSkel" in dir(self):
                cart = session.current.get("cart_products") or {}
                s = self.amountSkel()
                products = []
                for prod, atts in cart.items():
                    for i in range(0, atts["amount"]):
                        products.append(str(prod))

                s.fromClient({"product": products})
                s.toDB()

            session.current["order_" + myKindName] = {
                "key": str(key),
                "completedSteps": []
            }
            session.current.markChanged()

            raise errors.Redirect("?step=0&key=%s" % str(key))

        elif key:
            try:
                orderKey = db.Key(key)
                step = int(step)
                assert (step >= 0)
                assert (step < len(self.steps))
            except:
                raise errors.NotAcceptable()

            sessionInfo = session.current.get("order_" + myKindName)

            if not sessionInfo or not sessionInfo.get("key") == str(orderKey):
                raise errors.Unauthorized()

            if step in sessionInfo["completedSteps"]:
                session.current["order_" + myKindName]["completedSteps"] = [
                    x for x in sessionInfo["completedSteps"] if x < step
                ]
                session.current.markChanged()

            # Make sure that no steps can be skipped
            if step != 0 and not step - 1 in sessionInfo["completedSteps"]:
                raise errors.Redirect("?step=0&key=%s" % str(str(orderKey)))

            currentStep = self.steps[step]

            if "preHandler" in currentStep.keys():
                try:
                    if isinstance(currentStep["preHandler"], list):
                        for handler in currentStep["preHandler"]:
                            handler(self, step, str(orderKey), *args, **kwargs)
                    else:
                        currentStep["preHandler"](self,
                                                  step,
                                                  str(orderKey),
                                                  refkwargs=kwargs,
                                                  *args,
                                                  **kwargs)

                except SkipStepException:
                    session.current["order_" +
                                    myKindName]["completedSteps"].append(step)
                    session.current.markChanged()
                    raise errors.Redirect("?step=%s&key=%s" %
                                          (str(step + 1), str(orderKey)))
                except ReturnHtmlException as e:
                    return (e.html)

            if "requiresSecurityKey" in currentStep and currentStep[
                    "requiresSecurityKey"]:
                if not securitykey.validate(skey):
                    raise errors.PreconditionFailed()
                pass

            if "mainHandler" in currentStep:

                if currentStep["mainHandler"]["action"] == "edit":
                    skel = self.getSkelByName(
                        currentStep["mainHandler"]["skeleton"], str(orderKey))
                    skel.fromDB(str(orderKey))

                    if not len(kwargs.keys()) or not skel.fromClient(kwargs):
                        return (self.render.edit(
                            skel,
                            tpl=currentStep["mainHandler"]["template"],
                            step=step))

                    skel.toDB()

                if currentStep["mainHandler"]["action"] == "view":
                    if not "complete" in kwargs or not kwargs[
                            "complete"] == u"1":
                        skel = self.getSkelByName(
                            currentStep["mainHandler"]["skeleton"],
                            str(orderKey))
                        skel.fromDB(str(orderKey))
                        return (self.render.view(
                            skel,
                            tpl=currentStep["mainHandler"]["template"],
                            step=step))

                elif currentStep["mainHandler"]["action"] == "function":
                    res = currentStep["mainHandler"]["function"](self, step,
                                                                 str(orderKey),
                                                                 *args,
                                                                 **kwargs)
                    if res:
                        return res

            if "postHandler" in currentStep:
                currentStep["postHandler"](self, step, str(orderKey), *args,
                                           **kwargs)

            session.current["order_" +
                            myKindName]["completedSteps"].append(step)
            session.current.markChanged()

            logging.info("next ?step=%s&key=%s" %
                         (str(step + 1), str(orderKey)))
            raise errors.Redirect("?step=%s&key=%s" %
                                  (str(step + 1), str(orderKey)))
Пример #29
0
        skel["parententry"] = parentNodeSkel["key"]
        # parentrepo may not exist in parentNodeSkel as it may be an rootNode
        skel["parentrepo"] = parentNodeSkel["parentrepo"] or parentNodeSkel[
            "key"]

        if (len(kwargs) == 0  # no data supplied
                or skey == ""  # no security key
                or not skel.fromClient(
                    kwargs)  # failure on reading into the bones
                or not currentRequest.get().isPostRequest
                or ("bounce" in kwargs and kwargs["bounce"] == "1"
                    )  # review before adding
            ):
            return self.render.add(skel)
        if not securitykey.validate(skey, useSessionKey=True):
            raise errors.PreconditionFailed()

        self.onAdd(skelType, skel)
        skel.toDB()
        self.onAdded(skelType, skel)
        return self.render.addSuccess(skel)

    @exposed
    @forceSSL
    def edit(self, skelType, key, *args, **kwargs):
        """
		Modify an existing entry, and render the entry, eventually with error notes on incorrect data.
		Data is taken by any other arguments in *kwargs*.

		The function performs several access control checks on the requested entity before it is added.
Пример #30
0
    def pwrecover(self, *args, **kwargs):
        """
			This implements the password recovery process which let them set a new password for their account
			after validating a code send to them by email. The process is as following:

			- The user enters his email adress
			- We'll generate a random code, store it in his session and call sendUserPasswordRecoveryCode
			- sendUserPasswordRecoveryCode will run in the background, check if we have a user with that name
			  and send the code. It runs as a deferredTask so we don't leak the information if a user account exists.
			- If the user received his code, he can paste the code and set a new password for his account.

			To prevent automated attacks, the fist step is guarded by a captcha and we limited calls to this function
			to 10 actions per 15 minutes. (One complete recovery process consists of two calls).
		"""
        if not self.passwordRecoveryRateLimit.isQuotaAvailable():
            raise errors.Forbidden()  # Quota exhausted, bail out
        session = currentSession.get()
        request = currentRequest.get()
        recoverStep = session.get("user.auth_userpassword.pwrecover")
        if not recoverStep:
            # This is the first step, where we ask for the username of the account we'll going to reset the password on
            skel = self.lostPasswordStep1Skel()
            if not request.isPostRequest or not skel.fromClient(kwargs):
                return self.userModule.render.edit(
                    skel, self.passwordRecoveryStep1Template)
            if not securitykey.validate(kwargs.get("skey"),
                                        useSessionKey=True):
                raise errors.PreconditionFailed()
            self.passwordRecoveryRateLimit.decrementQuota()
            recoveryKey = utils.generateRandomString(
                13)  # This is the key the user will have to Copy&Paste
            self.sendUserPasswordRecoveryCode(
                skel["name"].lower(),
                recoveryKey)  # Send the code in the background
            session["user.auth_userpassword.pwrecover"] = {
                "name": skel["name"].lower(),
                "recoveryKey": recoveryKey,
                "creationdate": utcNow(),
                "errorCount": 0
            }
            del recoveryKey
            return self.pwrecover(
            )  # Fall through to the second step as that key in the session is now set
        else:
            if request.isPostRequest and kwargs.get("abort") == "1" \
             and securitykey.validate(kwargs.get("skey"), useSessionKey=True):
                # Allow a user to abort the process if a wrong email has been used
                session["user.auth_userpassword.pwrecover"] = None
                return self.pwrecover()
            # We're in the second step - the code has been send and is waiting for confirmation from the user
            if utcNow() - session["user.auth_userpassword.pwrecover"][
                    "creationdate"] > datetime.timedelta(minutes=15):
                # This recovery-process is expired; reset the session and start over
                session["user.auth_userpassword.pwrecover"] = None
                return self.userModule.render.view(
                    skel=None,
                    tpl=self.passwordRecoveryFailedTemplate,
                    reason=self.passwordRecoveryKeyExpired)
            skel = self.lostPasswordStep2Skel()
            if not skel.fromClient(kwargs) or not request.isPostRequest:
                return self.userModule.render.edit(
                    skel, self.passwordRecoveryStep2Template)
            if not securitykey.validate(kwargs.get("skey"),
                                        useSessionKey=True):
                raise errors.PreconditionFailed()
            self.passwordRecoveryRateLimit.decrementQuota()
            if not hmac.compare_digest(
                    session["user.auth_userpassword.pwrecover"]["recoveryKey"],
                    skel["recoveryKey"]):
                # The key was invalid, increase error-count or abort this recovery process altogether
                session["user.auth_userpassword.pwrecover"]["errorCount"] += 1
                if session["user.auth_userpassword.pwrecover"][
                        "errorCount"] > 3:
                    session["user.auth_userpassword.pwrecover"] = None
                    return self.userModule.render.view(
                        skel=None,
                        tpl=self.passwordRecoveryFailedTemplate,
                        reason=self.passwordRecoveryKeyInvalid)
                return self.userModule.render.edit(
                    skel,
                    self.passwordRecoveryStep2Template)  # Let's try again
            # If we made it here, the key was correct, so we'd hopefully have a valid user for this
            uSkel = userSkel().all().filter(
                "name.idx =",
                session["user.auth_userpassword.pwrecover"]["name"]).getSkel()
            if not uSkel:  # This *should* never happen - if we don't have a matching account we'll not send the key.
                session["user.auth_userpassword.pwrecover"] = None
                return self.userModule.render.view(
                    skel=None,
                    tpl=self.passwordRecoveryFailedTemplate,
                    reason=self.passwordRecoveryUserNotFound)
            if uSkel["status"] != 10:  # The account is locked or not yet validated. Abort the process
                session["user.auth_userpassword.pwrecover"] = None
                return self.userModule.render.view(
                    skel=None,
                    tpl=self.passwordRecoveryFailedTemplate,
                    reason=self.passwordRecoveryAccountLocked)
            # Update the password, save the user, reset his session and show the success-template
            uSkel["password"] = skel["password"]
            uSkel.toDB()
            session["user.auth_userpassword.pwrecover"] = None
            return self.userModule.render.view(
                None, self.passwordRecoverySuccessTemplate)