def createNewUserIfNotExists(): """ Create a new Admin user, if the userDB is empty """ userMod = getattr(conf["viur.mainApp"], "user", None) if (userMod # We have a user module and isinstance(userMod, User) and "addSkel" in dir(userMod) and "validAuthenticationMethods" in dir(userMod) # Its our user module :) and any([issubclass(x[0], UserPassword) for x in userMod.validAuthenticationMethods])): # It uses UserPassword login if not db.Query(userMod.addSkel().kindName).getEntry(): # There's currently no user in the database addSkel = skeletonByKind(userMod.addSkel().kindName)() # Ensure we have the full skeleton uname = "admin@%s.appspot.com" % utils.projectID pw = utils.generateRandomString(13) addSkel["name"] = uname addSkel["status"] = 10 # Ensure its enabled right away addSkel["access"] = ["root"] addSkel["password"] = pw try: addSkel.toDB() except Exception as e: logging.error("Something went wrong when trying to add admin user %s with Password %s", uname, pw) logging.exception(e) return logging.warning("ViUR created a new admin-user for you! Username: %s, Password: %s", uname, pw) utils.sendEMailToAdmins("Your new ViUR password", "ViUR created a new admin-user for you! Username: %s, Password: %s" % (uname, pw))
def fromClient(self, skel: 'SkeletonInstance', name: str, data: dict) -> Union[None, List[ReadFromClientError]]: if not name in data: return [ ReadFromClientError(ReadFromClientErrorSeverity.NotSet, "Field not submitted") ] value = data.get(name) if not value: # Password-Bone is special: As it cannot be read don't set back no None if no value is given # This means an once set password can only be changed - but never deleted. return [ ReadFromClientError(ReadFromClientErrorSeverity.Empty, "No value entered") ] err = self.isInvalid(value) if err: return [ ReadFromClientError(ReadFromClientErrorSeverity.Invalid, err) ] # As we don't escape passwords and allow most special characters we'll hash it early on so we don't open # an XSS attack vector if a password is echoed back to the client (which should not happen) salt = utils.generateRandomString(self.saltLength) passwd = pbkdf2(value[:conf["viur.maxPasswordLength"]], salt) skel[name] = {"pwhash": passwd, "salt": salt}
def createUploadURL( self, node: Union[str, None]) -> Tuple[str, str, Dict[str, str]]: global bucket targetKey = utils.generateRandomString() conditions = [["starts-with", "$key", "%s/source/" % targetKey]] if isinstance(credentials, ServiceAccountCredentials ): # We run locally with an service-account.json policy = bucket.generate_upload_policy(conditions) else: # Use our fixed PolicyGenerator - Google is currently unable to create one itself on its GCE policy = self.generateUploadPolicy(conditions) uploadUrl = "https://%s.storage.googleapis.com" % bucket.name # Create a correspondingfile-lock object early, otherwise we would have to ensure that the file-lock object # the user creates matches the file he had uploaded fileSkel = self.addSkel(TreeType.Leaf) fileSkel["key"] = targetKey fileSkel["name"] = "pending" fileSkel["size"] = 0 fileSkel["mimetype"] = "application/octetstream" fileSkel["dlkey"] = targetKey fileSkel["parentdir"] = None fileSkel["pendingparententry"] = db.keyHelper( node, self.addSkel(TreeType.Node).kindName) if node else None fileSkel["pending"] = True fileSkel["weak"] = True fileSkel["width"] = 0 fileSkel["height"] = 0 fileSkel.toDB() # Mark that entry dirty as we might never receive an add utils.markFileForDeletion(targetKey) return targetKey, uploadUrl, policy
def createUploadURL( self, node: Union[str, None]) -> Tuple[str, str, Dict[str, str]]: targetKey = utils.generateRandomString() conditions = [["starts-with", "$key", "%s/source/" % targetKey]] policy = bucket.generate_upload_policy(conditions) uploadUrl = "https://%s.storage.googleapis.com" % bucket.name # Create a correspondingfile-lock object early, otherwise we would have to ensure that the file-lock object # the user creates matches the file he had uploaded fileSkel = self.addLeafSkel() fileSkel["key"] = targetKey fileSkel["name"] = "pending" fileSkel["size"] = 0 fileSkel["mimetype"] = "application/octetstream" fileSkel["dlkey"] = targetKey fileSkel["parentdir"] = None fileSkel["pendingParentdir"] = db.keyHelper( node, self.addNodeSkel().kindName) if node else None fileSkel["pending"] = True fileSkel["weak"] = True fileSkel["width"] = 0 fileSkel["height"] = 0 fileSkel[""] = "" fileSkel.toDB() # Mark that entry dirty as we might never receive an add utils.markFileForDeletion(targetKey) return targetKey, uploadUrl, policy
def write(self, filename: str, content: Any, mimetype: str = "text/plain", width: int = None, height: int = None) -> db.Key: """ Write a file from any buffer into the file module. :param filename: Filename to be written. :param content: The file content to be written, as bytes-like object. :param mimetype: The file's mimetype. :param width: Optional width information for the file. :param height: Optional height information for the file. :return: Returns the key of the file object written. This can be associated e.g. with a fileBone. """ dl_key = utils.generateRandomString() blob = bucket.blob("%s/source/%s" % (dl_key, filename)) blob.upload_from_file(BytesIO(content), content_type=mimetype) skel = self.addSkel("leaf") skel["name"] = filename skel["size"] = blob.size skel["mimetype"] = mimetype skel["dlkey"] = dl_key skel["weak"] = True skel["width"] = width skel["height"] = height return skel.toDB()
def __init__(self, request: webob.Request, response: webob.Response): super() self.startTime = time() self.request = request self.response = response self.maxLogLevel = logging.DEBUG self._traceID = request.headers.get( 'X-Cloud-Trace-Context') or utils.generateRandomString()
def validateSecurityKey(self, key): """ Checks if key matches the current CSRF-Token of our session. On success, a new key is generated. """ if compare_digest(self.securityKey, key): self.securityKey = utils.generateRandomString(13) self.changed = True return True return False
def exchangeSecurityKey(): dbSession = db.Get(db.Key(self.kindName, self.cookieKey)) if not dbSession: # Should not happen (except if session.reset has been called in the same request) return False if dbSession["securityKey"] != key: # Race-Condidtion: That skey has been used in another instance return False dbSession["securityKey"] = utils.generateRandomString(13) db.Put(dbSession) return dbSession["securityKey"]
def serialize(self, skeletonValues, name): if name in skeletonValues.accessedValues and skeletonValues.accessedValues[ name]: salt = utils.generateRandomString(self.saltLength) passwd = pbkdf2( skeletonValues.accessedValues[name] [:conf["viur.maxPasswordLength"]], salt) skeletonValues.entity[name] = {"pwhash": passwd, "salt": salt} return True return False
def reset(self): """ Invalids the current session and starts a new one. This function is especially useful at login, where we might need to create an SSL-capable session. :warning: Everything (except the current language) is flushed. """ lang = self.session.get("language") if self.cookieKey: db.Delete(db.Key(self.kindName, self.cookieKey)) self.cookieKey = utils.generateRandomString(42) self.staticSecurityKey = utils.generateRandomString(13) self.securityKey = utils.generateRandomString(13) self.changed = True self.isInitial = True self.session = db.Entity() if lang: self.session["language"] = lang
def serialize(self, skel: 'SkeletonInstance', name: str, parentIndexed: bool) -> bool: if name in skel.accessedValues and skel.accessedValues[name]: salt = utils.generateRandomString(self.saltLength) passwd = pbkdf2( skel.accessedValues[name][:conf["viur.maxPasswordLength"]], salt) skel.dbEntity[name] = {"pwhash": passwd, "salt": salt} # Ensure our indexed flag is up2date indexed = self.indexed and parentIndexed if indexed and name in skel.dbEntity.exclude_from_indexes: skel.dbEntity.exclude_from_indexes.discard(name) elif not indexed and name not in skel.dbEntity.exclude_from_indexes: skel.dbEntity.exclude_from_indexes.add(name) return True return False
def create(duration: Union[None, int] = None, **kwargs) -> str: """ Creates a new onetime Securitykey or returns the current sessions csrf-token. The custom data (given as keyword arguments) that can be stored with the key if :param:duration is set must be serializable by the datastore. :param duration: Make this key valid for a fixed timeframe (and independent of the current session) :returns: The new onetime key """ if not duration: return currentSession.get().getSecurityKey() key = generateRandomString() duration = int(duration) dbObj = db.Entity(db.Key(securityKeyKindName, key)) for k, v in kwargs.items(): dbObj[k] = v dbObj["until"] = utcNow() + timedelta(seconds=duration) db.Put(dbObj) return key
def serialize(self, skel: 'SkeletonInstance', name: str, parentIndexed: bool) -> bool: if name in skel.accessedValues and skel.accessedValues[name]: value = skel.accessedValues[name] if isinstance( value, dict): # It is a pre-hashed value (probably fromClient) skel.dbEntity[name] = value else: # This has been set by skel["password"] = "******", we'll still have to hash it salt = utils.generateRandomString(self.saltLength) passwd = pbkdf2(value[:conf["viur.maxPasswordLength"]], salt) skel.dbEntity[name] = {"pwhash": passwd, "salt": salt} # Ensure our indexed flag is up2date indexed = self.indexed and parentIndexed if indexed and name in skel.dbEntity.exclude_from_indexes: skel.dbEntity.exclude_from_indexes.discard(name) elif not indexed and name not in skel.dbEntity.exclude_from_indexes: skel.dbEntity.exclude_from_indexes.add(name) return True return False
def create(duration: Union[None, int] = None, **kwargs): """ Creates a new onetime Securitykey for the current session If duration is not set, this key is valid only for the current session. Otherwise, the key and its data is serialized and saved inside the datastore for up to duration-seconds :param duration: Make this key valid for a fixed timeframe (and independend of the current session) :type duration: int or None :returns: The new onetime key """ if not duration: return currentSession.getSecurityKey() key = generateRandomString() duration = int(duration) dbObj = db.Entity(securityKeyKindName, name=key) for k, v in kwargs.items(): dbObj[k] = v dbObj["until"] = datetime.now() + timedelta(seconds=duration) db.Put(dbObj) return key
def initializeUpload(self, fileName: str, mimeType: str, node: Union[str, None], size: Union[int, None] = None) -> Tuple[str, str]: """ Internal helper that registers a new upload. Will create the pending fileSkel entry (needed to remove any started uploads from GCS if that file isn't used) and creates a resumable (and signed) uploadURL for that. :param fileName: Name of the file that will be uploaded :param mimeType: Mimetype of said file :param node: If set (to a string-key representation of a file-node) the upload will be written to this directory :param size: The *exact* filesize we're accepting in Bytes. Used to enforce a filesize limit by getUploadURL :return: Str-Key of the new file-leaf entry, the signed upload-url """ global bucket fileName = sanitizeFileName(fileName) targetKey = utils.generateRandomString() blob = bucket.blob("%s/source/%s" % (targetKey, fileName)) uploadUrl = blob.create_resumable_upload_session(content_type=mimeType, size=size, timeout=60) # Create a corresponding file-lock object early, otherwise we would have to ensure that the file-lock object # the user creates matches the file he had uploaded fileSkel = self.addSkel("leaf") fileSkel["name"] = "pending" fileSkel["size"] = 0 fileSkel["mimetype"] = "application/octetstream" fileSkel["dlkey"] = targetKey fileSkel["parentdir"] = None fileSkel["pendingparententry"] = db.keyHelper( node, self.addSkel("node").kindName) if node else None fileSkel["pending"] = True fileSkel["weak"] = True fileSkel["width"] = 0 fileSkel["height"] = 0 fileSkel.toDB() # Mark that entry dirty as we might never receive an add utils.markFileForDeletion(targetKey) return db.encodeKey(fileSkel["key"]), uploadUrl
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)