sqlite.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. #============================================================================
  2. # This file is part of Pwman3.
  3. #
  4. # Pwman3 is free software; you can redistribute iut 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. #============================================================================
  20. # Copyright (C) 2006 Ivan Kelly <ivan@ivankelly.net>
  21. #============================================================================
  22. """SQLite Database implementation."""
  23. from pwman.data.database import Database, DatabaseException
  24. from pwman.data.database import __DB_FORMAT__
  25. from pwman.data.nodes import NewNode
  26. from pwman.util.crypto_engine import CryptoEngine
  27. import sqlite3 as sqlite
  28. import pwman.util.config as config
  29. import itertools
  30. def check_db_version():
  31. """
  32. check the data base version query the right table
  33. """
  34. filename = config.get_value('Database', 'filename')
  35. con = sqlite.connect(filename)
  36. cur = con.cursor()
  37. cur.execute("PRAGMA TABLE_INFO(DBVERSION)")
  38. row = cur.fetchone()
  39. if not row:
  40. return "0.3" # pragma: no cover
  41. try:
  42. return row[-2]
  43. except IndexError: # pragma: no cover
  44. raise DatabaseException("Something seems fishy with the DB")
  45. class SQLiteDatabaseNewForm(Database):
  46. """SQLite Database implementation"""
  47. def __init__(self, filename=None, dbformat=__DB_FORMAT__):
  48. """Initialise SQLitePwmanDatabase instance."""
  49. #Database.__init__(self)
  50. super(SQLiteDatabaseNewForm, self).__init__()
  51. # error handling is implemented in config.get_value
  52. # so there's no need to try... except here...
  53. if not filename:
  54. self._filename = config.get_value('Database', 'filename')
  55. else:
  56. self._filename = filename
  57. if not self._filename:
  58. raise DatabaseException(("SQLite: missing config parameter:"
  59. " filename"))
  60. self.dbformat = dbformat
  61. def _open(self):
  62. try:
  63. self._con = sqlite.connect(self._filename)
  64. self._cur = self._con.cursor()
  65. self._checktables()
  66. except sqlite.DatabaseError as e: # pragma: no cover
  67. raise DatabaseException("SQLite: %s" % (e))
  68. def close(self):
  69. self._cur.close()
  70. self._con.close()
  71. def listtags(self, alltags=False):
  72. sql = ''
  73. params = []
  74. if not self._filtertags or alltags:
  75. sql = "SELECT DATA FROM TAGS ORDER BY DATA ASC"
  76. else:
  77. sql = ("SELECT TAGS.DATA FROM LOOKUP"
  78. " INNER JOIN TAGS ON LOOKUP.TAG = TAGS.ID"
  79. " WHERE NODE IN (")
  80. first = True
  81. for t in self._filtertags:
  82. if not first:
  83. sql += " INTERSECT " # pragma: no cover
  84. else:
  85. first = False
  86. sql += ("SELECT NODE FROM LOOKUP LEFT JOIN TAGS ON TAG = "
  87. " TAGS.ID WHERE TAGS.DATA LIKE ?")
  88. params.append(t._name.decode()+u'%')
  89. sql += ") EXCEPT SELECT DATA FROM TAGS WHERE "
  90. first = True
  91. for t in self._filtertags:
  92. if not first:
  93. sql += " OR " # pragma: no cover
  94. else:
  95. first = False
  96. sql += "TAGS.DATA = ?"
  97. params.append(t.name)
  98. try:
  99. self._cur.execute(sql, params)
  100. tags = [str(t[0]) for t in self._cur.fetchall()]
  101. return tags
  102. except sqlite.DatabaseError as e: # pragma: no cover
  103. raise DatabaseException("SQLite: %s" % (e))
  104. except sqlite.InterfaceError as e: # pragma: no cover
  105. raise e
  106. def parse_node_string(self, string):
  107. nodestring = string.split("##")
  108. keyvals = {}
  109. for pair in nodestring[:-1]:
  110. key, val = pair.split(":")
  111. keyvals[key.lstrip('##')] = val
  112. tags = nodestring[-1]
  113. tags = tags.split("tags:", 1)[1]
  114. tags = tags.split("tag:")
  115. tags = [tag.split('**endtag**')[0] for tag in tags]
  116. return keyvals, tags
  117. def getnodes(self, ids):
  118. """
  119. object should always be: (ipwman.data.nodes
  120. """
  121. nodes = []
  122. for i in ids:
  123. sql = "SELECT DATA FROM NODES WHERE ID = ?"
  124. self._cur.execute(sql, [i])
  125. row = self._cur.fetchone()
  126. if row is not None:
  127. nodestring = str(row[0])
  128. args, tags = self.parse_node_string(nodestring)
  129. node = NewNode()
  130. node._password = args['password']
  131. node._username = args['username']
  132. node._url = args['url']
  133. node._notes = args['notes']
  134. node.tags = tags
  135. node._id = i
  136. nodes.append(node)
  137. return nodes
  138. def editnode(self, id, node):
  139. try:
  140. sql = "UPDATE NODES SET DATA = ? WHERE ID = ?"
  141. self._cur.execute(sql, [node.dump_edit_to_db()[0], id])
  142. except sqlite.DatabaseError as e: # pragma: no cover
  143. raise DatabaseException("SQLite: %s" % (e))
  144. self._setnodetags(node)
  145. self._checktags()
  146. self._commit()
  147. def addnodes(self, nodes):
  148. """
  149. This method writes the data as an ecrypted string to
  150. the database
  151. """
  152. for n in nodes:
  153. sql = "INSERT INTO NODES(DATA) VALUES(?)"
  154. value = n.dump_edit_to_db()
  155. try:
  156. self._cur.execute(sql, value)
  157. except sqlite.DatabaseError as e: # pragma: no cover
  158. raise DatabaseException("SQLite: %s" % (e))
  159. idx = self._cur.lastrowid
  160. n._id = idx
  161. self._setnodetags(n)
  162. self._commit()
  163. def removenodes(self, nodes):
  164. for n in nodes:
  165. # if not isinstance(n, Node): raise DatabaseException(
  166. # "Tried to delete foreign object from database [%s]", n)
  167. try:
  168. sql = "DELETE FROM NODES WHERE ID = ?"
  169. self._cur.execute(sql, [n._id])
  170. except sqlite.DatabaseError as e: # pragma: no cover
  171. raise DatabaseException("SQLite: %s" % (e))
  172. self._deletenodetags(n)
  173. self._checktags()
  174. self._commit()
  175. def listnodes(self):
  176. sql = ''
  177. params = []
  178. if not self._filtertags:
  179. sql = "SELECT ID FROM NODES ORDER BY ID ASC"
  180. else:
  181. first = True
  182. for t in self._filtertags:
  183. if not first:
  184. sql += " INTERSECT " # pragma: no cover
  185. else:
  186. first = False
  187. sql += ("SELECT NODE FROM LOOKUP LEFT JOIN TAGS ON TAG = "
  188. " TAGS.ID WHERE TAGS.DATA LIKE ? ")
  189. # this is correct if tags are ciphertext
  190. p = t._name.strip()
  191. # this is wrong, it will work when tags are stored as plain
  192. # text
  193. # p = t.name.strip()
  194. p = '%'+p+'%'
  195. params = [p]
  196. try:
  197. self._cur.execute(sql, params)
  198. rows = self._cur.fetchall()
  199. ids = [row[0] for row in rows]
  200. return ids
  201. except sqlite.DatabaseError as e: # pragma: no cover
  202. raise DatabaseException("SQLite: %s" % (e))
  203. def _commit(self):
  204. try:
  205. self._con.commit()
  206. except sqlite.DatabaseError as e: # pragma: no cover
  207. self._con.rollback()
  208. raise DatabaseException(
  209. "SQLite: Error commiting data to db [%s]" % (e))
  210. def _create_tag(self, tag):
  211. """add tags to db"""
  212. # sql = "INSERT OR REPLACE INTO TAGS(DATA) VALUES(?)"
  213. sql = "INSERT OR IGNORE INTO TAGS(DATA) VALUES(?)"
  214. if isinstance(tag, str):
  215. self._cur.execute(sql, [tag])
  216. else:
  217. self._cur.execute(sql, [tag._name])
  218. def _deletenodetags(self, node):
  219. try:
  220. sql = "DELETE FROM LOOKUP WHERE NODE = ?"
  221. self._cur.execute(sql, [node._id])
  222. except sqlite.DatabaseError as e: # pragma: no cover
  223. raise DatabaseException("SQLite: %s" % (e))
  224. self._commit()
  225. def _update_tag_lookup(self, node, tag_id):
  226. sql = "INSERT OR REPLACE INTO LOOKUP VALUES(?, ?)"
  227. params = [node._id, tag_id]
  228. try:
  229. self._cur.execute(sql, params)
  230. except sqlite.DatabaseError as e: # pragma: no cover
  231. raise DatabaseException("SQLite: %s" % (e))
  232. def _tagids(self, tags):
  233. ids = []
  234. sql = "SELECT ID FROM TAGS WHERE DATA LIKE ?"
  235. for tag in tags:
  236. try:
  237. if isinstance(tag, str):
  238. enc = CryptoEngine.get()
  239. tag = enc.encrypt(tag)
  240. self._cur.execute(sql, [tag])
  241. else:
  242. self._cur.execute(sql, [tag._name.decode()+u'%'])
  243. values = self._cur.fetchall()
  244. if values: # tags already exist in the database
  245. ids.extend(list(itertools.chain(*values)))
  246. else:
  247. self._create_tag(tag)
  248. ids.append(self._cur.lastrowid)
  249. except sqlite.DatabaseError as e: # pragma: no cover
  250. raise DatabaseException("SQLite: %s" % (e))
  251. return ids
  252. def _setnodetags(self, node):
  253. ids = self._tagids(node.tags)
  254. for tagid in ids:
  255. self._update_tag_lookup(node, tagid)
  256. self._commit()
  257. def _checktags(self):
  258. try:
  259. sql = "DELETE FROM TAGS WHERE ID NOT IN (SELECT TAG FROM" \
  260. + " LOOKUP GROUP BY TAG)"
  261. self._cur.execute(sql)
  262. except sqlite.DatabaseError as e: # pragma: no cover
  263. raise DatabaseException("SQLite: %s" % (e))
  264. self._commit()
  265. def _checktables(self):
  266. """
  267. Check if the Pwman tables exist.
  268. TODO: This method should check the version of the
  269. database. If it finds an old format it should
  270. exis, and prompt the user to convert the database
  271. to the new version with a designated script.
  272. """
  273. self._cur.execute("PRAGMA TABLE_INFO(NODES)")
  274. if self._cur.fetchone() is None:
  275. # table doesn't exist, create it
  276. # SQLite does have constraints implemented at the moment
  277. # so datatype will just be a string
  278. self._cur.execute("CREATE TABLE NODES (ID INTEGER PRIMARY KEY"
  279. " AUTOINCREMENT,DATA BLOB NOT NULL)")
  280. self._cur.execute("CREATE TABLE TAGS"
  281. "(ID INTEGER PRIMARY KEY AUTOINCREMENT,"
  282. "DATA BLOB NOT NULL UNIQUE)")
  283. self._cur.execute("CREATE TABLE LOOKUP"
  284. "(NODE INTEGER NOT NULL, TAG INTEGER NOT NULL,"
  285. " PRIMARY KEY(NODE, TAG))")
  286. self._cur.execute("CREATE TABLE KEY"
  287. "(THEKEY TEXT NOT NULL DEFAULT '')")
  288. self._cur.execute("INSERT INTO KEY VALUES('')")
  289. # create a table to hold DB version info
  290. self._cur.execute("CREATE TABLE DBVERSION"
  291. "(DBVERSION TEXT NOT NULL DEFAULT '%s')" %
  292. self.dbformat)
  293. self._cur.execute("INSERT INTO DBVERSION VALUES('%s')" %
  294. self.dbformat)
  295. try:
  296. self._con.commit()
  297. except DatabaseException as e: # pragma: no cover
  298. self._con.rollback()
  299. raise e
  300. def savekey(self, key):
  301. """
  302. This function is saving the key to table KEY.
  303. The key already arrives as an encrypted string.
  304. It is the same self._keycrypted from
  305. crypto py (check with id(self._keycrypted) and
  306. id(key) here.
  307. """
  308. sql = "UPDATE KEY SET THEKEY = ?"
  309. values = [key]
  310. self._cur.execute(sql, values)
  311. try:
  312. self._con.commit()
  313. except sqlite.DatabaseError as e: # pragma: no cover
  314. self._con.rollback()
  315. raise DatabaseException(
  316. "SQLite: Error saving key [%s]" % (e))
  317. def loadkey(self):
  318. """
  319. fetch the key to database. the key is also stored
  320. encrypted.
  321. """
  322. self._cur.execute("SELECT THEKEY FROM KEY")
  323. keyrow = self._cur.fetchone()
  324. if (keyrow[0] == ''):
  325. return None
  326. else:
  327. return keyrow[0]