7 Module implementing the password manager. |
7 Module implementing the password manager. |
8 """ |
8 """ |
9 |
9 |
10 import os |
10 import os |
11 |
11 |
12 from PyQt4.QtCore import pyqtSignal, QObject, QByteArray, QUrl, QCoreApplication, \ |
12 from PyQt4.QtCore import pyqtSignal, QObject, QByteArray, QUrl, \ |
13 QXmlStreamReader |
13 QCoreApplication, QXmlStreamReader |
14 from PyQt4.QtGui import QProgressDialog, QApplication |
14 from PyQt4.QtGui import QProgressDialog, QApplication |
15 from PyQt4.QtNetwork import QNetworkRequest |
15 from PyQt4.QtNetwork import QNetworkRequest |
16 from PyQt4.QtWebKit import QWebSettings, QWebPage |
16 from PyQt4.QtWebKit import QWebSettings, QWebPage |
17 |
17 |
18 from E5Gui import E5MessageBox |
18 from E5Gui import E5MessageBox |
97 """ |
97 """ |
98 if not self.__loaded: |
98 if not self.__loaded: |
99 self.__load() |
99 self.__load() |
100 |
100 |
101 key = self.__createKey(url, realm) |
101 key = self.__createKey(url, realm) |
102 self.__logins[key] = (username, Utilities.crypto.pwConvert(password, encode=True)) |
102 self.__logins[key] = ( |
|
103 username, |
|
104 Utilities.crypto.pwConvert(password, encode=True) |
|
105 ) |
103 self.changed.emit() |
106 self.changed.emit() |
104 |
107 |
105 def __createKey(self, url, realm): |
108 def __createKey(self, url, realm): |
106 """ |
109 """ |
107 Private method to create the key string for the login credentials. |
110 Private method to create the key string for the login credentials. |
109 @param url URL to get the credentials for (QUrl) |
112 @param url URL to get the credentials for (QUrl) |
110 @param realm realm to get the credentials for (string) |
113 @param realm realm to get the credentials for (string) |
111 @return key string (string) |
114 @return key string (string) |
112 """ |
115 """ |
113 if realm: |
116 if realm: |
114 key = "{0}://{1} ({2})".format(url.scheme(), url.authority(), realm) |
117 key = "{0}://{1} ({2})".format( |
|
118 url.scheme(), url.authority(), realm) |
115 else: |
119 else: |
116 key = "{0}://{1}".format(url.scheme(), url.authority()) |
120 key = "{0}://{1}".format(url.scheme(), url.authority()) |
117 return key |
121 return key |
118 |
122 |
119 def getFileName(self): |
123 def getFileName(self): |
132 return |
136 return |
133 |
137 |
134 from .PasswordWriter import PasswordWriter |
138 from .PasswordWriter import PasswordWriter |
135 loginFile = self.getFileName() |
139 loginFile = self.getFileName() |
136 writer = PasswordWriter() |
140 writer = PasswordWriter() |
137 if not writer.write(loginFile, self.__logins, self.__loginForms, self.__never): |
141 if not writer.write( |
|
142 loginFile, self.__logins, self.__loginForms, self.__never): |
138 E5MessageBox.critical(None, |
143 E5MessageBox.critical(None, |
139 self.trUtf8("Saving login data"), |
144 self.trUtf8("Saving login data"), |
140 self.trUtf8("""<p>Login data could not be saved to <b>{0}</b></p>""" |
145 self.trUtf8( |
141 ).format(loginFile)) |
146 """<p>Login data could not be saved to <b>{0}</b></p>""" |
|
147 ).format(loginFile)) |
142 else: |
148 else: |
143 self.passwordsSaved.emit() |
149 self.passwordsSaved.emit() |
144 |
150 |
145 def __load(self): |
151 def __load(self): |
146 """ |
152 """ |
150 if not os.path.exists(loginFile): |
156 if not os.path.exists(loginFile): |
151 self.__loadNonXml(os.path.splitext(loginFile)[0]) |
157 self.__loadNonXml(os.path.splitext(loginFile)[0]) |
152 else: |
158 else: |
153 from .PasswordReader import PasswordReader |
159 from .PasswordReader import PasswordReader |
154 reader = PasswordReader() |
160 reader = PasswordReader() |
155 self.__logins, self.__loginForms, self.__never = reader.read(loginFile) |
161 self.__logins, self.__loginForms, self.__never = \ |
|
162 reader.read(loginFile) |
156 if reader.error() != QXmlStreamReader.NoError: |
163 if reader.error() != QXmlStreamReader.NoError: |
157 E5MessageBox.warning(None, |
164 E5MessageBox.warning(None, |
158 self.trUtf8("Loading login data"), |
165 self.trUtf8("Loading login data"), |
159 self.trUtf8("""Error when loading login data on""" |
166 self.trUtf8("""Error when loading login data on""" |
160 """ line {0}, column {1}:\n{2}""")\ |
167 """ line {0}, column {1}:\n{2}""")\ |
186 """<p>Reason: {1}</p>""")\ |
193 """<p>Reason: {1}</p>""")\ |
187 .format(loginFile, str(err))) |
194 .format(loginFile, str(err))) |
188 return |
195 return |
189 |
196 |
190 data = [] |
197 data = [] |
191 section = 0 # 0 = login data, 1 = forms data, 2 = never store info |
198 section = 0 # 0 = login data, 1 = forms data, |
|
199 # 2 = never store info |
192 for line in lines.splitlines(): |
200 for line in lines.splitlines(): |
193 if line == self.FORMS: |
201 if line == self.FORMS: |
194 section = 1 |
202 section = 1 |
195 continue |
203 continue |
196 elif line == self.NEVER: |
204 elif line == self.NEVER: |
202 data.append(line) |
210 data.append(line) |
203 else: |
211 else: |
204 if len(data) != 3: |
212 if len(data) != 3: |
205 E5MessageBox.critical(None, |
213 E5MessageBox.critical(None, |
206 self.trUtf8("Loading login data"), |
214 self.trUtf8("Loading login data"), |
207 self.trUtf8("""<p>Login data could not be loaded """ |
215 self.trUtf8( |
208 """from <b>{0}</b></p>""" |
216 """<p>Login data could not be loaded """ |
209 """<p>Reason: Wrong input format</p>""")\ |
217 """from <b>{0}</b></p>""" |
|
218 """<p>Reason: Wrong input format</p>""")\ |
210 .format(loginFile)) |
219 .format(loginFile)) |
211 return |
220 return |
212 self.__logins[data[0]] = (data[1], data[2]) |
221 self.__logins[data[0]] = (data[1], data[2]) |
213 data = [] |
222 data = [] |
214 |
223 |
371 mb = E5MessageBox.E5MessageBox(E5MessageBox.Question, |
380 mb = E5MessageBox.E5MessageBox(E5MessageBox.Question, |
372 self.trUtf8("Save password"), |
381 self.trUtf8("Save password"), |
373 self.trUtf8( |
382 self.trUtf8( |
374 """<b>Would you like to save this password?</b><br/>""" |
383 """<b>Would you like to save this password?</b><br/>""" |
375 """To review passwords you have saved and remove them, """ |
384 """To review passwords you have saved and remove them, """ |
376 """use the password management dialog of the Settings menu."""), |
385 """use the password management dialog of the Settings""" |
|
386 """ menu."""), |
377 modal=True) |
387 modal=True) |
378 neverButton = mb.addButton( |
388 neverButton = mb.addButton( |
379 self.trUtf8("Never for this site"), E5MessageBox.DestructiveRole) |
389 self.trUtf8("Never for this site"), |
380 noButton = mb.addButton(self.trUtf8("Not now"), E5MessageBox.RejectRole) |
390 E5MessageBox.DestructiveRole) |
|
391 noButton = mb.addButton( |
|
392 self.trUtf8("Not now"), E5MessageBox.RejectRole) |
381 mb.addButton(E5MessageBox.Yes) |
393 mb.addButton(E5MessageBox.Yes) |
382 mb.exec_() |
394 mb.exec_() |
383 if mb.clickedButton() == neverButton: |
395 if mb.clickedButton() == neverButton: |
384 self.__never.append(url.toString()) |
396 self.__never.append(url.toString()) |
385 return |
397 return |
398 elif password == "" and \ |
410 elif password == "" and \ |
399 type_ == "password": |
411 type_ == "password": |
400 password = element[1] |
412 password = element[1] |
401 form.elements[index] = (element[0], "--PASSWORD--") |
413 form.elements[index] = (element[0], "--PASSWORD--") |
402 if user and password: |
414 if user and password: |
403 self.__logins[key] = (user, Utilities.crypto.pwConvert(password, encode=True)) |
415 self.__logins[key] = \ |
|
416 (user, Utilities.crypto.pwConvert(password, encode=True)) |
404 self.__loginForms[key] = form |
417 self.__loginForms[key] = form |
405 self.changed.emit() |
418 self.changed.emit() |
406 |
419 |
407 def __stripUrl(self, url): |
420 def __stripUrl(self, url): |
408 """ |
421 """ |
421 """ |
434 """ |
422 Private method to find the form used for logging in. |
435 Private method to find the form used for logging in. |
423 |
436 |
424 @param webPage reference to the web page (QWebPage) |
437 @param webPage reference to the web page (QWebPage) |
425 @param data data to be sent (QByteArray) |
438 @param data data to be sent (QByteArray) |
426 @keyparam boundary boundary string (QByteArray) for multipart encoded data, |
439 @keyparam boundary boundary string (QByteArray) for multipart |
427 None for urlencoded data |
440 encoded data, None for urlencoded data |
428 @return parsed form (LoginForm) |
441 @return parsed form (LoginForm) |
429 """ |
442 """ |
430 from .LoginForm import LoginForm |
443 from .LoginForm import LoginForm |
431 form = LoginForm() |
444 form = LoginForm() |
432 if boundary is not None: |
445 if boundary is not None: |
544 for element in form.elements: |
557 for element in form.elements: |
545 name = element[0] |
558 name = element[0] |
546 value = element[1] |
559 value = element[1] |
547 |
560 |
548 disabled = page.mainFrame().evaluateJavaScript( |
561 disabled = page.mainFrame().evaluateJavaScript( |
549 'document.forms[{0}].elements["{1}"].disabled'.format(formName, name)) |
562 'document.forms[{0}].elements["{1}"].disabled'.format( |
|
563 formName, name)) |
550 if disabled: |
564 if disabled: |
551 continue |
565 continue |
552 |
566 |
553 readOnly = page.mainFrame().evaluateJavaScript( |
567 readOnly = page.mainFrame().evaluateJavaScript( |
554 'document.forms[{0}].elements["{1}"].readOnly'.format(formName, name)) |
568 'document.forms[{0}].elements["{1}"].readOnly'.format( |
|
569 formName, name)) |
555 if readOnly: |
570 if readOnly: |
556 continue |
571 continue |
557 |
572 |
558 type_ = page.mainFrame().evaluateJavaScript( |
573 type_ = page.mainFrame().evaluateJavaScript( |
559 'document.forms[{0}].elements["{1}"].type'.format(formName, name)) |
574 'document.forms[{0}].elements["{1}"].type'.format( |
|
575 formName, name)) |
560 if type_ == "" or \ |
576 if type_ == "" or \ |
561 type_ in ["hidden", "reset", "submit"]: |
577 type_ in ["hidden", "reset", "submit"]: |
562 continue |
578 continue |
563 if type_ == "password": |
579 if type_ == "password": |
564 value = Utilities.crypto.pwConvert(self.__logins[key][1], encode=False) |
580 value = Utilities.crypto.pwConvert( |
|
581 self.__logins[key][1], encode=False) |
565 setType = type_ == "checkbox" and "checked" or "value" |
582 setType = type_ == "checkbox" and "checked" or "value" |
566 value = value.replace("\\", "\\\\") |
583 value = value.replace("\\", "\\\\") |
567 value = value.replace('"', '\\"') |
584 value = value.replace('"', '\\"') |
568 javascript = 'document.forms[{0}].elements["{1}"].{2}="{3}";'.format( |
585 javascript = \ |
569 formName, name, setType, value) |
586 'document.forms[{0}].elements["{1}"].{2}="{3}";'.format( |
|
587 formName, name, setType, value) |
570 page.mainFrame().evaluateJavaScript(javascript) |
588 page.mainFrame().evaluateJavaScript(javascript) |
571 |
589 |
572 def masterPasswordChanged(self, oldPassword, newPassword): |
590 def masterPasswordChanged(self, oldPassword, newPassword): |
573 """ |
591 """ |
574 Public slot to handle the change of the master password. |
592 Public slot to handle the change of the master password. |
577 @param newPassword new master password (string) |
595 @param newPassword new master password (string) |
578 """ |
596 """ |
579 if not self.__loaded: |
597 if not self.__loaded: |
580 self.__load() |
598 self.__load() |
581 |
599 |
582 progress = QProgressDialog(self.trUtf8("Re-encoding saved passwords..."), |
600 progress = QProgressDialog( |
|
601 self.trUtf8("Re-encoding saved passwords..."), |
583 None, 0, len(self.__logins), QApplication.activeModalWidget()) |
602 None, 0, len(self.__logins), QApplication.activeModalWidget()) |
584 progress.setMinimumDuration(0) |
603 progress.setMinimumDuration(0) |
585 count = 0 |
604 count = 0 |
586 |
605 |
587 for key in self.__logins: |
606 for key in self.__logins: |