示例#1
0
	def fromClient(self, skel: 'SkeletonInstance', name: str, data: dict) -> Union[None, List[ReadFromClientError]]:
		"""
			Reads a value from the client.
			If this value is valid for this bone,
			store this value and return None.
			Otherwise our previous value is
			left unchanged and an error-message
			is returned.

			:param name: Our name in the skeleton
			:type name: str
			:param data: *User-supplied* request-data
			:type data: dict
			:returns: None or String
		"""
		if currentRequest.get().isDevServer:  # We dont enforce captchas on dev server
			return None
		user = utils.getCurrentUser()
		if user and "root" in user["access"]:
			return None  # Don't bother trusted users with this (not supported by admin/vi anyways)
		if not "g-recaptcha-response" in data:
			return [ReadFromClientError(ReadFromClientErrorSeverity.NotSet, "No Captcha given!")]
		data = {
			"secret": self.privateKey,
			"remoteip": currentRequest.get().request.remote_addr,
			"response": data["g-recaptcha-response"]
		}
		req = urllib.request.Request(url="https://www.google.com/recaptcha/api/siteverify",
									 data=urllib.parse.urlencode(data).encode(),
									 method="POST")
		response = urllib.request.urlopen(req)
		if json.loads(response.read()).get("success"):
			return None
		return [ReadFromClientError(ReadFromClientErrorSeverity.Invalid, "Invalid Captcha")]
示例#2
0
def index(*args, **kwargs):
    if currentRequest.get().isDevServer or currentRequest.get(
    ).isSSLConnection:
        raise errors.Redirect("/vi/s/main.html")
    else:
        appVersion = app_identity.get_default_version_hostname()
        raise errors.Redirect("https://%s/vi/s/main.html" % appVersion)
示例#3
0
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)
示例#4
0
    def _getEndpointKey(self):
        """
		:warning:
			It's invalid to call _getEndpointKey if method is set to user and there's no user logged in!

		:return: the key associated with the current endpoint (it's IP or the key of the current user)
		"""
        if self.useUser:
            user = utils.getCurrentUser()
            assert user, "Cannot decrement usage from guest!"
            return user["key"]
        else:
            remoteAddr = currentRequest.get().request.remote_addr
            if "::" in remoteAddr:  # IPv6 in shorted form
                remoteAddr = remoteAddr.split(":")
                blankIndex = remoteAddr.index("")
                missigParts = ["0000"] * (8 - len(remoteAddr))
                remoteAddr = remoteAddr[:blankIndex] + missigParts + remoteAddr[
                    blankIndex + 1:]
                return ":".join(remoteAddr[:4])
            elif ":" in remoteAddr:  # It's IPv6, so we remove the last 64 bits (interface id)
                # as it is easily controlled by the user
                return ":".join(remoteAddr.split(":")[:4])
            else:  # It's IPv4, simply return that address
                return remoteAddr
示例#5
0
    def _requeueStep(cls, qryDict: Dict[str, Any]) -> None:
        """
			Internal use only. Pushes a new step defined in qryDict to either the taskqueue or append it to
			the current request	if we are on the local development server.
		"""
        if not queueRegion:  # Run tasks inline - hopefully development server
            req = currentRequest.get()
            task = lambda *args, **kwargs: cls._qryStep(qryDict)
            if req:
                req.pendingTasks.append(
                    task
                )  # < This property will be only exist on development server!
                return
        project = utils.projectID
        location = queueRegion
        parent = taskClient.queue_path(project, location, cls.queueName)
        task = {
            'app_engine_http_request': {  # Specify the type of request.
                'http_method': 'POST',
                'relative_uri': '/_tasks/queryIter'
            }
        }
        task['app_engine_http_request']['body'] = json.dumps(
            preprocessJsonObject(qryDict)).encode("UTF-8")
        taskClient.create_task(parent=parent, task=task)
