Source code for keepassdb.model

# -*- coding: utf-8 -*-
"""
The model consists of the Group and Entity classes that together make up the contents
of the database.  These classes are directly related to their :mod:`keepassdb.structs`
counterparts; however, they also track the hierarchy of the database, track modification
times, and provide convenience methods to perform create and move operations.
""" 

__authors__ = ["Brett Viren <brett.viren@gmail.com>","Hans Lellelid <hans@xmpl.org>"]
__license__ = """
keepassdb is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or at your option) any later version.

keepassdb is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
keepassdb.  If not, see <http://www.gnu.org/licenses/>.
"""

import abc
import logging
import base64

from keepassdb import const, util
from keepassdb.structs import GroupStruct, EntryStruct

__authors__ = ["Karsten-Kai König <kkoenig@posteo.de>", "Hans Lellelid <hans@xmpl.org>"]
__copyright__ = "Copyright (C) 2012 Karsten-Kai König <kkoenig@posteo.de>"
__license__ = """
keepassdb is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or at your option) any later version.

keepassdb is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
keepassdb.  If not, see <http://www.gnu.org/licenses/>.
"""

class BaseModel(object):
    __metaclass__ = abc.ABCMeta
    
    def __init__(self):
        self.log = logging.getLogger('{0}.{1}'.format(self.__module__, self.__class__.__name__))
        
    @abc.abstractproperty
    def struct_type(self):
        pass
    
    @classmethod
    def from_struct(cls, structobj):
        """
        Initialize properties of this model object from specified struct object.
        
        :param structobj: The sturct object instance to use for initialization.
        """
        kwargs = structobj.attributes()
        return cls(**kwargs)
    
    def to_struct(self):
        """
        Initialize properties of the appropriate struct class from this model class.
        """
        structobj = self.struct_type()
        for k in structobj.attributes():
            self.log.info("Setting attribute %s to %r" % (k, getattr(self, k)))
            setattr(structobj, k, getattr(self, k))
        return structobj
        
