postgresql.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  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) 2015 Oz Nahum <nahumoz@gmail.com>
  18. # ============================================================================
  19. # Copyright (C) 2006 Ivan Kelly <ivan@ivankelly.net>
  20. # ============================================================================
  21. """Postgresql Database implementation."""
  22. import psycopg2 as pg
  23. from pwman.data.database import Database, __DB_FORMAT__
  24. from pwman.util.crypto_engine import CryptoEngine
  25. class PostgresqlDatabase(Database):
  26. """
  27. Postgresql Database implementation
  28. This assumes that your database admin has created a pwman database
  29. for you and shared the user name and password with you.
  30. This driver send no clear text on wire. ONLY excrypted stuff is sent
  31. between the client and the server.
  32. Encryption and decryption are happening on your localhost, not on
  33. the Postgresql server.
  34. """
  35. @classmethod
  36. def check_db_version(cls, dburi):
  37. """
  38. Check the database version
  39. """
  40. con = pg.connect(dburi)
  41. cur = con.cursor()
  42. try:
  43. cur.execute("SELECT VERSION from DBVERSION")
  44. version = cur.fetchone()
  45. cur.close()
  46. con.close()
  47. return version[-1]
  48. except pg.ProgrammingError:
  49. con.rollback()
  50. return __DB_FORMAT__
  51. def __init__(self, pgsqluri, dbformat=__DB_FORMAT__):
  52. """
  53. Initialise PostgresqlDatabase instance.
  54. """
  55. self._pgsqluri = pgsqluri
  56. self.dbversion = dbformat
  57. self._sub = "%s"
  58. self._list_nodes_sql = "SELECT NODEID FROM LOOKUP WHERE TAGID = %s "
  59. self._add_node_sql = ('INSERT INTO NODE(USERNAME, PASSWORD, URL, '
  60. 'NOTES) VALUES(%s, %s, %s, %s) RETURNING ID')
  61. self._insert_tag_sql = "INSERT INTO TAG(DATA) VALUES(%s) RETURNING ID"
  62. self.ProgrammingError = pg.ProgrammingError
  63. self._data_wrapper = lambda x: pg.Binary(x)
  64. def _open(self):
  65. self._con = pg.connect(self._pgsqluri.geturl())
  66. self._cur = self._con.cursor()
  67. self._create_tables()
  68. def _get_tag(self, tagcipher):
  69. sql_search = "SELECT * FROM TAG"
  70. self._cur.execute(sql_search)
  71. ce = CryptoEngine.get()
  72. try:
  73. tag = ce.decrypt(tagcipher)
  74. encrypted = True
  75. except Exception:
  76. tag = tagcipher
  77. encrypted = False
  78. rv = self._cur.fetchall()
  79. for idx, cipher in rv:
  80. cipher = cipher.tobytes()
  81. if encrypted and tag == ce.decrypt(cipher):
  82. return idx
  83. elif tag == cipher:
  84. return idx
  85. def _create_tables(self):
  86. if self._check_tables():
  87. return
  88. try:
  89. self._cur.execute("CREATE TABLE NODE(ID SERIAL PRIMARY KEY, "
  90. "USERNAME BYTEA NOT NULL, "
  91. "PASSWORD BYTEA NOT NULL, "
  92. "URL BYTEA NOT NULL, "
  93. "NOTES BYTEA NOT NULL"
  94. ")")
  95. self._cur.execute("CREATE TABLE TAG"
  96. "(ID SERIAL PRIMARY KEY,"
  97. "DATA BYTEA NOT NULL)")
  98. self._cur.execute("CREATE TABLE LOOKUP ("
  99. "nodeid INTEGER NOT NULL REFERENCES NODE(ID),"
  100. "tagid INTEGER NOT NULL REFERENCES TAG(ID)"
  101. ")")
  102. self._cur.execute("CREATE TABLE CRYPTO "
  103. "(SEED BYTEA, DIGEST BYTEA)")
  104. self._cur.execute("CREATE TABLE DBVERSION("
  105. "VERSION TEXT NOT NULL)")
  106. self._cur.execute("INSERT INTO DBVERSION VALUES(%s)",
  107. (self.dbversion,))
  108. self._con.commit()
  109. except Exception: # pragma: no cover
  110. self._con.rollback()
  111. def savekey(self, key):
  112. salt, digest = key.split('$6$')
  113. try:
  114. salt, digest = salt.encode(), digest.encode()
  115. except AttributeError:
  116. pass
  117. sql = "INSERT INTO CRYPTO(SEED, DIGEST) VALUES({},{})".format(self._sub, # noqa
  118. self._sub) # noqa
  119. self._cur.execute("DELETE FROM CRYPTO")
  120. self._cur.execute(sql, list(map(self._data_wrapper, (salt, digest))))
  121. self._digest = digest
  122. self._salt = salt
  123. self._con.commit()
  124. def loadkey(self):
  125. """
  126. return _keycrypted
  127. """
  128. sql = "SELECT * FROM CRYPTO"
  129. try:
  130. self._cur.execute(sql)
  131. seed, digest = self._cur.fetchone()
  132. return seed.tobytes() + b'$6$' + digest.tobytes()
  133. except TypeError: # pragma: no cover
  134. return None
  135. def getnodes(self, ids):
  136. if ids:
  137. sql = ("SELECT * FROM NODE WHERE ID IN ({})"
  138. "".format(','.join(self._sub for i in ids)))
  139. else:
  140. sql = "SELECT * FROM NODE"
  141. self._cur.execute(sql, (ids))
  142. nodes = self._cur.fetchall()
  143. if not nodes:
  144. return []
  145. nodes_w_tags = []
  146. for node in nodes:
  147. tags = [t.tobytes() for t in self._get_node_tags(node)]
  148. nodes_w_tags.append([node[0]] + [item.tobytes() for item in node[1:]] + tags)
  149. return nodes_w_tags
  150. def listtags(self):
  151. self._clean_orphans()
  152. get_tags = "select DATA from TAG"
  153. self._cur.execute(get_tags)
  154. tags = self._cur.fetchall()
  155. if tags:
  156. return [t[0].tobytes() for t in tags]
  157. return [] # pragma: no cover