示例#6
0
    def authenticateUser(self, userKey, **kwargs):
        """
			Performs Log-In for the current session and the given userKey.

			This resets the current session: All fields not explicitly marked as persistent
			by conf["viur.session.persistentFieldsOnLogin"] are gone afterwards.

			:param authProvider: Which authentication-provider issued the authenticateUser request
			:type authProvider: object
			:param userKey: The (DB-)Key of the user we shall authenticate
			:type userKey: db.Key
		"""
        currSess = currentSession.get()
        res = db.Get(userKey)
        assert res, "Unable to authenticate unknown user %s" % userKey
        oldSession = {k: v
                      for k, v in currSess.items()
                      }  # Store all items in the current session
        currSess.reset()
        # Copy the persistent fields over
        for k in conf["viur.session.persistentFieldsOnLogin"]:
            if k in oldSession:
                currSess[k] = oldSession[k]
        del oldSession
        currSess["user"] = res
        currSess.markChanged()
        currentRequest.get().response.headers[
            "Sec-X-ViUR-StaticSKey"] = currSess.staticSecurityKey
        self.onLogin()
        return self.render.loginSucceeded(**kwargs)
示例#7
0
    def guessTimeZone(self):
        """
		Guess the timezone the user is supposed to be in.
		If it cant be guessed, a safe default (UTC) is used
		"""
        timeZone = pytz.utc  # Default fallback
        currReqData = currentRequestData.get()
        if isLocalDevelopmentServer:
            return tzlocal.get_localzone()
        try:
            # Check the local cache first
            if "timeZone" in currReqData:
                return currReqData["timeZone"]
            headers = currentRequest.get().request.headers
            if "X-Appengine-Country" in headers:
                country = headers["X-Appengine-Country"]
            else:  # Maybe local development Server - no way to guess it here
                return timeZone
            tzList = pytz.country_timezones[country]
        except:  # Non-User generated request (deferred call; task queue etc), or no pytz
            return timeZone
        if len(tzList) == 1:  # Fine - the country has exactly one timezone
            timeZone = pytz.timezone(tzList[0])
        elif country.lower() == "us":  # Fallback for the US
            timeZone = pytz.timezone("EST")
        elif country.lower(
        ) == "de":  # For some freaking reason Germany is listed with two timezones
            timeZone = pytz.timezone("Europe/Berlin")
        elif country.lower() == "au":
            timeZone = pytz.timezone(
                "Australia/Canberra")  # Equivalent to NSW/Sydney :)
        else:  # The user is in a Country which has more than one timezone
            pass
        currReqData["timeZone"] = timeZone  # Cache the result
        return timeZone
示例#8
0
def validate(key: str, useSessionKey: bool) -> Union[bool, db.Entity]:
    """
		Validates a security key. If useSessionKey is true, the key is expected to be the sessions current security key
		(or it's static security key). Otherwise it must be a key created with a duration (so it's not session
		dependent)

		:param key: The key to validate
		:param useSessionKey: If True, we validate against the session's skey, otherwise we'll lookup an unbound key
		:returns: False if the key was not valid for whatever reasons, the data (given during createSecurityKey) as
			dictionary or True if the dict is empty (or :param:useSessionKey was true).
	"""
    if useSessionKey:
        if key == "staticSessionKey":
            skeyHeaderValue = currentRequest.get().request.headers.get(
                "Sec-X-ViUR-StaticSKey")
            if skeyHeaderValue and currentSession.get(
            ).validateStaticSecurityKey(skeyHeaderValue):
                return True
        elif currentSession.get().validateSecurityKey(key):
            return True
        return False
    if not key:
        return False
    dbKey = db.Key(securityKeyKindName, key)
    dbObj = db.Get(dbKey)
    if dbObj:
        db.Delete(dbKey)
        until = dbObj["until"]
        if until < utcNow():  # This key has expired
            return False
        del dbObj["until"]
        if not dbObj:
            return True
        return dbObj
    return False
