postgresql.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  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. """Postgresql Database implementation."""
  22. import sys
  23. if sys.version_info.major > 2: # pragma: no cover
  24. from urllib import parse as urlparse
  25. else:
  26. from urlparse import urlparse
  27. import psycopg2 as pg
  28. from pwman.data.database import Database, DatabaseException, __DB_FORMAT__
  29. class PostgresqlDatabase(Database):
  30. """
  31. Postgresql Database implementation
  32. This assumes that your database admin has created a pwman database
  33. for you and shared the user name and password with you.
  34. This driver send no clear text on wire. ONLY excrypted stuff is sent
  35. between the client and the server.
  36. Encryption and decryption are happening on your localhost, not on
  37. the Postgresql server.
  38. """
  39. @classmethod
  40. def check_db_version(cls, user, dbname='pwman'):
  41. """
  42. Check the database version
  43. """
  44. con = pg.connect("dbname=pwman user=%s" % user)
  45. cur = con.cursor()
  46. try:
  47. cur.execute("SELECT VERSION from DBVERSION")
  48. version = cur.fetchone()
  49. return version
  50. except pg.ProgrammingError:
  51. con.rollback()
  52. raise DatabaseException("Something seems fishy with the DB")
  53. def __init__(self, pgsqluri, dbformat=__DB_FORMAT__):
  54. """
  55. Initialise PostgresqlDatabase instance.
  56. """
  57. self._pgsqluri = pgsqluri
  58. self.dbversion = dbformat
  59. def _open(self):
  60. u = urlparse(self._pgsqluri)
  61. self._con = pg.connect(database=u.path[1:], user=u.username,
  62. password=u.password, host=u.hostname)
  63. self._cur = self._con.cursor()
  64. self._create_tables()
  65. def _get_tag(self, tagcipher):
  66. sql_search = "SELECT ID FROM TAG WHERE DATA = %s"
  67. self._cur.execute(sql_search, ([tagcipher]))
  68. rv = self._cur.fetchone()
  69. return rv
  70. def _get_or_create_tag(self, tagcipher):
  71. rv = self._get_tag(tagcipher)
  72. if rv:
  73. return rv[0]
  74. else:
  75. sql_insert = "INSERT INTO TAG(DATA) VALUES(%s) RETURNING ID"
  76. self._cur.execute(sql_insert, ([tagcipher]))
  77. rid = self._cur.fetchone()[0]
  78. return rid
  79. def _clean_orphans(self):
  80. clean = ("delete from tag where not exists "
  81. "(select 'x' from lookup l where l.tagid = tag.id)")
  82. self._cur.execute(clean)
  83. self._con.commit()
  84. def close(self):
  85. self._clean_orphans()
  86. self._cur.close()
  87. self._con.close()
  88. def listnodes(self, filter=None):
  89. if not filter:
  90. sql_all = "SELECT ID FROM NODE"
  91. self._cur.execute(sql_all)
  92. ids = self._cur.fetchall()
  93. return [id[0] for id in ids]
  94. else:
  95. tagid = self._get_tag(filter)
  96. if not tagid:
  97. return []
  98. sql_filter = "SELECT NODEID FROM LOOKUP WHERE TAGID = %s "
  99. self._cur.execute(sql_filter, (tagid))
  100. self._con.commit()
  101. ids = self._cur.fetchall()
  102. return [id[0] for id in ids]
  103. def editnode(self, nid, **kwargs):
  104. pass
  105. def add_node(self, node):
  106. sql = ("INSERT INTO NODE(USERNAME, PASSWORD, URL, NOTES)"
  107. "VALUES(%s, %s, %s, %s)")
  108. node_tags = list(node)
  109. node, tags = node_tags[:4], node_tags[-1]
  110. self._cur.execute(sql, (node))
  111. #self._setnodetags(self._cur.lastrowid, tags)
  112. self._con.commit()
  113. def getnodes(self, ids):
  114. sql = "SELECT * FROM NODE WHERE ID IN ({})".format(','.join('%s' for
  115. i in ids))
  116. self._cur.execute(sql, (ids))
  117. nodes = self._cur.fetchall()
  118. nodes_w_tags = []
  119. for node in nodes:
  120. #tags = list(self._get_node_tags(node))
  121. tags = []
  122. nodes_w_tags.append(list(node) + tags)
  123. return nodes_w_tags
  124. def removenodes(self, nodes):
  125. pass
  126. def listtags(self):
  127. self._clean_orphans()
  128. get_tags = "select data from tag"
  129. self._cur.execute(get_tags)
  130. tags = self._cur.fetchall()
  131. if tags:
  132. return [t[0] for t in tags]
  133. return []
  134. def _create_tables(self):
  135. try:
  136. self._cur.execute("SELECT 1 from DBVERSION")
  137. version = self._cur.fetchone()
  138. if version:
  139. return
  140. except pg.ProgrammingError:
  141. self._con.rollback()
  142. try:
  143. self._cur.execute("CREATE TABLE NODE(ID SERIAL PRIMARY KEY, "
  144. "USERNAME TEXT NOT NULL, "
  145. "PASSWORD TEXT NOT NULL, "
  146. "URL TEXT NOT NULL, "
  147. "NOTES TEXT NOT NULL"
  148. ")")
  149. self._cur.execute("CREATE TABLE TAG"
  150. "(ID SERIAL PRIMARY KEY,"
  151. "DATA TEXT NOT NULL UNIQUE)")
  152. self._cur.execute("CREATE TABLE LOOKUP ("
  153. "nodeid SERIAL REFERENCES NODE(ID),"
  154. "tagid SERIAL REFERENCES TAG(ID)"
  155. ")")
  156. self._cur.execute("CREATE TABLE CRYPTO "
  157. "(SEED TEXT, DIGEST TEXT)")
  158. self._cur.execute("CREATE TABLE DBVERSION("
  159. "VERSION TEXT NOT NULL DEFAULT {}"
  160. ")".format(__DB_FORMAT__))
  161. self._cur.execute("INSERT INTO DBVERSION VALUES(%s)",
  162. (self.dbversion,))
  163. self._con.commit()
  164. except pg.ProgrammingError:
  165. self._con.rollback()
  166. def save_crypto_info(self, seed, digest):
  167. """save the random seed and the digested key"""
  168. self._cur.execute("DELETE FROM CRYPTO")
  169. self._cur.execute("INSERT INTO CRYPTO VALUES(%s, %s)", (seed, digest))
  170. self._con.commit()
  171. def fetch_crypto_info(self):
  172. self._cur.execute("SELECT * FROM CRYPTO")
  173. row = self._cur.fetchone()
  174. return row
  175. def savekey(self, key):
  176. salt, digest = key.split('$6$')
  177. sql = "INSERT INTO CRYPTO(SEED, DIGEST) VALUES(%s,%s)"
  178. self._cur.execute("DELETE FROM CRYPTO")
  179. self._cur.execute(sql, (salt, digest))
  180. self._digest = digest.encode('utf-8')
  181. self._salt = salt.encode('utf-8')
  182. self._con.commit()
  183. def loadkey(self):
  184. sql = "SELECT * FROM CRYPTO"
  185. try:
  186. self._cur.execute(sql)
  187. seed, digest = self._cur.fetchone()
  188. return seed + u'$6$' + digest
  189. except TypeError:
  190. return None