def _do_get(self, phrase): """ Fetch all categories and items data for a particular user. :param phrase: decryption passphrase :returns: success result along with categories and items arrays """ cat_array = [] item_array = [] cipher = aescipher.AESCipher(phrase) try: categories = api.category_getall(self.session, self.user) for category in categories: cat_array.append(category.extract(cipher, with_items=False)) items = api.item_getall(self.session, self.user) for item in items: item_array.append(item.extract(cipher, with_category=False)) except UnicodeDecodeError: raise bh.OppError("Unable to decrypt data!") except Exception: raise bh.OppError("Unable to fetch from the database!") return { 'result': 'success', 'categories': cat_array, 'items': item_array }
def _gen_pwd(self, words, options): """ Wrapper function around xkcdpass password generation logic :param words: list of words to use for password generation :param options: additional password generation constraints :returns: generated multi-word password """ try: numwords = options['numwords'] except (TypeError, KeyError): numwords = 6 try: delimiter = options['delimiter'] except (TypeError, KeyError): delimiter = " " # Sanity validation if numwords < 1 or numwords > 20: msg = "Invalid password generation options!" desc = ("For sanity's sake, numwords must be a " "positive number less than or equal to 20.") raise bh.OppError(msg, desc) try: return xp.generate_xkcdpassword(words, numwords=numwords, interactive=False, acrostic=False, delimiter=delimiter) except Exception: raise bh.OppError("Exception during password generation!", None, 500)
def _get_words(self, options): """Locate a word file and parse it to get a list of words from which xkcdpass module will randomly choose a passphrase. The word file may be specified in the configuration file specific to a particular deployment. Otherwise the algorithm will try to locate a standard word file from well known locations. :param options: contains options for xkcdpass configuring the type of words to include. :returns: list of words """ wordfile = CONFIG['wordfile'] or xp.locate_wordfile() try: min_length = options['min_length'] except (TypeError, KeyError): min_length = 5 try: max_length = options['max_length'] except (TypeError, KeyError): max_length = 9 try: valid_chars = options['valid_chars'] except (TypeError, KeyError): valid_chars = "." # Sanity validation msg = "Invalid password generation options!" if min_length < 1 or min_length > 15: desc = ("For sanity's sake, minimum length must be " "a positive number less than or equal to 15.") raise bh.OppError(msg, desc) if max_length < 5 or max_length > 20: desc = ("For sanity's sake, maximum length must" " be a number between 5 and 20.") raise bh.OppError(msg, desc) if min_length > max_length: desc = "Minimum length cannot be larger than maximum length." raise bh.OppError(msg, desc) try: return xp.generate_wordlist(wordfile=wordfile, min_length=min_length, max_length=max_length, valid_chars=valid_chars) except Exception: raise bh.OppError("Exception during password generation!", None, 500)
def make_item(self, row, cipher, password, item_id=None): """ Extract various item data from the request and encrypt it :param row: item object parsed from JSON request :param cipher: encryption cipher :param password: if specified, ignore the `password` field in in the item object and instead use this auto-generated password. :param item_id: should be specified for item update operations, None otherwise. :returns Item ORM model for insertion into the database """ # Extract various item data into a list name = self._parse_or_set_empty(row, 'name') url = self._parse_or_set_empty(row, 'url') account = self._parse_or_set_empty(row, 'account') username = self._parse_or_set_empty(row, 'username') password = password or self._parse_or_set_empty(row, 'password') blob = self._parse_or_set_empty(row, 'blob') category_id = self._parse_or_set_empty(row, 'category_id', True) try: return models.Item(id=item_id, name=cipher.encrypt(name), url=cipher.encrypt(url), account=cipher.encrypt(account), username=cipher.encrypt(username), password=cipher.encrypt(password), blob=cipher.encrypt(blob), category_id=category_id, user=self.user) except (AttributeError, TypeError): raise bh.OppError("Invalid item data in list!")
def _do_put(self, phrase): """ Create a list of items, given an array of item parameters, with option to specify auto-generation of common or unique passwords for each item. :param phrase: decryption passphrase :returns: success result along with array of newly created items """ payload_dicts = [{ 'name': "items", 'is_list': True, 'required': True }, { 'name': "auto_pass", 'is_list': False, 'required': False }, { 'name': "unique", 'is_list': False, 'required': False }, { 'name': "genopts", 'is_list': False, 'required': False }] item_list, auto_pass, unique, genopts = self._check_payload( payload_dicts) if auto_pass is True: # Retrieve words dictionary words = self._get_words(genopts) # Generate common password for all items if unique is not True: common_password = self._gen_pwd(words, genopts) cipher = aescipher.AESCipher(phrase) items = [] for row in item_list: if auto_pass is True: if unique is True: password = self._gen_pwd(words, genopts) else: password = common_password else: password = None items.append(self.make_item(row, cipher, password)) try: items = api.item_create(self.session, items) response = [] for item in items: response.append(item.extract(cipher)) return {'result': 'success', 'items': response} except Exception: raise bh.OppError("Unable to add new items to the database!")
def _do_put(self, phrase): """ Create a new user. :param phrase: not used :returns: success result """ payload_dicts = [{ 'name': "username", 'is_list': False, 'required': True }, { 'name': "password", 'is_list': False, 'required': True }, { 'name': "phrase", 'is_list': False }] payload_objects = self._check_payload(payload_dicts) u = self._validate(payload_objects[0], 'username') p = self._validate(payload_objects[1], 'password') phrase = self._validate(payload_objects[2], 'phrase') if len(phrase) < 6: raise bh.OppError("Passphrase must be at least 6 characters long!") try: cipher = aescipher.AESCipher(phrase) ok = cipher.encrypt("OK") user = api.user_get_by_username(self.session, u) if user: raise bh.OppError("User already exists!") hashed = utils.hashpw(p) user = models.User(username=u, password=hashed, phrase_check=ok) api.user_create(self.session, user) user = api.user_get_by_username(self.session, u) if user: return {'result': 'success'} else: raise bh.OppError("Unable to add user: '******'" % u) except bh.OppError as e: raise bh.OppError(e.error, e.desc, e.status, e.headers) except Exception: raise bh.OppError("Unable to add user: '******'" % u)
def _do_get(self, phrase): """ Fetch all user's items. :param phrase: decryption passphrase :returns: success result along with decrypted items array """ response = [] cipher = aescipher.AESCipher(phrase) try: items = api.item_getall(self.session, self.user) for item in items: response.append(item.extract(cipher)) except UnicodeDecodeError: raise bh.OppError("Unable to decrypt data!") except Exception: raise bh.OppError("Unable to fetch items from the database!") return {'result': 'success', 'items': response}
def _do_delete(self): """ Delete a user. :returns: success result """ payload_dicts = [{ 'name': "username", 'is_list': False, 'required': True }, { 'name': "password", 'is_list': False, 'required': True }] payload_objects = self._check_payload(payload_dicts) u = self._validate(payload_objects[0], 'username') p = self._validate(payload_objects[1], 'password') try: user = api.user_get_by_username(self.session, u) if not user: raise bh.OppError("User does not exist!") if not utils.checkpw(p, user.password): raise bh.OppError("Incorrect password supplied!") api.category_delete_all(self.session, user, True) api.item_delete_all(self.session, user) api.user_delete(self.session, user) user = api.user_get_by_username(self.session, u) if user: bh.OppError("Unable to delete user: '******'" % u) else: return {'result': 'success'} except bh.OppError as e: raise bh.OppError(e.error, e.desc, e.status, e.headers) except Exception: raise bh.OppError("Unable to delate user: '******'" % u)
def _validate(self, obj, name): """ Validate the input object. :param obj: input object :param name: input object name :returns validated object or raises an error """ obj = obj.strip() if not obj: raise bh.OppError("Input '%s' parameter is empty!" % name) return obj
def _do_delete(self): """ Delete a list of items, identified by id. :returns: success result """ payload_dicts = [{'name': "ids", 'is_list': True, 'required': True}] payload_objects = self._check_payload(payload_dicts) id_list = payload_objects[0] try: api.item_delete_by_id(self.session, self.user, id_list) return {'result': "success"} except Exception: raise bh.OppError("Unable to delete items from the database!")
def _do_post(self, phrase): """ Update a user. :param phrase: not used :returns: success result """ payload_dicts = [{ 'name': "username", 'is_list': False, 'required': True }, { 'name': "password", 'is_list': False, 'required': True }, { 'name': "new_username", 'is_list': False, 'required': False }, { 'name': "new_password", 'is_list': False, 'required': False }] payload_objects = self._check_payload(payload_dicts) u = self._validate(payload_objects[0], 'username') p = self._validate(payload_objects[1], 'password') new_u = payload_objects[2].strip() new_p = payload_objects[3].strip() if not (new_u or new_p): raise bh.OppError("at least one of: " "[--new_username, --new_password] is required!") try: user = api.user_get_by_username(self.session, u) if not user: raise bh.OppError("User does not exist!") if not utils.checkpw(p, user.password): raise bh.OppError("Incorrect password supplied!") if new_u: new_user = api.user_get_by_username(self.session, new_u) if new_user: raise bh.OppError("Username: '******' already exists!" % new_u) if new_u: user.username = new_u if new_p: user.password = utils.hashpw(new_p) api.user_update(self.session, user) user = api.user_get_by_username(self.session, user.username) if user: return {'result': 'success'} else: raise bh.OppError("Unable to add user: '******'" % u) except bh.OppError as e: raise bh.OppError(e.error, e.desc, e.status, e.headers) except Exception: raise bh.OppError("Unable to update user: '******'" % u)
def _do_post(self, phrase): """ Update a list of items. Similar options to create except that item id is required for each item in the list. :param phrase: decryption passphrase :returns: success result """ payload_dicts = [{ 'name': "items", 'is_list': True, 'required': True }, { 'name': "auto_pass", 'is_list': False, 'required': False }, { 'name': "unique", 'is_list': False, 'required': False }, { 'name': "genopts", 'is_list': False, 'required': False }] item_list, auto_pass, unique, genopts = self._check_payload( payload_dicts) if auto_pass is True: # Retrieve words dictionary words = self._get_words(genopts) # Generate common password for all items if needed if unique is not True: common_password = self._gen_pwd(words, genopts) cipher = aescipher.AESCipher(phrase) items = [] for row in item_list: # Make sure item id is parsed from request try: item_id = row['id'] except KeyError: raise bh.OppError("Missing item id in list!") if not item_id: raise bh.OppError("Empty item id in list!") if auto_pass is True: if unique is True: password = self._gen_pwd(words, genopts) else: password = common_password else: password = None items.append(self.make_item(row, cipher, password, item_id)) try: api.item_update(self.session, items) response = [] for item in items: response.append(item.extract(cipher)) return {'result': 'success', 'items': response} except Exception: raise bh.OppError("Unable to update items in the database!")