def fromClient(self, skel: 'SkeletonInstance', name: str, data: dict) -> Union[None, List[ReadFromClientError]]: """ Reads a value from the client. If this value is valid for this bone, store this value and return None. Otherwise our previous value is left unchanged and an error-message is returned. :param name: Our name in the skeleton :type name: str :param data: *User-supplied* request-data :type data: dict :returns: None or String """ if currentRequest.get().isDevServer: # We dont enforce captchas on dev server return None user = utils.getCurrentUser() if user and "root" in user["access"]: return None # Don't bother trusted users with this (not supported by admin/vi anyways) if not "g-recaptcha-response" in data: return [ReadFromClientError(ReadFromClientErrorSeverity.NotSet, "No Captcha given!")] data = { "secret": self.privateKey, "remoteip": currentRequest.get().request.remote_addr, "response": data["g-recaptcha-response"] } req = urllib.request.Request(url="https://www.google.com/recaptcha/api/siteverify", data=urllib.parse.urlencode(data).encode(), method="POST") response = urllib.request.urlopen(req) if json.loads(response.read()).get("success"): return None return [ReadFromClientError(ReadFromClientErrorSeverity.Invalid, "Invalid Captcha")]
def 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)
def index(*args, **kwargs): from viur.core.render import isAdminAvailable, isViAvailable if not isAdminAvailable(): if isViAvailable(): # The admin is not available, the Vi however is, so redirect there raise errors.Redirect("/vi") raise errors.NotFound() if currentRequest.get().isDevServer or currentRequest.get().isSSLConnection: raise errors.Redirect("/admin/s/admin.html") else: appVersion = currentRequest.get().request.host raise errors.Redirect("https://%s/admin/s/admin.html" % appVersion)
def _getEndpointKey(self): """ :warning: It's invalid to call _getEndpointKey if method is set to user and there's no user logged in! :return: the key associated with the current endpoint (it's IP or the key of the current user) """ if self.useUser: user = utils.getCurrentUser() assert user, "Cannot decrement usage from guest!" return user["key"] else: remoteAddr = currentRequest.get().request.remote_addr if "::" in remoteAddr: # IPv6 in shorted form remoteAddr = remoteAddr.split(":") blankIndex = remoteAddr.index("") missigParts = ["0000"] * (8 - len(remoteAddr)) remoteAddr = remoteAddr[:blankIndex] + missigParts + remoteAddr[ blankIndex + 1:] return ":".join(remoteAddr[:4]) elif ":" in remoteAddr: # It's IPv6, so we remove the last 64 bits (interface id) # as it is easily controlled by the user return ":".join(remoteAddr.split(":")[:4]) else: # It's IPv4, simply return that address return remoteAddr
def _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)
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)
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
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
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)
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
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)
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)
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
def canAccess(*args, **kwargs): user = utils.getCurrentUser() if user and ("root" in user["access"] or "admin" in user["access"]): return True pathList = currentRequest.get().pathlist if len(pathList) >= 2 and pathList[1] in ["skey", "getVersion"]: # Give the user the chance to login :) return True if (len(pathList) >= 3 and pathList[1] == "user" and (pathList[2].startswith("auth_") or pathList[2].startswith("f2_") or pathList[2] == "getAuthMethods" or pathList[2] == "logout")): # Give the user the chance to login :) return True if (len(pathList) >= 4 and pathList[1] == "user" and pathList[2] == "view" and pathList[3] == "self"): # Give the user the chance to view himself. return True return False
def add(self, skelType, node, *args, **kwargs): """ Add a new entry with the given parent *node*, and render the entry, eventually with error notes on incorrect data. Data is taken by any other arguments in *kwargs*. The function performs several access control checks on the requested entity before it is added. .. seealso:: :func:`onAdded`, :func:`canAdd` :param skelType: Defines the type of the new entry and may either be "node" or "leaf". :type skelType: str :param node: URL-safe key of the parent. :type node: str :returns: The rendered, added object of the entry, eventually with error hints. :raises: :exc:`server.errors.NotAcceptable`, when no valid *skelType* was provided. :raises: :exc:`server.errors.NotFound`, when no valid *node* was found. :raises: :exc:`server.errors.Unauthorized`, if the current user does not have the required permissions. :raises: :exc:`server.errors.PreconditionFailed`, if the *skey* could not be verified. """ if "skey" in kwargs: skey = kwargs["skey"] else: skey = "" if skelType == "node": skelType = TreeType.Node elif skelType == "leaf" and self.leafSkelCls: skelType = TreeType.Leaf else: raise errors.NotAcceptable() skel = self.addSkel(skelType) parentNodeSkel = self.editSkel(TreeType.Node) if not parentNodeSkel.fromDB(node): raise errors.NotFound() if not self.canAdd(skelType, parentNodeSkel): raise errors.Unauthorized() if (len(kwargs) == 0 # no data supplied or skey == "" # no security key or not skel.fromClient( kwargs) # failure on reading into the bones or not currentRequest.get().isPostRequest or ("bounce" in kwargs and kwargs["bounce"] == "1" ) # review before adding ): return self.render.add(skel) if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() skel["parententry"] = parentNodeSkel["key"] # parentrepo may not exist of parentNodeSkel as it may be an rootNode skel["parentrepo"] = parentNodeSkel[ "parentrepo"] if "parentrepo" in parentNodeSkel else parentNodeSkel[ "key"] skel.toDB() self.onAdded(skel) return self.render.addSuccess(skel)
def getTemplateFileName(self, template, ignoreStyle=False): """ Returns the filename of the template. This function decides in which language and which style a given template is rendered. The style is provided as get-parameters for special-case templates that differ from their usual way. It is advised to override this function in case that :func:`server.render.jinja2.default.Render.getLoaders` is redefined. :param template: The basename of the template to use. :type template: str :param ignoreStyle: Ignore any maybe given style hints. :type ignoreStyle: bool :returns: Filename of the template :rtype: str """ validChars = "abcdefghijklmnopqrstuvwxyz1234567890-" if "htmlpath" in dir(self): htmlpath = self.htmlpath else: htmlpath = "html" currReq = currentRequest.get() if not ignoreStyle \ and "style" in currReq.kwargs \ and all([x in validChars for x in currReq.kwargs["style"].lower()]): stylePostfix = "_" + currReq.kwargs["style"] else: stylePostfix = "" lang = currentLanguage.get() # session.current.getLanguage() fnames = [template + stylePostfix + ".html", template + ".html"] if lang: fnames = [ os.path.join(lang, template + stylePostfix + ".html"), template + stylePostfix + ".html", os.path.join(lang, template + ".html"), template + ".html" ] for fn in fnames: # check subfolders prefix = template.split("_")[0] if os.path.isfile( os.path.join(utils.projectBasePath, htmlpath, prefix, fn)): return ("%s/%s" % (prefix, fn)) for fn in fnames: # Check the templatefolder of the application if os.path.isfile(os.path.join(utils.projectBasePath, htmlpath, fn)): return fn for fn in fnames: # Check the fallback if os.path.isfile( os.path.join(utils.projectBasePath, "viur", "core", "template", fn)): return fn raise errors.NotFound("Template %s not found." % template)
def 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
def edit(self, *args, **kwargs): """ Modify an existing entry, and render the entry, eventually with error notes on incorrect data. Data is taken by any other arguments in *kwargs*. The entry is fetched by its entity key, which either is provided via *kwargs["key"]*, or as the first parameter in *args*. The function performs several access control checks on the requested entity before it is modified. .. seealso:: :func:`editSkel`, :func:`onEdit`, :func:`onEdited`, :func:`canEdit` :returns: The rendered, edited object of the entry, eventually with error hints. :raises: :exc:`viur.core.errors.NotAcceptable`, when no *key* is provided. :raises: :exc:`viur.core.errors.NotFound`, when no entry with the given *key* was found. :raises: :exc:`viur.core.errors.Unauthorized`, if the current user does not have the required permissions. :raises: :exc:`viur.core.errors.PreconditionFailed`, if the *skey* could not be verified. """ if "skey" in kwargs: skey = kwargs["skey"] else: skey = "" if "key" in kwargs: key = kwargs["key"] elif len(args) == 1: key = args[0] else: raise errors.NotAcceptable() skel = self.editSkel() if not skel.fromDB(key): raise errors.NotFound() if not self.canEdit(skel): raise errors.Unauthorized() if (len(kwargs) == 0 # no data supplied or skey == "" # no security key or not currentRequest.get(). isPostRequest # failure if not using POST-method or not skel.fromClient( kwargs) # failure on reading into the bones or ("bounce" in kwargs and kwargs["bounce"] == "1" ) # review before changing ): # render the skeleton in the version it could as far as it could be read. return self.render.edit(skel) if not securitykey.validate(skey, useSessionKey=True): raise errors.PreconditionFailed() self.onEdit(skel) skel.toDB() # write it! self.onEdited(skel) return self.render.editSuccess(skel)
def 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
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"])
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
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)
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)
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
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)
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)
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)
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
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)
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)