convertdb.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  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. import os
  20. import shutil
  21. import os.path
  22. import time
  23. import getpass
  24. from pwman.util.crypto import CryptoEngine
  25. import pwman.data.factory
  26. from pwman.util.callback import Callback
  27. from pwman.data.nodes import NewNode
  28. import sys
  29. _NEWVERSION = 0.4
  30. from pwman.data.database import Database, DatabaseException
  31. import sqlite3 as sqlite
  32. import pwman.util.config as config
  33. import cPickle
  34. class Tag(object): # pragma: no cover
  35. """
  36. tags are specific strings used to classify nodes
  37. the methods in this class override some built-ins
  38. for strings.
  39. """
  40. def __init__(self, name):
  41. self.set_name(name)
  42. def __eq__(self, other):
  43. if other._name == self._name:
  44. return True
  45. else:
  46. return False
  47. def get_name(self):
  48. enc = CryptoEngine.get()
  49. return enc.decrypt(self._name)
  50. def set_name(self, name):
  51. enc = CryptoEngine.get()
  52. self._name = enc.encrypt(name)
  53. def __str__(self):
  54. enc = CryptoEngine.get()
  55. return enc.decrypt(self._name)
  56. class Node(object): # pragma: no cover
  57. def __init__(self, username="", password="", url="",
  58. notes="", tags=[]):
  59. """Initialise everything to null."""
  60. self._id = 0
  61. enc = CryptoEngine.get()
  62. self._username = enc.encrypt(username)
  63. self._password = enc.encrypt(password)
  64. self._url = enc.encrypt(url)
  65. self._notes = enc.encrypt(notes)
  66. self._tags = []
  67. self.set_tags(tags)
  68. def get_tags(self):
  69. tags = []
  70. enc = CryptoEngine.get()
  71. for i in self._tags:
  72. tags.append(enc.decrypt(i))
  73. return tags
  74. def set_tags(self, tags):
  75. self._tags = []
  76. enc = CryptoEngine.get()
  77. for i in tags:
  78. self._tags.append(enc.encrypt(i))
  79. def get_id(self):
  80. return self._id
  81. def set_id(self, id):
  82. self._id = id
  83. def get_username(self):
  84. """Return the username."""
  85. enc = CryptoEngine.get()
  86. return enc.decrypt(self._username)
  87. def set_username(self, username):
  88. """Set the username."""
  89. enc = CryptoEngine.get()
  90. self._username = enc.encrypt(username)
  91. def get_password(self):
  92. """Return the password."""
  93. enc = CryptoEngine.get()
  94. return enc.decrypt(self._password)
  95. def set_password(self, password):
  96. """Set the password."""
  97. enc = CryptoEngine.get()
  98. self._password = enc.encrypt(password)
  99. def get_url(self):
  100. """Return the URL."""
  101. enc = CryptoEngine.get()
  102. return enc.decrypt(self._url)
  103. def set_url(self, url):
  104. """Set the URL."""
  105. enc = CryptoEngine.get()
  106. self._url = enc.encrypt(url)
  107. def get_notes(self):
  108. """Return the Notes."""
  109. enc = CryptoEngine.get()
  110. return enc.decrypt(self._notes)
  111. def set_notes(self, notes):
  112. """Set the Notes."""
  113. enc = CryptoEngine.get()
  114. self._notes = enc.encrypt(notes)
  115. class SQLiteDatabaseReader(Database):
  116. """SQLite Database implementation"""
  117. def __init__(self):
  118. """Initialise SQLitePwmanDatabase instance."""
  119. Database.__init__(self)
  120. try:
  121. self._filename = config.get_value('Database', 'filename')
  122. except KeyError, e:
  123. raise DatabaseException(
  124. "SQLite: missing parameter [%s]" % (e))
  125. def _open(self):
  126. try:
  127. self._con = sqlite.connect(self._filename)
  128. self._cur = self._con.cursor()
  129. self._checktables()
  130. except sqlite.DatabaseError, e:
  131. raise DatabaseException("SQLite: %s" % (e))
  132. def close(self):
  133. self._cur.close()
  134. self._con.close()
  135. def getnodes(self, ids):
  136. nodes = []
  137. for i in ids:
  138. sql = "SELECT DATA FROM NODES WHERE ID = ?"
  139. try:
  140. self._cur.execute(sql, [i])
  141. row = self._cur.fetchone()
  142. if row is not None:
  143. node = cPickle.loads(str(row[0]))
  144. node.set_id(i)
  145. nodes.append(node)
  146. except sqlite.DatabaseError, e:
  147. raise DatabaseException("SQLite: %s" % (e))
  148. return nodes
  149. def listnodes(self):
  150. sql = ''
  151. params = []
  152. if len(self._filtertags) == 0:
  153. sql = "SELECT ID FROM NODES ORDER BY ID ASC"
  154. else:
  155. first = True
  156. for t in self._filtertags:
  157. if not first:
  158. sql += " INTERSECT "
  159. else:
  160. first = False
  161. sql += ("SELECT NODE FROM LOOKUP LEFT JOIN TAGS "
  162. + " ON TAG = TAGS.ID"
  163. + " WHERE TAGS.DATA = ? ")
  164. params.append(cPickle.dumps(t))
  165. try:
  166. self._cur.execute(sql, params)
  167. ids = []
  168. row = self._cur.fetchone()
  169. while (row is not None):
  170. ids.append(row[0])
  171. row = self._cur.fetchone()
  172. return ids
  173. except sqlite.DatabaseError, e:
  174. raise DatabaseException("SQLite: %s" % (e))
  175. def _commit(self):
  176. try:
  177. self._con.commit()
  178. except sqlite.DatabaseError, e:
  179. self._con.rollback()
  180. raise DatabaseException(
  181. "SQLite: Error commiting data to db [%s]" % (e))
  182. def _tagids(self, tags):
  183. ids = []
  184. for t in tags:
  185. sql = "SELECT ID FROM TAGS WHERE DATA = ?"
  186. if not isinstance(t, Tag):
  187. raise DatabaseException(
  188. "Tried to insert foreign object into database [%s]", t)
  189. data = cPickle.dumps(t)
  190. try:
  191. self._cur.execute(sql, [data])
  192. row = self._cur.fetchone()
  193. if (row is not None):
  194. ids.append(row[0])
  195. else:
  196. sql = "INSERT INTO TAGS(DATA) VALUES(?)"
  197. self._cur.execute(sql, [data])
  198. ids.append(self._cur.lastrowid)
  199. except sqlite.DatabaseError, e:
  200. raise DatabaseException("SQLite: %s" % (e))
  201. return ids
  202. def _checktables(self):
  203. """ Check if the Pwman tables exist """
  204. self._cur.execute("PRAGMA TABLE_INFO(NODES)")
  205. if (self._cur.fetchone() is None):
  206. # table doesn't exist, create it
  207. # SQLite does have constraints implemented at the moment
  208. # so datatype will just be a string
  209. self._cur.execute("CREATE TABLE NODES "
  210. "(ID INTEGER PRIMARY KEY AUTOINCREMENT,"
  211. "DATA BLOB NOT NULL)")
  212. self._cur.execute("CREATE TABLE TAGS "
  213. "(ID INTEGER PRIMARY KEY AUTOINCREMENT,"
  214. "DATA BLOB NOT NULL UNIQUE)")
  215. self._cur.execute("CREATE TABLE LOOKUP "
  216. "(NODE INTEGER NOT NULL, TAG INTEGER NOT NULL,"
  217. " PRIMARY KEY(NODE, TAG))")
  218. self._cur.execute("CREATE TABLE KEY "
  219. + "(THEKEY TEXT NOT NULL DEFAULT '')")
  220. self._cur.execute("INSERT INTO KEY VALUES('')")
  221. try:
  222. self._con.commit()
  223. except DatabaseException, e:
  224. self._con.rollback()
  225. raise e
  226. def loadkey(self):
  227. """
  228. fetch the key to database. the key is also stored
  229. encrypted.
  230. """
  231. self._cur.execute("SELECT THEKEY FROM KEY")
  232. keyrow = self._cur.fetchone()
  233. if (keyrow[0] == ''):
  234. return None
  235. else:
  236. return keyrow[0]
  237. class CLICallback(Callback):
  238. def getinput(self, question):
  239. return raw_input(question)
  240. def getsecret(self, question):
  241. return getpass.getpass(question + ":")
  242. class PwmanConvertDB(object):
  243. """
  244. Class to migrate from DB in version 0.3 to
  245. DB used in later versions.
  246. """
  247. def __init__(self, args, config):
  248. self.dbname = config.get_value('Database', 'filename')
  249. self.dbtype = config.get_value("Database", "type")
  250. print "Will convert the following Database: %s " % self.dbname
  251. if os.path.exists(config.get_value("Database", "filename")):
  252. dbver = pwman.data.factory.check_db_version(self.dbtype)
  253. self.dbver = float(dbver)
  254. backup = '.backup-%s'.join(os.path.splitext(self.dbname)) % \
  255. time.strftime(
  256. '%Y-%m-%d-%H:%M')
  257. shutil.copy(self.dbname, backup)
  258. print "backup created in ", backup
  259. def read_old_db(self):
  260. "read the old db and get all nodes"
  261. self.db = SQLiteDatabaseReader()
  262. enc = CryptoEngine.get()
  263. enc.set_callback(CLICallback())
  264. self.db.open()
  265. self.oldnodes = self.db.listnodes()
  266. self.oldnodes = self.db.getnodes(self.oldnodes)
  267. def create_new_db(self):
  268. dest = '-newdb'.join(os.path.splitext(self.dbname))
  269. if os.path.exists('-newdb'.join(os.path.splitext(self.dbname))):
  270. print "%s already exists, please move this file!" % dest
  271. sys.exit(2)
  272. self.newdb_name = '-newdb'.join(os.path.splitext(self.dbname))
  273. self.newdb = pwman.data.factory.create(self.dbtype, _NEWVERSION,
  274. self.newdb_name)
  275. self.newdb._open()
  276. def convert_nodes(self):
  277. """convert old nodes instances to new format"""
  278. self.NewNodes = []
  279. for node in self.oldnodes:
  280. username = node.get_username()
  281. password = node.get_password()
  282. url = node.get_url()
  283. notes = node.get_notes()
  284. tags = node.get_tags()
  285. tags_strings = [tag.get_name() for tag in tags]
  286. newNode = NewNode()
  287. newNode.username = username
  288. newNode.password = password
  289. newNode.url = url
  290. newNode.notes = notes
  291. tags = tags_strings
  292. newNode.tags = tags
  293. self.NewNodes.append(newNode)
  294. def save_new_nodes_to_db(self):
  295. self.newdb.addnodes(self.NewNodes)
  296. self.newdb._commit()
  297. def save_old_key(self):
  298. enc = CryptoEngine.get()
  299. self.oldkey = enc.get_cryptedkey()
  300. self.newdb.savekey(self.oldkey)
  301. def print_success(self):
  302. print """pwman successfully converted the old database to the new
  303. format.\nPlease run `pwman3 -d %s` to make sure your password and
  304. data are still correct. If you are convinced that no harm was done,
  305. update your config file to indicate the permanent location
  306. to your new database.
  307. If you found errors, please report a bug in Pwman homepage in github.
  308. """ % self.newdb_name
  309. def run(self):
  310. self.read_old_db()
  311. self.create_new_db()
  312. self.convert_nodes()
  313. self.save_new_nodes_to_db()
  314. self.save_old_key()
  315. self.print_success()