database.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. # ============================================================================
  2. # This file is part of Pwman3.
  3. #
  4. # Pwman3 is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License, version 2
  6. # as published by the Free Software Foundation;
  7. #
  8. # Pwman3 is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with Pwman3; if not, write to the Free Software
  15. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  16. # ============================================================================
  17. # Copyright (C) 2012 Oz Nahum <nahumoz@gmail.com>
  18. # ============================================================================
  19. # Copyright (C) 2006 Ivan Kelly <ivan@ivankelly.net>
  20. # ============================================================================
  21. from pwman.util.crypto_engine import CryptoEngine
  22. __DB_FORMAT__ = 0.6
  23. class DatabaseException(Exception):
  24. pass # pragma: no cover
  25. class Database(object):
  26. def open(self, dbver=None):
  27. """
  28. Open the database, by calling the _open method of the
  29. class inherited for the specific database.
  30. When done validation that the file is OK, check if it has
  31. encryption key, by calling
  32. enc = CryptoEngine.get()
  33. key = self.loadkey()
  34. """
  35. self._open()
  36. enc = CryptoEngine.get()
  37. key = self.loadkey()
  38. if key is not None:
  39. enc.set_cryptedkey(key)
  40. else:
  41. self.get_user_password()
  42. def _check_tables(self):
  43. try:
  44. self._cur.execute("SELECT 1 from DBVERSION")
  45. version = self._cur.fetchone()
  46. return version
  47. except self.ProgrammingError:
  48. self._con.rollback()
  49. def _create_tables(self):
  50. if self._check_tables():
  51. return
  52. try:
  53. self._cur.execute("CREATE TABLE NODE(ID SERIAL PRIMARY KEY, "
  54. "USERNAME TEXT NOT NULL, "
  55. "PASSWORD TEXT NOT NULL, "
  56. "URL TEXT NOT NULL, "
  57. "NOTES TEXT NOT NULL"
  58. ")")
  59. self._cur.execute("CREATE TABLE TAG"
  60. "(ID SERIAL PRIMARY KEY,"
  61. "DATA VARCHAR(255) NOT NULL UNIQUE)")
  62. self._cur.execute("CREATE TABLE LOOKUP ("
  63. "nodeid INTEGER NOT NULL REFERENCES NODE(ID),"
  64. "tagid INTEGER NOT NULL REFERENCES TAG(ID)"
  65. ")")
  66. self._cur.execute("CREATE TABLE CRYPTO "
  67. "(SEED TEXT, DIGEST TEXT)")
  68. self._cur.execute("CREATE TABLE DBVERSION("
  69. "VERSION TEXT NOT NULL)")
  70. self._cur.execute("INSERT INTO DBVERSION VALUES(%s)",
  71. (self.dbversion,))
  72. self._con.commit()
  73. except self.ProgrammingError: # pragma: no cover
  74. self._con.rollback()
  75. def get_user_password(self):
  76. """
  77. get the databases password from the user
  78. """
  79. enc = CryptoEngine.get()
  80. newkey = enc.changepassword()
  81. return self.savekey(newkey)
  82. def _clean_orphans(self):
  83. clean = ("delete from TAG where not exists "
  84. "(select 'x' from LOOKUP l where l.TAGID = TAG.ID)")
  85. self._cur.execute(clean)
  86. self._con.commit()
  87. def _get_node_tags(self, node):
  88. sql = "SELECT tagid FROM LOOKUP WHERE NODEID = {}".format(self._sub)
  89. self._cur.execute(sql, (str(node[0]),))
  90. tagids = self._cur.fetchall()
  91. if tagids:
  92. sql = ("SELECT DATA FROM TAG WHERE ID IN"
  93. " ({})".format(','.join([self._sub]*len(tagids))))
  94. tagids = [str(id[0]) for id in tagids]
  95. self._cur.execute(sql, (tagids))
  96. tags = self._cur.fetchall()
  97. for t in tags:
  98. yield t[0]
  99. def _setnodetags(self, nodeid, tags):
  100. for tag in tags:
  101. tid = self._get_or_create_tag(tag)
  102. self._update_tag_lookup(nodeid, tid)
  103. def _get_tag(self, tagcipher):
  104. sql_search = "SELECT ID FROM TAG WHERE DATA = {}".format(self._sub)
  105. self._cur.execute(sql_search, ([tagcipher]))
  106. rv = self._cur.fetchone()
  107. return rv
  108. def _get_or_create_tag(self, tagcipher):
  109. rv = self._get_tag(tagcipher)
  110. if rv:
  111. return rv[0]
  112. else:
  113. self._cur.execute(self._insert_tag_sql, ([tagcipher]))
  114. try:
  115. return self._cur.fetchone()[0]
  116. except TypeError:
  117. return self._cur.lastrowid
  118. def _update_tag_lookup(self, nodeid, tid):
  119. sql_lookup = "INSERT INTO LOOKUP(nodeid, tagid) VALUES({}, {})".format(
  120. self._sub, self._sub)
  121. self._cur.execute(sql_lookup, (nodeid, tid))
  122. self._con.commit()
  123. def getnodes(self, ids):
  124. if ids:
  125. sql = ("SELECT * FROM NODE WHERE ID IN ({})"
  126. "".format(','.join(self._sub for i in ids)))
  127. else:
  128. sql = "SELECT * FROM NODE"
  129. self._cur.execute(sql, (ids))
  130. nodes = self._cur.fetchall()
  131. nodes_w_tags = []
  132. for node in nodes:
  133. tags = list(self._get_node_tags(node))
  134. nodes_w_tags.append(list(node) + tags)
  135. return nodes_w_tags
  136. def listnodes(self, filter=None):
  137. """return a list of node ids"""
  138. if not filter:
  139. sql_all = "SELECT ID FROM NODE"
  140. self._cur.execute(sql_all)
  141. ids = self._cur.fetchall()
  142. return [id[0] for id in ids]
  143. else:
  144. tagid = self._get_tag(filter)
  145. if not tagid:
  146. return [] # pragma: no cover
  147. self._cur.execute(self._list_nodes_sql, (tagid))
  148. self._con.commit()
  149. ids = self._cur.fetchall()
  150. return [id[0] for id in ids]
  151. def add_node(self, node):
  152. node_tags = list(node)
  153. node, tags = node_tags[:4], node_tags[-1]
  154. self._cur.execute(self._add_node_sql, (node))
  155. try:
  156. nid = self._cur.fetchone()[0]
  157. except TypeError:
  158. nid = self._cur.lastrowid
  159. self._setnodetags(nid, tags)
  160. self._con.commit()
  161. def listtags(self):
  162. self._clean_orphans()
  163. get_tags = "select DATA from TAG"
  164. self._cur.execute(get_tags)
  165. tags = self._cur.fetchall()
  166. if tags:
  167. return [t[0] for t in tags]
  168. return [] # pragma: no cover
  169. # TODO: add this to test of postgresql and mysql!
  170. def editnode(self, nid, **kwargs):
  171. tags = kwargs.pop('tags', None)
  172. sql = ("UPDATE NODE SET {} WHERE ID = {} ".format(
  173. ','.join(['{}={}'.format(k, self._sub) for k in list(kwargs)]),
  174. self._sub))
  175. self._cur.execute(sql, (list(kwargs.values()) + [nid]))
  176. if tags:
  177. # update all old node entries in lookup
  178. # create new entries
  179. # clean all old tags
  180. sql_clean = "DELETE FROM LOOKUP WHERE NODEID={}".format(self._sub)
  181. self._cur.execute(sql_clean, (str(nid),))
  182. self._setnodetags(nid, tags)
  183. self._con.commit()
  184. def removenodes(self, nid):
  185. # shall we do this also in the sqlite driver?
  186. sql_clean = "DELETE FROM LOOKUP WHERE NODEID={}".format(self._sub)
  187. self._cur.execute(sql_clean, nid)
  188. sql_rm = "delete from NODE where ID = {}".format(self._sub)
  189. self._cur.execute(sql_rm, nid)
  190. self._con.commit()
  191. self._con.commit()
  192. def fetch_crypto_info(self):
  193. self._cur.execute("SELECT * FROM CRYPTO")
  194. row = self._cur.fetchone()
  195. return row
  196. def save_crypto_info(self, seed, digest):
  197. """save the random seed and the digested key"""
  198. self._cur.execute("DELETE FROM CRYPTO")
  199. self._cur.execute("INSERT INTO CRYPTO VALUES({}, {})".format(self._sub,
  200. self._sub),
  201. (seed, digest))
  202. self._con.commit()
  203. def loadkey(self):
  204. """
  205. return _keycrypted
  206. """
  207. sql = "SELECT * FROM CRYPTO"
  208. try:
  209. self._cur.execute(sql)
  210. seed, digest = self._cur.fetchone()
  211. return seed + u'$6$' + digest
  212. except TypeError: # pragma: no cover
  213. return None
  214. def savekey(self, key):
  215. salt, digest = key.split('$6$')
  216. sql = "INSERT INTO CRYPTO(SEED, DIGEST) VALUES({},{})".format(self._sub,
  217. self._sub)
  218. self._cur.execute("DELETE FROM CRYPTO")
  219. self._cur.execute(sql, (salt, digest))
  220. self._digest = digest.encode('utf-8')
  221. self._salt = salt.encode('utf-8')
  222. self._con.commit()
  223. def close(self): # pragma: no cover
  224. self._clean_orphans()
  225. self._cur.close()
  226. self._con.close()