test_converter.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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-2017 Oz Nahum Tiram <oz.tiram@gmail.com>
  18. # ============================================================================
  19. # pylint: disable=I0011
  20. from __future__ import print_function
  21. import sys
  22. import os
  23. sys.path.insert(0, os.getcwd())
  24. from pwman.data.database import Database, DatabaseException
  25. from pwman.data.drivers.sqlite import SQLiteDatabaseNewForm
  26. from pwman.data.nodes import Node
  27. from pwman.data.nodes import NewNode
  28. from pwman.util.crypto_engine import CryptoEngine
  29. from pwman.data.tags import Tag
  30. from db_tests import node_factory
  31. from pwman.util.callback import CLICallback
  32. import sqlite3 as sqlite
  33. import pwman.util.config as config
  34. from pwman import default_config
  35. import cPickle
  36. from test_tools import SetupTester, DummyCallback4
  37. from pwman.data.convertdb import DBConverter
  38. import copy
  39. import unittest
  40. class SQLiteDatabase(Database):
  41. """SQLite Database implementation"""
  42. def __init__(self, fname):
  43. """Initialise SQLitePwmanDatabase instance."""
  44. Database.__init__(self)
  45. try:
  46. self._filename = fname
  47. except KeyError as e:
  48. raise DatabaseException(
  49. "SQLite: missing parameter [%s]" % (e))
  50. def _open(self):
  51. try:
  52. self._con = sqlite.connect(self._filename)
  53. self._cur = self._con.cursor()
  54. self._checktables()
  55. except sqlite.DatabaseError as e:
  56. raise DatabaseException("SQLite: %s" % (e))
  57. def close(self):
  58. self._cur.close()
  59. self._con.close()
  60. def listtags(self, all=False):
  61. sql = ''
  62. params = []
  63. if len(self._filtertags) == 0 or all:
  64. sql = "SELECT DATA FROM TAGS ORDER BY DATA ASC"
  65. else:
  66. sql = ("SELECT TAGS.DATA FROM LOOKUP"
  67. + " INNER JOIN TAGS ON LOOKUP.TAG = TAGS.ID"
  68. + " WHERE NODE IN (")
  69. first = True
  70. for t in self._filtertags:
  71. if not first:
  72. sql += " INTERSECT "
  73. else:
  74. first = False
  75. sql += (("SELECT NODE FROM LOOKUP OUTER JOIN TAGS ON "
  76. "TAG = TAGS.ID "
  77. " WHERE TAGS.DATA = ?"))
  78. params.append(cPickle.dumps(t))
  79. sql += ") EXCEPT SELECT DATA FROM TAGS WHERE "
  80. first = True
  81. for t in self._filtertags:
  82. if not first:
  83. sql += " OR "
  84. else:
  85. first = False
  86. sql += "TAGS.DATA = ?"
  87. params.append(cPickle.dumps(t))
  88. try:
  89. self._cur.execute(sql, params)
  90. tags = []
  91. row = self._cur.fetchone()
  92. while (row is not None):
  93. tag = cPickle.loads(str(row[0]))
  94. tags.append(tag)
  95. row = self._cur.fetchone()
  96. return tags
  97. except sqlite.DatabaseError as e:
  98. raise DatabaseException("SQLite: %s" % (e))
  99. def getnodes(self, ids):
  100. nodes = []
  101. for i in ids:
  102. sql = "SELECT DATA FROM NODES WHERE ID = ?"
  103. try:
  104. self._cur.execute(sql, [i])
  105. row = self._cur.fetchone()
  106. if row is not None:
  107. node = cPickle.loads(str(row[0]))
  108. node.set_id(i)
  109. nodes.append(node)
  110. except sqlite.DatabaseError as e:
  111. raise DatabaseException("SQLite: %s" % (e))
  112. return nodes
  113. def editnode(self, id, node):
  114. if not isinstance(node, Node):
  115. raise DatabaseException(
  116. "Tried to insert foreign object into database [%s]" % node)
  117. try:
  118. sql = "UPDATE NODES SET DATA = ? WHERE ID = ?"
  119. self._cur.execute(sql, [cPickle.dumps(node), id])
  120. except sqlite.DatabaseError as e:
  121. raise DatabaseException("SQLite: %s" % (e))
  122. self._setnodetags(node)
  123. self._checktags()
  124. self._commit()
  125. def addnodes(self, nodes):
  126. for n in nodes:
  127. sql = "INSERT INTO NODES(DATA) VALUES(?)"
  128. if not isinstance(n, Node):
  129. raise DatabaseException(("Tried to insert foreign object"
  130. "into database [%s]", n))
  131. value = cPickle.dumps(n)
  132. try:
  133. self._cur.execute(sql, [value])
  134. except sqlite.DatabaseError as e:
  135. raise DatabaseException("SQLite: %s" % (e))
  136. id = self._cur.lastrowid
  137. n.set_id(id)
  138. self._setnodetags(n)
  139. self._commit()
  140. def removenodes(self, nodes):
  141. for n in nodes:
  142. if not isinstance(n, Node):
  143. raise DatabaseException(
  144. "Tried to delete foreign object from database [%s]", n)
  145. try:
  146. sql = "DELETE FROM NODES WHERE ID = ?"
  147. self._cur.execute(sql, [n.get_id()])
  148. except sqlite.DatabaseError as e:
  149. raise DatabaseException("SQLite: %s" % (e))
  150. self._deletenodetags(n)
  151. self._checktags()
  152. self._commit()
  153. def listnodes(self):
  154. sql = ''
  155. params = []
  156. if len(self._filtertags) == 0:
  157. sql = "SELECT ID FROM NODES ORDER BY ID ASC"
  158. else:
  159. first = True
  160. for t in self._filtertags:
  161. if not first:
  162. sql += " INTERSECT "
  163. else:
  164. first = False
  165. sql += ("SELECT NODE FROM LOOKUP OUTER JOIN "
  166. "TAGS ON TAG = TAGS.ID"
  167. " WHERE TAGS.DATA = ? ")
  168. params.append(cPickle.dumps(t))
  169. try:
  170. self._cur.execute(sql, params)
  171. ids = []
  172. row = self._cur.fetchone()
  173. while (row is not None):
  174. ids.append(row[0])
  175. row = self._cur.fetchone()
  176. return ids
  177. except sqlite.DatabaseError as e:
  178. raise DatabaseException("SQLite: %s" % (e))
  179. def _commit(self):
  180. try:
  181. self._con.commit()
  182. except sqlite.DatabaseError as e:
  183. self._con.rollback()
  184. raise DatabaseException(
  185. "SQLite: Error commiting data to db [%s]" % (e))
  186. def _tagids(self, tags):
  187. ids = []
  188. for t in tags:
  189. sql = "SELECT ID FROM TAGS WHERE DATA = ?"
  190. if not isinstance(t, Tag):
  191. raise DatabaseException("Tried to insert foreign "
  192. "object into database [%s]", t)
  193. data = cPickle.dumps(t)
  194. try:
  195. self._cur.execute(sql, [data])
  196. row = self._cur.fetchone()
  197. if (row is not None):
  198. ids.append(row[0])
  199. else:
  200. sql = "INSERT INTO TAGS(DATA) VALUES(?)"
  201. self._cur.execute(sql, [data])
  202. ids.append(self._cur.lastrowid)
  203. except sqlite.DatabaseError as e:
  204. raise DatabaseException("SQLite: %s" % (e))
  205. return ids
  206. def _deletenodetags(self, node):
  207. try:
  208. sql = "DELETE FROM LOOKUP WHERE NODE = ?"
  209. self._cur.execute(sql, [node.get_id()])
  210. except sqlite.DatabaseError as e:
  211. raise DatabaseException("SQLite: %s" % (e))
  212. self._commit()
  213. def _setnodetags(self, node):
  214. self._deletenodetags(node)
  215. ids = self._tagids(node.get_tags())
  216. for i in ids:
  217. sql = "INSERT OR REPLACE INTO LOOKUP VALUES(?, ?)"
  218. params = [node.get_id(), i]
  219. try:
  220. self._cur.execute(sql, params)
  221. except sqlite.DatabaseError as e:
  222. raise DatabaseException("SQLite: %s" % (e))
  223. self._commit()
  224. def _checktags(self):
  225. try:
  226. sql = ("DELETE FROM TAGS WHERE ID NOT "
  227. "IN (SELECT TAG FROM LOOKUP GROUP BY TAG)")
  228. self._cur.execute(sql)
  229. except sqlite.DatabaseError as e:
  230. raise DatabaseException("SQLite: %s" % (e))
  231. self._commit()
  232. def _checktables(self):
  233. """ Check if the Pwman tables exist """
  234. self._cur.execute("PRAGMA TABLE_INFO(NODES)")
  235. if (self._cur.fetchone() is None):
  236. # table doesn't exist, create it
  237. # SQLite does have constraints implemented at the moment
  238. # so datatype will just be a string
  239. self._cur.execute("CREATE TABLE NODES"
  240. + "(ID INTEGER PRIMARY KEY AUTOINCREMENT,"
  241. + "DATA BLOB NOT NULL)")
  242. self._cur.execute("CREATE TABLE TAGS"
  243. + "(ID INTEGER PRIMARY KEY AUTOINCREMENT,"
  244. + "DATA BLOB NOT NULL UNIQUE)")
  245. self._cur.execute("CREATE TABLE LOOKUP"
  246. + "(NODE INTEGER NOT NULL, TAG INTEGER NOT NULL,"
  247. + " PRIMARY KEY(NODE, TAG))")
  248. self._cur.execute("CREATE TABLE KEY"
  249. + "(THEKEY TEXT NOT NULL DEFAULT '')")
  250. self._cur.execute("INSERT INTO KEY VALUES('')")
  251. try:
  252. self._con.commit()
  253. except DatabaseException as e:
  254. self._con.rollback()
  255. raise e
  256. def savekey(self, key):
  257. sql = "UPDATE KEY SET THEKEY = ?"
  258. values = [key]
  259. self._cur.execute(sql, values)
  260. try:
  261. self._con.commit()
  262. except sqlite.DatabaseError as e:
  263. self._con.rollback()
  264. raise DatabaseException(
  265. "SQLite: Error saving key [%s]" % (e))
  266. def loadkey(self):
  267. self._cur.execute("SELECT THEKEY FROM KEY")
  268. keyrow = self._cur.fetchone()
  269. if (keyrow[0] == ''):
  270. return None
  271. else:
  272. return keyrow[0]
  273. class CreateTestDataBases(object):
  274. def __init__(self):
  275. config.set_defaults(default_config)
  276. enc = CryptoEngine.get(dbver=0.4)
  277. enc.callback = DummyCallback4()
  278. self.enc1 = copy.copy(enc)
  279. enc = CryptoEngine.get(dbver=0.5)
  280. enc.callback = DummyCallback4()
  281. self.enc2 = copy.copy(enc)
  282. self.db1 = SQLiteDatabaseNewForm('konverter-v0.4.db', dbformat=0.4)
  283. self.db2 = SQLiteDatabaseNewForm('konverter-v0.5.db', dbformat=0.5)
  284. assert self.enc1 is not self.enc2
  285. def open_dbs(self):
  286. self.db1._open()
  287. self.db2._open()
  288. self.db1.close()
  289. self.db2.close()
  290. def add_nodes_to_db1(self):
  291. username = 'tester'
  292. password = 'Password'
  293. url = 'example.org'
  294. notes = 'some notes'
  295. node = node_factory(username, password, url, notes,
  296. ['testing1', 'testing2'])
  297. self.db1.addnodes([node])
  298. idx_created = node._id
  299. new_node = self.db1.getnodes([idx_created])[0]
  300. for key, attr in {'password': password, 'username': username,
  301. 'url': url, 'notes': notes}.iteritems():
  302. assert attr == getattr(new_node, key)
  303. self.db1.close()
  304. def add_nodes_to_db2(self):
  305. username = 'tester'
  306. password = 'Password'
  307. url = 'example.org'
  308. notes = 'some notes'
  309. node = node_factory(username, password, url, notes,
  310. ['testing1', 'testing2'])
  311. self.db2.addnodes([node])
  312. idx_created = node._id
  313. new_node = self.db2.getnodes([idx_created])[0]
  314. for key, attr in {'password': password, 'username': username,
  315. 'url': url, 'notes': notes}.iteritems():
  316. assert attr == getattr(new_node, key)
  317. self.db2.close()
  318. def run(self):
  319. # before add nodes to db1 we have to create an encryption key!
  320. # this is handeld by the open method
  321. self.db1._open()
  322. enc1 = CryptoEngine.get(dbver=0.4)
  323. enc1.callback = DummyCallback4()
  324. key = self.db1.loadkey()
  325. if key is not None:
  326. enc1.set_cryptedkey(key)
  327. else:
  328. newkey = enc1.changepassword()
  329. self.db1.savekey(newkey)
  330. enc1c = copy.copy(enc1)
  331. if key is not None:
  332. enc1.set_cryptedkey(key)
  333. self.add_nodes_to_db1()
  334. CryptoEngine._instance = None
  335. self.db2._open()
  336. enc2 = CryptoEngine.get(dbver=0.5)
  337. enc2.callback = DummyCallback4()
  338. key = self.db2.loadkey()
  339. if key is not None:
  340. enc2.set_cryptedkey(key)
  341. else:
  342. newkey = enc2.changepassword()
  343. self.db2.savekey(newkey)
  344. enc2c = copy.copy(enc2)
  345. if key is not None:
  346. enc2.set_cryptedkey(key)
  347. self.add_nodes_to_db2()
  348. assert enc1 is not enc2
  349. assert enc1c is not enc2c
  350. class TestConverter(unittest.TestCase):
  351. pass
  352. if __name__ == '__main__':
  353. tester = CreateTestDataBases()
  354. tester.run()
  355. # afther the test databases are created, invoking
  356. # pwman3 -d konverter-v0.5.db
  357. assert "0.4" == DBConverter.detect_db_version('konverter-v0.4.db')
  358. assert "0.5" == DBConverter.detect_db_version('konverter-v0.5.db')
  359. # python scripts/pwman3 -d konverter-v0.5.db -e AES,
  360. # works !