示例#9
0
 def renderEntry(self, skel, actionName, params=None):
     if isinstance(skel, list):
         vals = [self.renderSkelValues(x) for x in skel]
         struct = self.renderSkelStructure(skel[0])
         errors = None
     elif isinstance(skel, SkeletonInstance):
         vals = self.renderSkelValues(skel)
         struct = self.renderSkelStructure(skel)
         errors = [{
             "severity": x.severity.value,
             "fieldPath": x.fieldPath,
             "errorMessage": x.errorMessage,
             "invalidatedFields": x.invalidatedFields
         } for x in skel.errors]
     else:  # Hopefully we can pass it directly...
         vals = skel
         struct = None
         errors = None
     res = {
         "values": vals,
         "structure": struct,
         "errors": errors,
         "action": actionName,
         "params": params
     }
     currentRequest.get(
     ).response.headers["Content-Type"] = "application/json"
     return json.dumps(res, cls=CustomJsonEncoder)
示例#10
0
    def getUploads(self, field_name=None):
        """
			Get uploads sent to this handler.
			Cheeky borrowed from blobstore_handlers.py - © 2007 Google Inc.

			Args:
				field_name: Only select uploads that were sent as a specific field.

			Returns:
				A list of BlobInfo records corresponding to each upload.
				Empty list if there are no blob-info records for field_name.

		"""
        uploads = collections.defaultdict(list)
        for key, value in currentRequest.get().request.params.items():
            if isinstance(value, cgi.FieldStorage):
                if 'blob-key' in value.type_options:
                    uploads[key].append(blobstore.parse_blob_info(value))
        if field_name:
            return list(uploads.get(field_name, []))
        else:
            results = []
            for uploads in uploads.itervalues():
                results.extend(uploads)
            return results
示例#11
0
def dumpConfig(adminTree):
    adminConfig = {}
    for key in dir(adminTree):
        app = getattr(adminTree, key)
        if "adminInfo" in dir(app) and app.adminInfo:
            if callable(app.adminInfo):
                info = app.adminInfo()
                if info is not None:
                    adminConfig[key] = info
            else:
                adminConfig[key] = app.adminInfo.copy()
                adminConfig[key]["name"] = str(adminConfig[key]["name"])
                adminConfig[key]["views"] = []
                if "views" in app.adminInfo:
                    for v in app.adminInfo["views"]:
                        tmp = v.copy()
                        tmp["name"] = str(tmp["name"])
                        adminConfig[key]["views"].append(tmp)
    res = {
        "capabilities": conf["viur.capabilities"],
        "modules": adminConfig,
        "configuration": {}
    }
    for k, v in conf.items():
        if k.lower().startswith("admin."):
            res["configuration"][k[6:]] = v

    currentRequest.get().response.headers["Content-Type"] = "application/json"
    return json.dumps(res)
示例#12
0
    def index(self, *args, **kwargs):
        """
			Default, SEO-Friendly fallback for view and list.

			:param args: The first argument - if provided - is interpreted as seoKey.
			:param kwargs: Used for the fallback list.
			:return: The rendered entity or list.
		"""
        if args and args[0]:
            # We probably have a Database or SEO-Key here
            seoKey = str(args[0]).lower()
            skel = self.viewSkel().all(_excludeFromAccessLog=True).filter(
                "viur.viurActiveSeoKeys =", seoKey).getSkel()
            if skel:
                db.currentDbAccessLog.get(set()).add(skel["key"])
                if not self.canView(skel):
                    raise errors.Forbidden()
                seoUrl = utils.seoUrlToEntry(self.moduleName, skel)
                # Check whether this is the current seo-key, otherwise redirect to it
                if currentRequest.get().request.path != seoUrl:
                    raise errors.Redirect(seoUrl, status=301)
                self.onView(skel)
                return self.render.view(skel)
        # This was unsuccessfully, we'll render a list instead
        if not kwargs:
            kwargs = self.getDefaultListParams()
        return self.list(kwargs)
