convertdb.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  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) 2013 Oz Nahum <nahumoz@gmail.com>
  18. # ============================================================================
  19. from __future__ import print_function
  20. import os
  21. import shutil
  22. import time
  23. from pwman.util.crypto import CryptoEngine
  24. import pwman.data.factory
  25. from pwman.util.callback import CLICallback
  26. from pwman.data.nodes import NewNode
  27. from pwman.data.tags import Tag
  28. from pwman.data.database import Database, DatabaseException
  29. import sqlite3 as sqlite
  30. import pwman.util.config as config
  31. import sys
  32. if sys.version_info.major > 2:
  33. import pickle as cPickle
  34. else:
  35. import cPickle
  36. _NEWVERSION = 0.4
  37. class SQLiteDatabaseReader(Database):
  38. """SQLite Database implementation"""
  39. def __init__(self, filename=None):
  40. """Initialise SQLitePwmanDatabase instance."""
  41. Database.__init__(self)
  42. try:
  43. self._filename = config.get_value('Database', 'filename')
  44. except KeyError as e:
  45. raise DatabaseException(
  46. "SQLite: missing config parameter [%s]" % (e))
  47. def _open(self):
  48. try:
  49. self._con = sqlite.connect(self._filename)
  50. self._cur = self._con.cursor()
  51. self._checktables()
  52. except sqlite.DatabaseError as e:
  53. raise DatabaseException("SQLite: %s" % (e))
  54. def close(self):
  55. self._cur.close()
  56. self._con.close()
  57. def getnodes(self, ids):
  58. nodes = []
  59. for i in ids:
  60. sql = "SELECT DATA FROM NODES WHERE ID = ?"
  61. try:
  62. self._cur.execute(sql, [i])
  63. row = self._cur.fetchone()
  64. if row is not None:
  65. node = cPickle.loads(str(row[0]))
  66. node.set_id(i)
  67. nodes.append(node)
  68. except sqlite.DatabaseError as e:
  69. raise DatabaseException("SQLite: %s" % (e))
  70. return nodes
  71. def listnodes(self):
  72. sql = ''
  73. params = []
  74. if len(self._filtertags) == 0:
  75. sql = "SELECT ID FROM NODES ORDER BY ID ASC"
  76. else:
  77. first = True
  78. for t in self._filtertags:
  79. if not first:
  80. sql += " INTERSECT "
  81. else:
  82. first = False
  83. sql += ("SELECT NODE FROM LOOKUP LEFT JOIN TAGS "
  84. " ON TAG = TAGS.ID"
  85. " WHERE TAGS.DATA = ? ")
  86. params.append(cPickle.dumps(t))
  87. try:
  88. self._cur.execute(sql, params)
  89. ids = []
  90. row = self._cur.fetchone()
  91. while (row is not None):
  92. ids.append(row[0])
  93. row = self._cur.fetchone()
  94. return ids
  95. except sqlite.DatabaseError as e:
  96. raise DatabaseException("SQLite: %s" % (e))
  97. def _commit(self):
  98. try:
  99. self._con.commit()
  100. except sqlite.DatabaseError as e:
  101. self._con.rollback()
  102. raise DatabaseException(
  103. "SQLite: Error commiting data to db [%s]" % (e))
  104. def _tagids(self, tags):
  105. ids = []
  106. for t in tags:
  107. sql = "SELECT ID FROM TAGS WHERE DATA = ?"
  108. if not isinstance(t, Tag):
  109. raise DatabaseException(
  110. "Tried to insert foreign object into database [%s]", t)
  111. data = cPickle.dumps(t)
  112. try:
  113. self._cur.execute(sql, [data])
  114. row = self._cur.fetchone()
  115. if (row is not None):
  116. ids.append(row[0])
  117. else:
  118. sql = "INSERT INTO TAGS(DATA) VALUES(?)"
  119. self._cur.execute(sql, [data])
  120. ids.append(self._cur.lastrowid)
  121. except sqlite.DatabaseError as e:
  122. raise DatabaseException("SQLite: %s" % (e))
  123. return ids
  124. def _checktables(self):
  125. """ Check if the Pwman tables exist """
  126. self._cur.execute("PRAGMA TABLE_INFO(NODES)")
  127. if (self._cur.fetchone() is None):
  128. # table doesn't exist, create it
  129. # SQLite does have constraints implemented at the moment
  130. # so datatype will just be a string
  131. self._cur.execute("CREATE TABLE NODES "
  132. "(ID INTEGER PRIMARY KEY AUTOINCREMENT,"
  133. "DATA BLOB NOT NULL)")
  134. self._cur.execute("CREATE TABLE TAGS "
  135. "(ID INTEGER PRIMARY KEY AUTOINCREMENT,"
  136. "DATA BLOB NOT NULL UNIQUE)")
  137. self._cur.execute("CREATE TABLE LOOKUP "
  138. "(NODE INTEGER NOT NULL, TAG INTEGER NOT NULL,"
  139. " PRIMARY KEY(NODE, TAG))")
  140. self._cur.execute("CREATE TABLE KEY "
  141. + "(THEKEY TEXT NOT NULL DEFAULT '')")
  142. self._cur.execute("INSERT INTO KEY VALUES('')")
  143. try:
  144. self._con.commit()
  145. except DatabaseException as e:
  146. self._con.rollback()
  147. raise e
  148. def loadkey(self):
  149. """
  150. fetch the key to database. the key is also stored
  151. encrypted.
  152. """
  153. self._cur.execute("SELECT THEKEY FROM KEY")
  154. keyrow = self._cur.fetchone()
  155. if (keyrow[0] == ''):
  156. return None
  157. else:
  158. return keyrow[0]
  159. class DBConverter(object):
  160. """
  161. A general class to provide a base template for converting a database
  162. from one version to another
  163. """
  164. def __init__(self, args, config):
  165. self.dbname = config.get_value('Database', 'filename')
  166. self.dbtype = config.get_value("Database", "type")
  167. if not args.output:
  168. self.newdb_name = self.dbname
  169. else:
  170. self.newdb_name = '.new-%s'.join(os.path.splitext(self.dbname))
  171. @staticmethod
  172. def detect_db_version(filename):
  173. """
  174. This method should accept a pwman db file name, and it should try to
  175. detect which database version it is.
  176. """
  177. con = sqlite.connect(filename)
  178. cur = con.cursor()
  179. cur.execute("SELECT DBVERSION FROM DBVERSION")
  180. row = cur.fetchone()
  181. if not row:
  182. return "0.3"
  183. else:
  184. return row[0]
  185. @staticmethod
  186. def invoke_converter(dbversion, future_version):
  187. """
  188. this method should accept the two parameters and according to them
  189. invoke the right converter
  190. """
  191. pass
  192. def backup_old_db(self):
  193. print("Will convert the following Database: %s " % self.dbname)
  194. if os.path.exists(config.get_value("Database", "filename")):
  195. dbver = pwman.data.factory.check_db_version(self.dbtype)
  196. self.dbver = float(dbver)
  197. backup = '.backup-%s'.join(os.path.splitext(self.dbname)) % \
  198. time.strftime(
  199. '%Y-%m-%d-%H:%M')
  200. shutil.copy(self.dbname, backup)
  201. print("backup created in ", backup)
  202. def read_old_db(self):
  203. raise Exception("This methodod should be overriden")
  204. def create_new_db(self, new_version=_NEWVERSION):
  205. if os.path.exists(self.newdb_name):
  206. os.remove(self.newdb_name)
  207. self.newdb = pwman.data.factory.create(self.dbtype, new_version,
  208. self.newdb_name)
  209. self.newdb._open()
  210. def convert_nodes(self):
  211. raise Exception("This methodod should be overriden")
  212. def save_new_nodes_to_db(self):
  213. self.newdb.addnodes(self.NewNodes)
  214. self.newdb._commit()
  215. def save_old_key(self):
  216. raise Exception("This methodod should be overriden")
  217. def print_success(self):
  218. print("pwman successfully converted the old database to the new "
  219. "format.\nPlease run `pwman3 -d %s` to make sure your password "
  220. "and data are still correct. If you found errors, please "
  221. "report a bug in Pwman homepage in github. " % self.newdb_name)
  222. def run(self):
  223. self.backup_old_db()
  224. self.read_old_db()
  225. self.create_new_db()
  226. self.convert_nodes()
  227. self.save_new_nodes_to_db()
  228. self.save_old_key()
  229. self.print_success()
  230. class PwmanConvertDB(DBConverter):
  231. """
  232. Class to migrate from DB in version 0.3 to
  233. DB used in version 0.4 to 0.5.
  234. """
  235. def read_old_db(self):
  236. "read the old db and get all nodes"
  237. self.db = SQLiteDatabaseReader()
  238. enc = CryptoEngine.get()
  239. enc.callback = CLICallback()
  240. self.db.open()
  241. self.oldnodes = self.db.listnodes()
  242. self.oldnodes = self.db.getnodes(self.oldnodes)
  243. def save_old_key(self):
  244. enc = CryptoEngine.get()
  245. self.oldkey = enc.get_cryptedkey()
  246. self.newdb.savekey(self.oldkey)
  247. def convert_nodes(self):
  248. """convert old nodes instances to new format"""
  249. self.NewNodes = []
  250. for node in self.oldnodes:
  251. username = node.get_username()
  252. password = node.get_password()
  253. url = node.get_url()
  254. notes = node.get_notes()
  255. tags = node.get_tags()
  256. tags_strings = [tag._name for tag in tags]
  257. newNode = NewNode()
  258. newNode.username = username
  259. newNode.password = password
  260. newNode.url = url
  261. newNode.notes = notes
  262. newNode.tags = tags_strings
  263. self.NewNodes.append(newNode)
  264. class PwmanConvertKey(DBConverter):
  265. def read_old_db(self):
  266. enc = CryptoEngine.get()
  267. enc.callback = CLICallback()
  268. self.db.open()
  269. self.oldnodes = self.db.listnodes()
  270. self.oldnodes = self.db.getnodes(self.oldnodes)
  271. def save_old_key(self):
  272. CryptoEngine._instance = None
  273. enc = CryptoEngine.get(0.5)
  274. self.oldkey = enc.get_cryptedkey()
  275. self.newdb.savekey(self.oldkey)