[docs]class RootGroup(object): """ A group-like object that serves as the root node of the tree. This is the `root` attribute of the :class:`keepassdb.db.Database` instance. This object is not written to the database; it simply exists to provide a virtual node from which to make the top-level groups children. :ivar title: The title for the group ('Root Group') :ivar level: The hierarchy level / depth in tree; This is -1 for root group. :ivar children: List of children groups (:class:`keepassdb.model.Group`) """ parent = None level = -1 children = None entries = None title = 'Root Group' def __init__(self): self.children = [] self.entries = [] def __repr__(self): return '<RootGroup>'
[docs]class Group(BaseModel): """ Represents a single group of a KeePass 1.x database. :ivar db: The parent database (:class:`keepassdb.db.Database`) :ivar id: The group numeric id (unsigned int) :ivar title: The group title (string) :ivar level: The hierarchy level / depth in tree (unsigned int) :ivar icon: The group icon identifier used in KeePassX (unsigned int) :ivar parent: The parent group (:class:`keepassdb.model.Group`) :ivar children: List of children groups (:class:`keepassdb.model.Group`) :ivar entries: List of member entries (:class:`keepassdb.model.Entry`) """ # These are the shadow attribs for our getters and setters _title = None _icon = None _expires = None struct_type = GroupStruct def __init__(self, id=None, title=None, icon=None, level=None, created=None, modified=None, accessed=None, expires=None, flags=None, parent=None, db=None): """ Initialize a new Group object with optional attributes. Use the :method:`Group.parse` class method if you would like to initialize a group from the data structure. """ super(Group, self).__init__() if icon is None: icon = 1 if created is None: created = util.now() if modified is None: modified = util.now() if accessed is None: accessed = util.now() if expires is None: expires = const.NEVER if flags is None: flags = 0 # XXX: Need to figure out what this is, but 0 seems to be the correct default self.id = id self._title = title self._icon = icon self.level = level self.created = created self.modified = modified self.accessed = accessed self._expires = expires self.flags = flags # TODO: Determine how we want to handle these other attributes. self.parent = parent self.db = db self.children = [] self.entries = [] def __repr__(self): return '<Group title={0} id={1} level={2}>'.format(self.title, self.id, self.level) @property def title(self): return self._title @title.setter def title(self, value): self._title = value self.modified = util.now() @property def icon(self): return self._icon @icon.setter def icon(self, value): self._icon = value self.modified = util.now() @property def expires(self): return self._expires @expires.setter def expires(self, value): self._expires = value self.modified = util.now()
[docs] def move(self, parent, index=None): """ Move this group to a new parent. :param parent: The new parent group; if None will be root group. :type parent: :class:`keepassdb.model.Group` :param index: The 0-based index within the parent (defaults to appending group to end of parent's children). :type index: int """ return self.db.move_group(self, parent, index=index)
[docs] def change_index(self, index): """ Move the group to the new 0-based index within the same parent. :param index: The 0-based index for the new position within parent group. Note that this index will be evaluated *after* the group has been removed from the list. :type index: int """ return self.db.change_group_index(self, index)
[docs] def remove(self): """This method calls remove_group of the holding db""" return self.db.remove_group(self)
[docs] def create_entry(self, **kwargs): """ This method creates an entry in this group. :keyword title: :keyword icon: :keyword url: :keyword username: :keyword password: :keyword notes: :keyword expires: Expiration date (if None, entry will never expire). :type expires: datetime """ return self.db.create_entry(group=self, **kwargs)
def to_dict(self, hierarchy=True, hide_passwords=False): d = dict(id=self.id, title=self.title, icon=self.icon, level=self.level, created=self.created if self.created != const.NEVER else None, modified=self.modified if self.modified != const.NEVER else None, accessed=self.accessed if self.accessed != const.NEVER else None, expires=self.expires if self.expires != const.NEVER else None, flags=self.flags ) d['entries'] = [e.to_dict(hide_passwords=hide_passwords) for e in self.entries] if hierarchy: d['children'] = [g.to_dict(hierarchy=hierarchy, hide_passwords=hide_passwords) for g in self.children] return d
[docs]class Entry(BaseModel): """Entry represents a simple entry of a KeePass 1.x database. :ivar uuid: The ID for the entry. :ivar group_id: The numeric ID for the group. :ivar group: The group object that this entity is related to. :ivar icon: The icon identifier. :ivar title: The title for the entry. :ivar username: The username. :ivar password: The password :ivar url: The entry URL. :ivar notes: Notes/comment for the entry. :ivar created: When entry was created (default: now) :ivar modified: When entry was last modified (default: now) :ivar accessed: When the entry was last accessed (default: now) :ivar expires: When the entry (password) expires. Default will be :ref:`keepassdb.const.NEVER`. :ivar binary_desc: Description/metadata for the binary column. :ivar binary: Binary contents. """ struct_type = EntryStruct _group = None def __init__(self, uuid = None, group_id = None, group = None, icon = None, title = None, url = None, username = None, password = None, notes = None, created = None, modified = None, accessed = None, expires = None, binary_desc = None, binary = None): """ Initialize a Entry-instance with provided attributes. Typically Entry objects should be created using the :meth:`Group.create_entry` method which will additionally bind the entry to the group. :keyword uuid: The ID for the entry. :type uuid: str (16 bytes) :keyword group_id: The numeric ID for the group. :type group_id: int :keyword group: The group object that this entity is related to. :type group: :class:`Group` :keyword icon: The icon identifier. :type icon: int :keyword title: The title for the entry. :type username: unicode :keyword username: The username. :type username: unicode :keyword password: The password :type password: unicode :keyword url: The entry URL. :type url: unicode :keyword notes: Notes/comment for the entry. :type notes: unicode :keyword created: When entry was created (default: now) :type created: :class:`datetime.datetime` :keyword modified: When entry was last modified (default: now) :type modified: :class:`datetime.datetime` :keyword accessed: When the entry was last accessed (default: now) :type accessed: :class:`datetime.datetime` :keyword expires: When the entry (password) expires. Default will be :ref:`keepassdb.const.NEVER`. :type expires :class:`datetime.datetime` :keyword binary_desc: Description/metadata for the binary column. :type binary_desc: unicode :keyword binary: Binary contents. :type binary: str """ super(Entry, self).__init__() if icon is None: icon = 1 if created is None: created = util.now() if modified is None: modified = util.now() if accessed is None: accessed = util.now() if expires is None: expires = const.NEVER # Some casting to strings here, since this is what we'll get when we read # these entries from the database. (Probably needs to be more comprehensive.) if title is None: title = u'' if notes is None: notes = u'' if url is None: url = u'' if binary is None: binary = b'' if binary_desc is None: binary_desc = u'' self.uuid = uuid self.group_id = group_id self.group = group # Property attributes self._icon = icon self._title = title self._url = url self._username = username self._password = password self._notes = notes self.created = created self.modified = modified self.accessed = accessed self._expires = expires self.binary_desc = binary_desc self.binary = binary def __repr__(self): return '<Entry title={0} username={1}>'.format(self.title, self.username) @property def group(self): return self._group @group.setter def group(self, value): self._group = value if value is not None: self.group_id = value.id @property def title(self): return self._title @title.setter def title(self, value): self._title = value self.modified = util.now() @property def icon(self): return self._icon @icon.setter def icon(self, value): self._icon = value self.modified = util.now() @property def url(self): return self._url @url.setter def url(self, value): self._url = value self.modified = util.now() @property def username(self): return self._username @username.setter def username(self, value): self._username = value self.modified = util.now() @property def password(self): return self._password @password.setter def password(self, value): self._password = value self.modified = util.now() @property def notes(self): return self._notes @notes.setter def notes(self, value): self._notes = value self.modified = util.now() @property def expires(self): return self._expires @expires.setter def expires(self, value): self._expires = value self.modified = util.now()
[docs] def move(self, group, index=None): """ This method moves the entry to another group. """ return self.group.db.move_entry(self, group, index=index)
[docs] def change_index(self, index): """ This method moves the entry to another position in the group. """ return self.group.db.move_entry_in_group(self, index)
[docs] def remove(self): """ This method removes this entry. """ return self.group.db.remove_entry(self)
def to_dict(self, hide_passwords=False): d = dict(uuid=self.uuid, group_id=self.group_id, icon=self.icon, title=self.title, url=self.url, username=self.username, password=self.password if not hide_passwords else '********', notes=self.notes, created=self.created if self.created != const.NEVER else None, modified=self.modified if self.modified != const.NEVER else None, expires=self.expires if self.expires != const.NEVER else None, binary_desc=self.binary_desc, binary=base64.b64encode(self.binary) if self.binary is not None else '' ) return d