示例#13
0
def validate(key: str, useSessionKey: bool) -> Union[bool, db.Entity]:
    """
		Validates a onetime securitykey

		:type key: str
		:param key: The key to validate
		:type useSessionKey: Bool
		:param useSessionKey: If True, we validate against the session's skey, otherwise we'll lookup an unbound key
		:returns: False if the key was not valid for whatever reasons, the data (given during createSecurityKey) as dictionary or True if the dict is empty.
	"""
    if useSessionKey:
        if key == "staticSessionKey":
            skeyHeaderValue = currentRequest.get().request.headers.get(
                "Sec-X-ViUR-StaticSKey")
            if skeyHeaderValue and currentSession.get(
            ).validateStaticSecurityKey(skeyHeaderValue):
                return True
        elif currentSession.get().validateSecurityKey(key):
            return True
        return False
    if not key:
        return False
    dbKey = db.Key(securityKeyKindName, key)
    dbObj = db.Get(dbKey)
    if dbObj:
        db.Delete(dbKey)
        if dbObj["until"] < datetime.now():  # This key has expired
            return False
        del dbObj["until"]
        if not dbObj:
            return True
        return dbObj
    return False
示例#14
0
def canAccess(*args, **kwargs):
	user = utils.getCurrentUser()
	if user and ("root" in user["access"] or "admin" in user["access"]):
		return True

	pathList = currentRequest.get().pathlist

	if len(pathList) >= 2 and pathList[1] in ["skey", "getVersion"]:
		# Give the user the chance to login :)
		return True

	if (len(pathList) >= 3
		and pathList[1] == "user"
		and (pathList[2].startswith("auth_")
			 or pathList[2].startswith("f2_")
			 or pathList[2] == "getAuthMethods"
			 or pathList[2] == "logout")):
		# Give the user the chance to login :)
		return True

	if (len(pathList) >= 4
		and pathList[1] == "user"
		and pathList[2] == "view"
		and pathList[3] == "self"):
		# Give the user the chance to view himself.
		return True

	return False
示例#15
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:`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)
示例#16
0
    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)
示例#17
0
def getHostUrl(render, forceSSL=False, *args, **kwargs):
	"""
	Jinja2 global: Retrieve hostname with protocol.

	:returns: Returns the hostname, including the currently used protocol, e.g: http://www.example.com
	:rtype: str
	"""
	url = currentRequest.get().request.url
	url = url[:url.find("/", url.find("://") + 5)]
	if forceSSL and url.startswith("http://"):
		url = "https://" + url[7:]
	return url
示例#18
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:`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)
示例#19
0
def requestParams(render):
	"""
	Jinja2 global: Allows for accessing the request-parameters from the template.

	These returned values are escaped, as users tend to use these in an unsafe manner.

	:returns: dict of parameter and values.
	:rtype: dict
	"""
	res = {}
	for k, v in currentRequest.get().kwargs.items():
		res[utils.escapeString(k)] = utils.escapeString(v)
	return res
示例#20
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"])
示例#21
0
 def __str__(self):
     if self.translationCache is None:
         global systemTranslations
         self.translationCache = systemTranslations.get(self.key, {})
     try:
         lang = currentRequest.get().language
     except:
         return self.defaultText or self.key
     if lang in conf["viur.languageAliasMap"]:
         lang = conf["viur.languageAliasMap"][lang]
     if not lang in self.translationCache:
         return self.defaultText or self.key
     trStr = self.translationCache.get(lang, "")
     return trStr
示例#22
0
    def emit(self, record):
        message = super(ViURDefaultLogger, self).format(record)
        try:
            currentReq = currentRequest.get()
            TRACE = "projects/{}/traces/{}".format(client.project,
                                                   currentReq._traceID)
            currentReq.maxLogLevel = max(currentReq.maxLogLevel,
                                         record.levelno)
        except:
            TRACE = None

        self.transport.send(record,
                            message,
                            resource=self.resource,
                            labels=self.labels,
                            trace=TRACE)
