def search_items(connection: DBusConnection, attributes: Dict[str, str]) -> Iterator[Item]: """Returns a generator of items in all collections with the given attributes. `attributes` should be a dictionary.""" service = DBusAddressWrapper(SS_PATH, SERVICE_IFACE, connection) locked, unlocked = service.call('SearchItems', 'a{ss}', attributes) for item_path in locked + unlocked: yield Item(connection, item_path)
def get_collection_by_alias(connection: DBusConnection, alias: str) -> Collection: """Returns the collection with the given `alias`. If there is no such collection, raises :exc:`~secretstorage.exceptions.ItemNotFoundException`.""" service = DBusAddressWrapper(SS_PATH, SERVICE_IFACE, connection) collection_path, = service.call('ReadAlias', 's', alias) if len(collection_path) <= 1: raise ItemNotFoundException('No collection with such alias.') return Collection(connection, collection_path)
def create_collection(connection: DBusConnection, label: str, alias: str = '', session: Optional[Session] = None) -> Collection: """Creates a new :class:`Collection` with the given `label` and `alias` and returns it. This action requires prompting. :raises: :exc:`~secretstorage.exceptions.PromptDismissedException` if the prompt is dismissed. """ if not session: session = open_session(connection) properties = {SS_PREFIX + 'Collection.Label': ('s', label)} service = DBusAddressWrapper(SS_PATH, SERVICE_IFACE, connection) collection_path, prompt = service.call('CreateCollection', 'a{sv}s', properties, alias) if len(collection_path) > 1: return Collection(connection, collection_path, session=session) dismissed, result = exec_prompt(connection, prompt) if dismissed: raise PromptDismissedException('Prompt dismissed.') signature, collection_path = result assert signature == 'o' return Collection(connection, collection_path, session=session)
def lock(self) -> None: """Locks the collection.""" service = DBusAddressWrapper(SS_PATH, SERVICE_IFACE, self.connection) service.call('Lock', 'ao', [self.collection_path])
class Collection(object): """Represents a collection.""" def __init__(self, connection: DBusConnection, collection_path: str = DEFAULT_COLLECTION, session: Optional[Session] = None) -> None: self.connection = connection self.session = session self.collection_path = collection_path self._collection = DBusAddressWrapper(collection_path, COLLECTION_IFACE, connection) self._collection.get_property('Label') def is_locked(self) -> bool: """Returns :const:`True` if item is locked, otherwise :const:`False`.""" return bool(self._collection.get_property('Locked')) def ensure_not_locked(self) -> None: """If collection is locked, raises :exc:`~secretstorage.exceptions.LockedException`.""" if self.is_locked(): raise LockedException('Collection is locked!') def unlock(self) -> bool: """Requests unlocking the collection. Returns a boolean representing whether the prompt has been dismissed; that means :const:`False` on successful unlocking and :const:`True` if it has been dismissed. .. versionchanged:: 3.0 No longer accepts the ``callback`` argument. """ return unlock_objects(self.connection, [self.collection_path]) def lock(self) -> None: """Locks the collection.""" service = DBusAddressWrapper(SS_PATH, SERVICE_IFACE, self.connection) service.call('Lock', 'ao', [self.collection_path]) def delete(self) -> None: """Deletes the collection and all items inside it.""" self.ensure_not_locked() prompt, = self._collection.call('Delete', '') if prompt != "/": dismissed, _result = exec_prompt(self.connection, prompt) if dismissed: raise PromptDismissedException('Prompt dismissed.') def get_all_items(self) -> Iterator[Item]: """Returns a generator of all items in the collection.""" for item_path in self._collection.get_property('Items'): yield Item(self.connection, item_path, self.session) def search_items(self, attributes: Dict[str, str]) -> Iterator[Item]: """Returns a generator of items with the given attributes. `attributes` should be a dictionary.""" result, = self._collection.call('SearchItems', 'a{ss}', attributes) for item_path in result: yield Item(self.connection, item_path, self.session) def get_label(self) -> str: """Returns the collection label.""" label = self._collection.get_property('Label') assert isinstance(label, str) return label def set_label(self, label: str) -> None: """Sets collection label to `label`.""" self.ensure_not_locked() self._collection.set_property('Label', 's', label) def create_item(self, label: str, attributes: Dict[str, str], secret: bytes, replace: bool = False, content_type: str = 'text/plain') -> Item: """Creates a new :class:`~secretstorage.item.Item` with given `label` (unicode string), `attributes` (dictionary) and `secret` (bytestring). If `replace` is :const:`True`, replaces the existing item with the same attributes. If `content_type` is given, also sets the content type of the secret (``text/plain`` by default). Returns the created item.""" self.ensure_not_locked() if not self.session: self.session = open_session(self.connection) _secret = format_secret(self.session, secret, content_type) properties = { SS_PREFIX + 'Item.Label': ('s', label), SS_PREFIX + 'Item.Attributes': ('a{ss}', attributes), } new_item, prompt = self._collection.call('CreateItem', 'a{sv}(oayays)b', properties, _secret, replace) return Item(self.connection, new_item, self.session)
class Collection(object): """Represents a collection.""" def __init__(self, connection: DBusConnection, collection_path: str = DEFAULT_COLLECTION, session: Optional[Session] = None) -> None: self.connection = connection self.session = session self.collection_path = collection_path self._collection = DBusAddressWrapper( collection_path, COLLECTION_IFACE, connection) self._collection.get_property('Label') def is_locked(self) -> bool: """Returns :const:`True` if item is locked, otherwise :const:`False`.""" return bool(self._collection.get_property('Locked')) def ensure_not_locked(self) -> None: """If collection is locked, raises :exc:`~secretstorage.exceptions.LockedException`.""" if self.is_locked(): raise LockedException('Collection is locked!') def unlock(self) -> bool: """Requests unlocking the collection. Returns a boolean representing whether the prompt has been dismissed; that means :const:`False` on successful unlocking and :const:`True` if it has been dismissed. .. versionchanged:: 3.0 No longer accepts the ``callback`` argument. """ return unlock_objects(self.connection, [self.collection_path]) def lock(self) -> None: """Locks the collection.""" service = DBusAddressWrapper(SS_PATH, SERVICE_IFACE, self.connection) service.call('Lock', 'ao', [self.collection_path]) def delete(self) -> None: """Deletes the collection and all items inside it.""" self.ensure_not_locked() prompt, = self._collection.call('Delete', '') if prompt != "/": dismissed, _result = exec_prompt(self.connection, prompt) if dismissed: raise PromptDismissedException('Prompt dismissed.') def get_all_items(self) -> Iterator[Item]: """Returns a generator of all items in the collection.""" for item_path in self._collection.get_property('Items'): yield Item(self.connection, item_path, self.session) def search_items(self, attributes: Dict[str, str]) -> Iterator[Item]: """Returns a generator of items with the given attributes. `attributes` should be a dictionary.""" result, = self._collection.call('SearchItems', 'a{ss}', attributes) for item_path in result: yield Item(self.connection, item_path, self.session) def get_label(self) -> str: """Returns the collection label.""" label = self._collection.get_property('Label') assert isinstance(label, str) return label def set_label(self, label: str) -> None: """Sets collection label to `label`.""" self.ensure_not_locked() self._collection.set_property('Label', 's', label) def create_item(self, label: str, attributes: Dict[str, str], secret: bytes, replace: bool = False, content_type: str = 'text/plain') -> Item: """Creates a new :class:`~secretstorage.item.Item` with given `label` (unicode string), `attributes` (dictionary) and `secret` (bytestring). If `replace` is :const:`True`, replaces the existing item with the same attributes. If `content_type` is given, also sets the content type of the secret (``text/plain`` by default). Returns the created item.""" self.ensure_not_locked() if not self.session: self.session = open_session(self.connection) _secret = format_secret(self.session, secret, content_type) properties = { SS_PREFIX + 'Item.Label': ('s', label), SS_PREFIX + 'Item.Attributes': ('a{ss}', attributes), } new_item, prompt = self._collection.call('CreateItem', 'a{sv}(oayays)b', properties, _secret, replace) return Item(self.connection, new_item, self.session)
class Item(object): """Represents a secret item.""" def __init__(self, connection: DBusConnection, item_path: str, session: Optional[Session] = None) -> None: self.item_path = item_path self._item = DBusAddressWrapper(item_path, ITEM_IFACE, connection) self._item.get_property('Label') self.session = session self.connection = connection def __eq__(self, other: "DBusConnection") -> bool: assert isinstance(other.item_path, str) return self.item_path == other.item_path def is_locked(self) -> bool: """Returns :const:`True` if item is locked, otherwise :const:`False`.""" return bool(self._item.get_property('Locked')) def ensure_not_locked(self) -> None: """If collection is locked, raises :exc:`~secretstorage.exceptions.LockedException`.""" if self.is_locked(): raise LockedException('Item is locked!') def unlock(self) -> bool: """Requests unlocking the item. Usually, this means that the whole collection containing this item will be unlocked. Returns a boolean representing whether the prompt has been dismissed; that means :const:`False` on successful unlocking and :const:`True` if it has been dismissed. .. versionadded:: 2.1.2 .. versionchanged:: 3.0 No longer accepts the ``callback`` argument. """ return unlock_objects(self.connection, [self.item_path]) def get_attributes(self) -> Dict[str, str]: """Returns item attributes (dictionary).""" attrs = self._item.get_property('Attributes') return dict(attrs) def set_attributes(self, attributes: Dict[str, str]) -> None: """Sets item attributes to `attributes` (dictionary).""" self._item.set_property('Attributes', 'a{ss}', attributes) def get_label(self) -> str: """Returns item label (unicode string).""" label = self._item.get_property('Label') assert isinstance(label, str) return label def set_label(self, label: str) -> None: """Sets item label to `label`.""" self.ensure_not_locked() self._item.set_property('Label', 's', label) def delete(self) -> None: """Deletes the item.""" self.ensure_not_locked() prompt, = self._item.call('Delete', '') if prompt != "/": dismissed, _result = exec_prompt(self.connection, prompt) if dismissed: raise PromptDismissedException('Prompt dismissed.') def get_secret(self) -> bytes: """Returns item secret (bytestring).""" self.ensure_not_locked() if not self.session: self.session = open_session(self.connection) secret, = self._item.call('GetSecret', 'o', self.session.object_path) if not self.session.encrypted: return bytes(secret[2]) assert self.session.aes_key is not None aes = algorithms.AES(self.session.aes_key) aes_iv = bytes(secret[1]) decryptor = Cipher(aes, modes.CBC(aes_iv), default_backend()).decryptor() encrypted_secret = secret[2] padded_secret = decryptor.update( bytes(encrypted_secret)) + decryptor.finalize() assert isinstance(padded_secret, bytes) return padded_secret[:-padded_secret[-1]] def get_secret_content_type(self) -> str: """Returns content type of item secret (string).""" self.ensure_not_locked() if not self.session: self.session = open_session(self.connection) secret, = self._item.call('GetSecret', 'o', self.session.object_path) return str(secret[3]) def set_secret(self, secret: bytes, content_type: str = 'text/plain') -> None: """Sets item secret to `secret`. If `content_type` is given, also sets the content type of the secret (``text/plain`` by default).""" self.ensure_not_locked() if not self.session: self.session = open_session(self.connection) _secret = format_secret(self.session, secret, content_type) self._item.call('SetSecret', '(oayays)', _secret) def get_created(self) -> int: """Returns UNIX timestamp (integer) representing the time when the item was created. .. versionadded:: 1.1""" created = self._item.get_property('Created') assert isinstance(created, int) return created def get_modified(self) -> int: """Returns UNIX timestamp (integer) representing the time when the item was last modified.""" modified = self._item.get_property('Modified') assert isinstance(modified, int) return modified