baseui.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  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, 2014 Oz Nahum Tiram <nahumoz@gmail.com>
  18. # ============================================================================
  19. from __future__ import print_function
  20. import sys
  21. import os
  22. import getpass
  23. import ast
  24. import csv
  25. import time
  26. from colorama import Fore
  27. from pwman.data.nodes import Node
  28. from pwman.ui import tools
  29. from pwman.util.crypto_engine import CryptoEngine
  30. from .base import HelpUIMixin, AliasesMixin
  31. if sys.version_info.major > 2: # pragma: no cover
  32. raw_input = input
  33. class BaseCommands(HelpUIMixin, AliasesMixin):
  34. @property
  35. def _xsel(self):
  36. if self.hasxsel:
  37. return True
  38. def error(self, exception):
  39. if (isinstance(exception, KeyboardInterrupt)):
  40. print('')
  41. else:
  42. print("Error: {0} ".format(exception))
  43. def do_copy(self, args): # pragma: no cover
  44. """copy item to clipboard"""
  45. if not self._xsel:
  46. return
  47. if not args.isdigit():
  48. print("Copy accepts only IDs ...")
  49. return
  50. ids = args.split()
  51. if len(ids) > 1:
  52. print("Can copy only 1 password at a time...")
  53. return
  54. nodes = self._db.getnodes(ids)
  55. for node in nodes:
  56. ce = CryptoEngine.get()
  57. password = ce.decrypt(node[2])
  58. tools.text_to_clipboards(password)
  59. print("erasing in 10 sec...")
  60. time.sleep(10) # TODO: this should be configurable!
  61. tools.text_to_clipboards("")
  62. def do_open(self, args): # pragma: no cover
  63. ids = self.get_ids(args)
  64. if not args:
  65. self.help_open()
  66. return
  67. nodes = self._db.getnodes(ids)
  68. for node in nodes:
  69. ce = CryptoEngine.get()
  70. url = ce.decrypt(node[3])
  71. tools.open_url(url)
  72. def do_exit(self, args): # pragma: no cover
  73. """close the text console"""
  74. self._db.close()
  75. return True
  76. def do_cls(self, args): # pragma: no cover
  77. """clear the screen"""
  78. os.system("clear")
  79. def do_edit(self, args):
  80. """edit a node"""
  81. pass
  82. def do_export(self, args):
  83. """export the database to a given format"""
  84. try:
  85. args = ast.literal_eval(args)
  86. except Exception:
  87. args = {}
  88. filename = args.get('filename', 'pwman-export.csv')
  89. delim = args.get('delimiter', ';')
  90. nodeids = self._db.listnodes()
  91. nodes = self._db.getnodes(nodeids)
  92. with open(filename, 'w') as csvfile:
  93. writer = csv.writer(csvfile, delimiter=delim)
  94. writer.writerow(['Username', 'URL', 'Password', 'Notes',
  95. 'Tags'])
  96. for node in nodes:
  97. n = Node.from_encrypted_entries(node[1], node[2], node[3],
  98. node[4],
  99. node[5:])
  100. tags = n.tags
  101. tags = ','.join(t.strip().decode() for t in tags)
  102. r = list(map(bytes.decode, [n.username, n.url, n.password,
  103. n.notes]))
  104. writer.writerow(r + [tags])
  105. print("Successfuly exported database to {}".format(
  106. os.path.join(os.getcwd(), filename)))
  107. def do_forget(self, args):
  108. """
  109. drop saved key forcing the user to re-enter the master
  110. password
  111. """
  112. enc = CryptoEngine.get()
  113. enc.forget()
  114. def do_passwd(self, args):
  115. """change the master password of the database"""
  116. pass
  117. def do_tags(self, args):
  118. """
  119. print all existing tags
  120. """
  121. ce = CryptoEngine.get()
  122. print("Tags:")
  123. tags = self._db.listtags()
  124. for t in tags:
  125. print(ce.decrypt(t).decode())
  126. def _get_tags(self, default=None, reader=raw_input):
  127. """
  128. Read tags from user input.
  129. Tags are simply returned as a list
  130. """
  131. # TODO: add method to read tags from db, so they
  132. # could be used for tab completer
  133. print("Tags: ", end="")
  134. sys.stdout.flush()
  135. taglist = sys.stdin.readline()
  136. tagstrings = taglist.split()
  137. tags = [tn for tn in tagstrings]
  138. return tags
  139. def _prep_term(self):
  140. self.do_cls('')
  141. if sys.platform != 'win32':
  142. rows, cols = tools.gettermsize()
  143. else:
  144. rows, cols = 18, 80 # fix this !
  145. cols -= 8
  146. return rows, cols
  147. def _format_line(self, tag_pad, nid="ID", user="USER", url="URL",
  148. tags="TAGS"):
  149. return ("{ID:<3} {USER:<{us}}{URL:<{ur}}{Tags:<{tg}}"
  150. "".format(ID=nid, USER=user,
  151. URL=url, Tags=tags, us=12,
  152. ur=20, tg=tag_pad - 32))
  153. def _print_node_line(self, node, rows, cols):
  154. tagstring = ','.join([t.decode() for t in node.tags])
  155. fmt = self._format_line(cols - 32, node._id, node.username.decode(),
  156. node.url.decode(),
  157. tagstring)
  158. formatted_entry = tools.typeset(fmt, Fore.YELLOW, False)
  159. print(formatted_entry)
  160. def _get_node_ids(self, args):
  161. filter = None
  162. if args:
  163. filter = args.split()[0]
  164. ce = CryptoEngine.get()
  165. filter = ce.encrypt(filter)
  166. nodeids = self._db.listnodes(filter=filter)
  167. return nodeids
  168. def do_list(self, args):
  169. """list all existing nodes in database"""
  170. rows, cols = self._prep_term()
  171. nodeids = self._get_node_ids(args)
  172. nodes = self._db.getnodes(nodeids)
  173. _nodes_inst = []
  174. # user, pass, url, notes
  175. for node in nodes:
  176. _nodes_inst.append(Node.from_encrypted_entries(
  177. node[1],
  178. node[2],
  179. node[3],
  180. node[4],
  181. node[5:]))
  182. _nodes_inst[-1]._id = node[0]
  183. head = self._format_line(cols-32)
  184. print(tools.typeset(head, Fore.YELLOW, False))
  185. for idx, node in enumerate(_nodes_inst):
  186. self._print_node_line(node, rows, cols)
  187. def _get_input(self, prompt):
  188. print(prompt, end="")
  189. sys.stdout.flush()
  190. return sys.stdin.readline().strip()
  191. def _get_secret(self):
  192. # TODO: enable old functionallity, with password generator.
  193. if sys.stdin.isatty(): # pragma: no cover
  194. p = getpass.getpass()
  195. else:
  196. p = sys.stdin.readline().rstrip()
  197. return p
  198. def _do_new(self, args):
  199. node = {}
  200. node['username'] = self._get_input("Username: ")
  201. node['password'] = self._get_secret()
  202. node['url'] = self._get_input("Url: ")
  203. node['notes'] = self._get_input("Notes: ")
  204. node['tags'] = self._get_tags()
  205. node = Node(clear_text=True, **node)
  206. self._db.add_node(node)
  207. return node
  208. def do_new(self, args): # pragma: no cover
  209. # The cmd module stops if and of do_* return something
  210. # else than None ...
  211. # This is bad for testing, so everything that is do_*
  212. # should call _do_* method which is testable
  213. self._do_new(args)
  214. def do_print(self, args):
  215. if not args.isdigit():
  216. print("print accepts only IDs ...")
  217. return
  218. # get node
  219. titles = ['Username', 'Password', 'URL', 'Notes', 'Tags']
  220. entry = "Ya"
  221. for title in titles:
  222. print("{entry_title:>{width}} {entry:<{width}}".format(
  223. entry_title=tools.typeset(title, Fore.RED), width=10,
  224. entry=entry))