示例#23
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:`canEdit`, :func:`onEdit`, :func:`onEdited`

		: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 not self._checkSkelType(skelType):
            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()
        self.onEdit(skel)
        skel.toDB()
        self.onEdited(skel)
        return self.render.editSuccess(skel)
示例#24
0
 def wrapF(self, *args, **kwargs) -> Union[str, bytes]:
     currReq = currentRequest.get()
     if conf["viur.disableCache"] or currReq.disableCache:
         # Caching disabled
         if conf["viur.disableCache"]:
             logging.debug("Caching is disabled by config")
         return f(self, *args, **kwargs)
     # How many arguments are part of the way to the function called (and how many are just *args)
     offset = -len(currReq.args) or len(currReq.pathlist)
     path = "/" + "/".join(currReq.pathlist[:offset])
     if not path in urls:
         # This path (possibly a sub-render) should not be cached
         logging.debug("Not caching for %s" % path)
         return f(self, *args, **kwargs)
     key = keyFromArgs(f, userSensitive, languageSensitive, evaluatedArgs,
                       path, args, kwargs)
     if not key:
         # Something is wrong (possibly the parameter-count)
         # Let's call f, but we knew already that this will clash
         return f(self, *args, **kwargs)
     dbRes = db.Get(db.Key(viurCacheName, key))
     if dbRes is not None:
         if not maxCacheTime \
          or dbRes["creationtime"] > utils.utcNow() - timedelta(seconds=maxCacheTime):
             # We store it unlimited or the cache is fresh enough
             logging.debug("This request was served from cache.")
             currReq.response.headers['Content-Type'] = dbRes[
                 "content-type"]
             return dbRes["data"]
     # If we made it this far, the request wasn't cached or too old; we need to rebuild it
     oldAccessLog = db.startDataAccessLog()
     try:
         res = f(self, *args, **kwargs)
     finally:
         accessedEntries = db.endDataAccessLog(oldAccessLog)
     dbEntity = db.Entity(db.Key(viurCacheName, key))
     dbEntity["data"] = res
     dbEntity["creationtime"] = utils.utcNow()
     dbEntity["path"] = path
     dbEntity["content-type"] = currReq.response.headers['Content-Type']
     dbEntity["accessedEntries"] = list(accessedEntries)
     dbEntity.exclude_from_indexes = ["data", "content-type"
                                      ]  # We can save 2 DB-Writs :)
     db.Put(dbEntity)
     logging.debug("This request was a cache-miss. Cache has been updated.")
     return res
示例#25
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)
示例#26
0
    def edit(self, skel, tpl=None, params=None, **kwargs):
        """
			Renders a page for modifying an entry.

			The template must construct the HTML-form on itself; the required information
			are passed via skel.structure, skel.value and skel.errors.

			A jinja2-macro, which builds such kind of forms, is shipped with the server.

			Any data in \*\*kwargs is passed unmodified to the template.

			:param skel: Skeleton of the entry which should be modified.
			:type skel: server.db.skeleton.Skeleton

			:param tpl: Name of a different template, which should be used instead of the default one.
			:type tpl: str

			:param params: Optional data that will be passed unmodified to the template
			:type params: object

			:return: Returns the emitted HTML response.
			:rtype: str
		"""
        if not tpl and "editTemplate" in dir(self.parent):
            tpl = self.parent.editTemplate

        tpl = tpl or self.editTemplate
        template = self.getEnv().get_template(self.getTemplateFileName(tpl))
        skeybone = baseBone(descr="SecurityKey", readOnly=True, visible=False)
        skel.skey = skeybone
        skel["skey"] = securitykey.create()

        if currentRequest.get().kwargs.get("nomissing") == "1":
            if isinstance(skel, SkeletonInstance):
                super(SkeletonInstance, skel).__setattr__("errors", [])
        skel.renderPreparation = self.renderBoneValue
        return template.render(skel={
            "structure": self.renderSkelStructure(skel),
            "errors": skel.errors,
            "value": skel
        },
                               params=params,
                               **kwargs)
示例#27
0
	def list(self, skellist, action="list", params=None, **kwargs):
		res = {}
		skels = []

		if skellist:
			for skel in skellist:
				skels.append(self.renderSkelValues(skel))

			res["cursor"] = skellist.getCursor()
			res["structure"] = self.renderSkelStructure(skellist.baseSkel)
		else:
			res["structure"] = None
			res["cursor"] = None

		res["skellist"] = skels
		res["action"] = action
		res["params"] = params
		currentRequest.get().response.headers["Content-Type"] = "application/json"
		return json.dumps(res, cls=CustomJsonEncoder)
示例#28
0
def extendCsp(additionalRules: dict = None,
              overrideRules: dict = None) -> None:
    """
		Adds additional csp rules to the current request. ViUR will emit a default csp-header based on the
		project-wide config. For some requests, it's needed to extend or override these rules without having to include
		them in the project config. Each dictionary must be in the same format as the
		conf["viur.security.contentSecurityPolicy"]. Values in additionalRules will extend the project-specific
		configuration, while overrideRules will replace them.

		..Note: This function will only work on CSP-Rules in "enforce" mode, "monitor" is not suppored

		:param additionalRules: Dictionary with additional csp-rules to emit
		:param overrideRules: Values in this dictionary will override the corresponding default rule
	"""
    assert additionalRules or overrideRules, "Either additionalRules or overrideRules must be given!"
    tmpDict = {}  # Copy the project-wide config in
    if conf["viur.security.contentSecurityPolicy"].get("enforce"):
        tmpDict.update(conf["viur.security.contentSecurityPolicy"]["enforce"])
    if overrideRules:  # Merge overrideRules
        for k, v in overrideRules.items():
            if v is None and k in tmpDict:
                del tmpDict[k]
            else:
                tmpDict[k] = v
    if additionalRules:  # Merge the extension dict
        for k, v in additionalRules.items():
            if k not in tmpDict:
                tmpDict[k] = []
            tmpDict[k].extend(v)
    resStr = ""  # Rebuild the CSP-Header
    for key, values in tmpDict.items():
        resStr += key
        for value in values:
            resStr += " "
            if value in {"self", "unsafe-inline", "unsafe-eval", "script", "none"} or \
             any([value.startswith(x) for x in ["nonce-", "sha256-", "sha384-", "sha512-"]]):
                resStr += "'%s'" % value
            else:
                resStr += value
        resStr += "; "
    currentRequest.get().response.headers["Content-Security-Policy"] = resStr
示例#29
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)
示例#30
0
    def queryIter(self, *args, **kwargs):
        """
			This processes one chunk of a queryIter (see below).
		"""
        global _deferedTasks, _appengineServiceIPs
        req = currentRequest.get().request
        if 'X-AppEngine-TaskName' not in req.headers:
            logging.critical(
                'Detected an attempted XSRF attack. The header "X-AppEngine-Taskname" was not set.'
            )
            raise errors.Forbidden()
        if req.environ.get(
                "HTTP_X_APPENGINE_USER_IP") not in _appengineServiceIPs:
            logging.critical(
                'Detected an attempted XSRF attack. This request did not originate from Task Queue.'
            )
            raise errors.Forbidden()
        data = json.loads(req.body, object_hook=jsonDecodeObjectHook)
        if data["classID"] not in MetaQueryIter._classCache:
            logging.error(
                "Could not continue queryIter - %s not known on this instance"
                % data["classID"])
        MetaQueryIter._classCache[data["classID"]]._qryStep(data)