29 |
34 |
30 |
35 |
31 class PasswordManager(QObject): |
36 class PasswordManager(QObject): |
32 """ |
37 """ |
33 Class implementing the password manager. |
38 Class implementing the password manager. |
34 |
39 |
35 @signal changed() emitted to indicate a change |
40 @signal changed() emitted to indicate a change |
36 @signal passwordsSaved() emitted after the passwords were saved |
41 @signal passwordsSaved() emitted after the passwords were saved |
37 """ |
42 """ |
|
43 |
38 changed = pyqtSignal() |
44 changed = pyqtSignal() |
39 passwordsSaved = pyqtSignal() |
45 passwordsSaved = pyqtSignal() |
40 |
46 |
41 def __init__(self, parent=None): |
47 def __init__(self, parent=None): |
42 """ |
48 """ |
43 Constructor |
49 Constructor |
44 |
50 |
45 @param parent reference to the parent object (QObject) |
51 @param parent reference to the parent object (QObject) |
46 """ |
52 """ |
47 super().__init__(parent) |
53 super().__init__(parent) |
48 |
54 |
49 # setup userscript to monitor forms |
55 # setup userscript to monitor forms |
50 script = QWebEngineScript() |
56 script = QWebEngineScript() |
51 script.setName("_eric_passwordmonitor") |
57 script.setName("_eric_passwordmonitor") |
52 script.setInjectionPoint(QWebEngineScript.InjectionPoint.DocumentReady) |
58 script.setInjectionPoint(QWebEngineScript.InjectionPoint.DocumentReady) |
53 script.setWorldId(WebBrowserPage.SafeJsWorld) |
59 script.setWorldId(WebBrowserPage.SafeJsWorld) |
54 script.setRunsOnSubFrames(True) |
60 script.setRunsOnSubFrames(True) |
55 script.setSourceCode(Scripts.setupFormObserver()) |
61 script.setSourceCode(Scripts.setupFormObserver()) |
56 profile = WebBrowser.WebBrowserWindow.WebBrowserWindow.webProfile() |
62 profile = WebBrowser.WebBrowserWindow.WebBrowserWindow.webProfile() |
57 profile.scripts().insert(script) |
63 profile.scripts().insert(script) |
58 |
64 |
59 self.__logins = {} |
65 self.__logins = {} |
60 self.__loginForms = {} |
66 self.__loginForms = {} |
61 self.__never = [] |
67 self.__never = [] |
62 self.__loaded = False |
68 self.__loaded = False |
63 self.__saveTimer = AutoSaver(self, self.save) |
69 self.__saveTimer = AutoSaver(self, self.save) |
64 |
70 |
65 self.changed.connect(self.__saveTimer.changeOccurred) |
71 self.changed.connect(self.__saveTimer.changeOccurred) |
66 |
72 |
67 def clear(self): |
73 def clear(self): |
68 """ |
74 """ |
69 Public slot to clear the saved passwords. |
75 Public slot to clear the saved passwords. |
70 """ |
76 """ |
71 if not self.__loaded: |
77 if not self.__loaded: |
72 self.__load() |
78 self.__load() |
73 |
79 |
74 self.__logins = {} |
80 self.__logins = {} |
75 self.__loginForms = {} |
81 self.__loginForms = {} |
76 self.__never = [] |
82 self.__never = [] |
77 self.__saveTimer.changeOccurred() |
83 self.__saveTimer.changeOccurred() |
78 self.__saveTimer.saveIfNeccessary() |
84 self.__saveTimer.saveIfNeccessary() |
79 |
85 |
80 self.changed.emit() |
86 self.changed.emit() |
81 |
87 |
82 def getLogin(self, url, realm): |
88 def getLogin(self, url, realm): |
83 """ |
89 """ |
84 Public method to get the login credentials. |
90 Public method to get the login credentials. |
85 |
91 |
86 @param url URL to get the credentials for (QUrl) |
92 @param url URL to get the credentials for (QUrl) |
87 @param realm realm to get the credentials for (string) |
93 @param realm realm to get the credentials for (string) |
88 @return tuple containing the user name (string) and password (string) |
94 @return tuple containing the user name (string) and password (string) |
89 """ |
95 """ |
90 if not self.__loaded: |
96 if not self.__loaded: |
91 self.__load() |
97 self.__load() |
92 |
98 |
93 key = self.__createKey(url, realm) |
99 key = self.__createKey(url, realm) |
94 try: |
100 try: |
95 return self.__logins[key][0], Utilities.crypto.pwConvert( |
101 return self.__logins[key][0], Utilities.crypto.pwConvert( |
96 self.__logins[key][1], encode=False) |
102 self.__logins[key][1], encode=False |
|
103 ) |
97 except KeyError: |
104 except KeyError: |
98 return "", "" |
105 return "", "" |
99 |
106 |
100 def setLogin(self, url, realm, username, password): |
107 def setLogin(self, url, realm, username, password): |
101 """ |
108 """ |
102 Public method to set the login credentials. |
109 Public method to set the login credentials. |
103 |
110 |
104 @param url URL to set the credentials for (QUrl) |
111 @param url URL to set the credentials for (QUrl) |
105 @param realm realm to set the credentials for (string) |
112 @param realm realm to set the credentials for (string) |
106 @param username username for the login (string) |
113 @param username username for the login (string) |
107 @param password password for the login (string) |
114 @param password password for the login (string) |
108 """ |
115 """ |
109 if not self.__loaded: |
116 if not self.__loaded: |
110 self.__load() |
117 self.__load() |
111 |
118 |
112 key = self.__createKey(url, realm) |
119 key = self.__createKey(url, realm) |
113 self.__logins[key] = ( |
120 self.__logins[key] = ( |
114 username, |
121 username, |
115 Utilities.crypto.pwConvert(password, encode=True) |
122 Utilities.crypto.pwConvert(password, encode=True), |
116 ) |
123 ) |
117 self.changed.emit() |
124 self.changed.emit() |
118 |
125 |
119 def __createKey(self, url, realm): |
126 def __createKey(self, url, realm): |
120 """ |
127 """ |
121 Private method to create the key string for the login credentials. |
128 Private method to create the key string for the login credentials. |
122 |
129 |
123 @param url URL to get the credentials for (QUrl) |
130 @param url URL to get the credentials for (QUrl) |
124 @param realm realm to get the credentials for (string) |
131 @param realm realm to get the credentials for (string) |
125 @return key string (string) |
132 @return key string (string) |
126 """ |
133 """ |
127 authority = url.authority() |
134 authority = url.authority() |
128 if authority.startswith("@"): |
135 if authority.startswith("@"): |
129 authority = authority[1:] |
136 authority = authority[1:] |
130 key = ( |
137 key = ( |
131 "{0}://{1} ({2})".format(url.scheme(), authority, realm) |
138 "{0}://{1} ({2})".format(url.scheme(), authority, realm) |
132 if realm else |
139 if realm |
133 "{0}://{1}".format(url.scheme(), authority) |
140 else "{0}://{1}".format(url.scheme(), authority) |
134 ) |
141 ) |
135 return key |
142 return key |
136 |
143 |
137 def getFileName(self): |
144 def getFileName(self): |
138 """ |
145 """ |
139 Public method to get the file name of the passwords file. |
146 Public method to get the file name of the passwords file. |
140 |
147 |
141 @return name of the passwords file (string) |
148 @return name of the passwords file (string) |
142 """ |
149 """ |
143 return os.path.join(Utilities.getConfigDir(), |
150 return os.path.join(Utilities.getConfigDir(), "web_browser", "logins.xml") |
144 "web_browser", "logins.xml") |
151 |
145 |
|
146 def save(self): |
152 def save(self): |
147 """ |
153 """ |
148 Public slot to save the login entries to disk. |
154 Public slot to save the login entries to disk. |
149 """ |
155 """ |
150 if not self.__loaded: |
156 if not self.__loaded: |
151 return |
157 return |
152 |
158 |
153 from WebBrowser.WebBrowserWindow import WebBrowserWindow |
159 from WebBrowser.WebBrowserWindow import WebBrowserWindow |
|
160 |
154 if not WebBrowserWindow.isPrivate(): |
161 if not WebBrowserWindow.isPrivate(): |
155 from .PasswordWriter import PasswordWriter |
162 from .PasswordWriter import PasswordWriter |
|
163 |
156 loginFile = self.getFileName() |
164 loginFile = self.getFileName() |
157 writer = PasswordWriter() |
165 writer = PasswordWriter() |
158 if not writer.write( |
166 if not writer.write( |
159 loginFile, self.__logins, self.__loginForms, self.__never): |
167 loginFile, self.__logins, self.__loginForms, self.__never |
|
168 ): |
160 EricMessageBox.critical( |
169 EricMessageBox.critical( |
161 None, |
170 None, |
162 self.tr("Saving login data"), |
171 self.tr("Saving login data"), |
163 self.tr( |
172 self.tr( |
164 """<p>Login data could not be saved to""" |
173 """<p>Login data could not be saved to""" """ <b>{0}</b></p>""" |
165 """ <b>{0}</b></p>""" |
174 ).format(loginFile), |
166 ).format(loginFile)) |
175 ) |
167 else: |
176 else: |
168 self.passwordsSaved.emit() |
177 self.passwordsSaved.emit() |
169 |
178 |
170 def __load(self): |
179 def __load(self): |
171 """ |
180 """ |
172 Private method to load the saved login credentials. |
181 Private method to load the saved login credentials. |
173 """ |
182 """ |
174 if self.__loaded: |
183 if self.__loaded: |
175 return |
184 return |
176 |
185 |
177 loginFile = self.getFileName() |
186 loginFile = self.getFileName() |
178 if os.path.exists(loginFile): |
187 if os.path.exists(loginFile): |
179 from .PasswordReader import PasswordReader |
188 from .PasswordReader import PasswordReader |
|
189 |
180 reader = PasswordReader() |
190 reader = PasswordReader() |
181 self.__logins, self.__loginForms, self.__never = reader.read( |
191 self.__logins, self.__loginForms, self.__never = reader.read(loginFile) |
182 loginFile) |
|
183 if reader.error() != QXmlStreamReader.Error.NoError: |
192 if reader.error() != QXmlStreamReader.Error.NoError: |
184 EricMessageBox.warning( |
193 EricMessageBox.warning( |
185 None, |
194 None, |
186 self.tr("Loading login data"), |
195 self.tr("Loading login data"), |
187 self.tr("""Error when loading login data on""" |
196 self.tr( |
188 """ line {0}, column {1}:\n{2}""") |
197 """Error when loading login data on""" |
189 .format(reader.lineNumber(), |
198 """ line {0}, column {1}:\n{2}""" |
190 reader.columnNumber(), |
199 ).format( |
191 reader.errorString())) |
200 reader.lineNumber(), reader.columnNumber(), reader.errorString() |
192 |
201 ), |
|
202 ) |
|
203 |
193 self.__loaded = True |
204 self.__loaded = True |
194 |
205 |
195 def reload(self): |
206 def reload(self): |
196 """ |
207 """ |
197 Public method to reload the login data. |
208 Public method to reload the login data. |
198 """ |
209 """ |
199 if not self.__loaded: |
210 if not self.__loaded: |
200 return |
211 return |
201 |
212 |
202 self.__loaded = False |
213 self.__loaded = False |
203 self.__load() |
214 self.__load() |
204 |
215 |
205 def close(self): |
216 def close(self): |
206 """ |
217 """ |
207 Public method to close the passwords manager. |
218 Public method to close the passwords manager. |
208 """ |
219 """ |
209 self.__saveTimer.saveIfNeccessary() |
220 self.__saveTimer.saveIfNeccessary() |
210 |
221 |
211 def removePassword(self, site): |
222 def removePassword(self, site): |
212 """ |
223 """ |
213 Public method to remove a password entry. |
224 Public method to remove a password entry. |
214 |
225 |
215 @param site web site name (string) |
226 @param site web site name (string) |
216 """ |
227 """ |
217 if site in self.__logins: |
228 if site in self.__logins: |
218 del self.__logins[site] |
229 del self.__logins[site] |
219 if site in self.__loginForms: |
230 if site in self.__loginForms: |
220 del self.__loginForms[site] |
231 del self.__loginForms[site] |
221 self.changed.emit() |
232 self.changed.emit() |
222 |
233 |
223 def allSiteNames(self): |
234 def allSiteNames(self): |
224 """ |
235 """ |
225 Public method to get a list of all site names. |
236 Public method to get a list of all site names. |
226 |
237 |
227 @return sorted list of all site names (list of strings) |
238 @return sorted list of all site names (list of strings) |
228 """ |
239 """ |
229 if not self.__loaded: |
240 if not self.__loaded: |
230 self.__load() |
241 self.__load() |
231 |
242 |
232 return sorted(self.__logins.keys()) |
243 return sorted(self.__logins.keys()) |
233 |
244 |
234 def sitesCount(self): |
245 def sitesCount(self): |
235 """ |
246 """ |
236 Public method to get the number of available sites. |
247 Public method to get the number of available sites. |
237 |
248 |
238 @return number of sites (integer) |
249 @return number of sites (integer) |
239 """ |
250 """ |
240 if not self.__loaded: |
251 if not self.__loaded: |
241 self.__load() |
252 self.__load() |
242 |
253 |
243 return len(self.__logins) |
254 return len(self.__logins) |
244 |
255 |
245 def siteInfo(self, site): |
256 def siteInfo(self, site): |
246 """ |
257 """ |
247 Public method to get a reference to the named site. |
258 Public method to get a reference to the named site. |
248 |
259 |
249 @param site web site name (string) |
260 @param site web site name (string) |
250 @return tuple containing the user name (string) and password (string) |
261 @return tuple containing the user name (string) and password (string) |
251 """ |
262 """ |
252 if not self.__loaded: |
263 if not self.__loaded: |
253 self.__load() |
264 self.__load() |
254 |
265 |
255 if site not in self.__logins: |
266 if site not in self.__logins: |
256 return None |
267 return None |
257 |
268 |
258 return self.__logins[site][0], Utilities.crypto.pwConvert( |
269 return self.__logins[site][0], Utilities.crypto.pwConvert( |
259 self.__logins[site][1], encode=False) |
270 self.__logins[site][1], encode=False |
260 |
271 ) |
|
272 |
261 def formSubmitted(self, urlStr, userName, password, data, page): |
273 def formSubmitted(self, urlStr, userName, password, data, page): |
262 """ |
274 """ |
263 Public method to record login data. |
275 Public method to record login data. |
264 |
276 |
265 @param urlStr form submission URL |
277 @param urlStr form submission URL |
266 @type str |
278 @type str |
267 @param userName name of the user |
279 @param userName name of the user |
268 @type str |
280 @type str |
269 @param password user password |
281 @param password user password |
296 self.tr("Save password"), |
308 self.tr("Save password"), |
297 self.tr( |
309 self.tr( |
298 """<b>Would you like to save this password?</b><br/>""" |
310 """<b>Would you like to save this password?</b><br/>""" |
299 """To review passwords you have saved and remove""" |
311 """To review passwords you have saved and remove""" |
300 """ them, use the password management dialog of the""" |
312 """ them, use the password management dialog of the""" |
301 """ Settings menu."""), |
313 """ Settings menu.""" |
302 modal=True, parent=page.view()) |
314 ), |
|
315 modal=True, |
|
316 parent=page.view(), |
|
317 ) |
303 neverButton = mb.addButton( |
318 neverButton = mb.addButton( |
304 self.tr("Never for this site"), |
319 self.tr("Never for this site"), EricMessageBox.DestructiveRole |
305 EricMessageBox.DestructiveRole) |
320 ) |
306 noButton = mb.addButton( |
321 noButton = mb.addButton(self.tr("Not now"), EricMessageBox.RejectRole) |
307 self.tr("Not now"), EricMessageBox.RejectRole) |
|
308 mb.addButton(EricMessageBox.Yes) |
322 mb.addButton(EricMessageBox.Yes) |
309 mb.exec() |
323 mb.exec() |
310 if mb.clickedButton() == neverButton: |
324 if mb.clickedButton() == neverButton: |
311 self.__never.append(url.toString()) |
325 self.__never.append(url.toString()) |
312 return |
326 return |
313 elif mb.clickedButton() == noButton: |
327 elif mb.clickedButton() == noButton: |
314 return |
328 return |
315 |
329 |
316 self.__logins[key] = ( |
330 self.__logins[key] = ( |
317 userName, |
331 userName, |
318 Utilities.crypto.pwConvert(password, encode=True) |
332 Utilities.crypto.pwConvert(password, encode=True), |
319 ) |
333 ) |
320 from .LoginForm import LoginForm |
334 from .LoginForm import LoginForm |
|
335 |
321 form = LoginForm() |
336 form = LoginForm() |
322 form.url = url |
337 form.url = url |
323 form.name = userName |
338 form.name = userName |
324 form.postData = Utilities.crypto.pwConvert( |
339 form.postData = Utilities.crypto.pwConvert( |
325 bytes(data).decode("utf-8"), encode=True) |
340 bytes(data).decode("utf-8"), encode=True |
|
341 ) |
326 self.__loginForms[key] = form |
342 self.__loginForms[key] = form |
327 self.changed.emit() |
343 self.changed.emit() |
328 |
344 |
329 def __stripUrl(self, url): |
345 def __stripUrl(self, url): |
330 """ |
346 """ |
331 Private method to strip off all unneeded parts of a URL. |
347 Private method to strip off all unneeded parts of a URL. |
332 |
348 |
333 @param url URL to be stripped (QUrl) |
349 @param url URL to be stripped (QUrl) |
334 @return stripped URL (QUrl) |
350 @return stripped URL (QUrl) |
335 """ |
351 """ |
336 cleanUrl = QUrl(url) |
352 cleanUrl = QUrl(url) |
337 cleanUrl.setQuery("") |
353 cleanUrl.setQuery("") |
338 cleanUrl.setUserInfo("") |
354 cleanUrl.setUserInfo("") |
339 |
355 |
340 authority = cleanUrl.authority() |
356 authority = cleanUrl.authority() |
341 if authority.startswith("@"): |
357 if authority.startswith("@"): |
342 authority = authority[1:] |
358 authority = authority[1:] |
343 cleanUrl = QUrl("{0}://{1}{2}".format( |
359 cleanUrl = QUrl( |
344 cleanUrl.scheme(), authority, cleanUrl.path())) |
360 "{0}://{1}{2}".format(cleanUrl.scheme(), authority, cleanUrl.path()) |
|
361 ) |
345 cleanUrl.setFragment("") |
362 cleanUrl.setFragment("") |
346 return cleanUrl |
363 return cleanUrl |
347 |
364 |
348 def completePage(self, page): |
365 def completePage(self, page): |
349 """ |
366 """ |
350 Public slot to complete login forms with saved data. |
367 Public slot to complete login forms with saved data. |
351 |
368 |
352 @param page reference to the web page (WebBrowserPage) |
369 @param page reference to the web page (WebBrowserPage) |
353 """ |
370 """ |
354 if page is None: |
371 if page is None: |
355 return |
372 return |
356 |
373 |
357 if not self.__loaded: |
374 if not self.__loaded: |
358 self.__load() |
375 self.__load() |
359 |
376 |
360 url = page.url() |
377 url = page.url() |
361 url = self.__stripUrl(url) |
378 url = self.__stripUrl(url) |
362 key = self.__createKey(url, "") |
379 key = self.__createKey(url, "") |
363 if ( |
380 if key not in self.__loginForms or key not in self.__logins: |
364 key not in self.__loginForms or |
381 return |
365 key not in self.__logins |
382 |
366 ): |
|
367 return |
|
368 |
|
369 form = self.__loginForms[key] |
383 form = self.__loginForms[key] |
370 if form.url != url: |
384 if form.url != url: |
371 return |
385 return |
372 |
386 |
373 postData = QByteArray(Utilities.crypto.pwConvert( |
387 postData = QByteArray( |
374 form.postData, encode=False).encode("utf-8")) |
388 Utilities.crypto.pwConvert(form.postData, encode=False).encode("utf-8") |
|
389 ) |
375 script = Scripts.completeFormData(postData) |
390 script = Scripts.completeFormData(postData) |
376 page.runJavaScript(script, WebBrowserPage.SafeJsWorld) |
391 page.runJavaScript(script, WebBrowserPage.SafeJsWorld) |
377 |
392 |
378 def masterPasswordChanged(self, oldPassword, newPassword): |
393 def masterPasswordChanged(self, oldPassword, newPassword): |
379 """ |
394 """ |
380 Public slot to handle the change of the master password. |
395 Public slot to handle the change of the master password. |
381 |
396 |
382 @param oldPassword current master password (string) |
397 @param oldPassword current master password (string) |
383 @param newPassword new master password (string) |
398 @param newPassword new master password (string) |
384 """ |
399 """ |
385 if not self.__loaded: |
400 if not self.__loaded: |
386 self.__load() |
401 self.__load() |
387 |
402 |
388 progress = EricProgressDialog( |
403 progress = EricProgressDialog( |
389 self.tr("Re-encoding saved passwords..."), |
404 self.tr("Re-encoding saved passwords..."), |
390 None, 0, len(self.__logins) + len(self.__loginForms), |
405 None, |
|
406 0, |
|
407 len(self.__logins) + len(self.__loginForms), |
391 self.tr("%v/%m Passwords"), |
408 self.tr("%v/%m Passwords"), |
392 QApplication.activeModalWidget()) |
409 QApplication.activeModalWidget(), |
|
410 ) |
393 progress.setMinimumDuration(0) |
411 progress.setMinimumDuration(0) |
394 progress.setWindowTitle(self.tr("Passwords")) |
412 progress.setWindowTitle(self.tr("Passwords")) |
395 count = 0 |
413 count = 0 |
396 |
414 |
397 # step 1: do the logins |
415 # step 1: do the logins |
398 for key in self.__logins: |
416 for key in self.__logins: |
399 progress.setValue(count) |
417 progress.setValue(count) |
400 QCoreApplication.processEvents() |
418 QCoreApplication.processEvents() |
401 username, pwHash = self.__logins[key] |
419 username, pwHash = self.__logins[key] |
402 pwHash = Utilities.crypto.pwRecode( |
420 pwHash = Utilities.crypto.pwRecode(pwHash, oldPassword, newPassword) |
403 pwHash, oldPassword, newPassword) |
|
404 self.__logins[key] = (username, pwHash) |
421 self.__logins[key] = (username, pwHash) |
405 count += 1 |
422 count += 1 |
406 |
423 |
407 # step 2: do the login forms |
424 # step 2: do the login forms |
408 for key in self.__loginForms: |
425 for key in self.__loginForms: |
409 progress.setValue(count) |
426 progress.setValue(count) |
410 QCoreApplication.processEvents() |
427 QCoreApplication.processEvents() |
411 postData = self.__loginForms[key].postData |
428 postData = self.__loginForms[key].postData |
412 postData = Utilities.crypto.pwRecode( |
429 postData = Utilities.crypto.pwRecode(postData, oldPassword, newPassword) |
413 postData, oldPassword, newPassword) |
|
414 self.__loginForms[key].postData = postData |
430 self.__loginForms[key].postData = postData |
415 count += 1 |
431 count += 1 |
416 |
432 |
417 progress.setValue(len(self.__logins) + len(self.__loginForms)) |
433 progress.setValue(len(self.__logins) + len(self.__loginForms)) |
418 QCoreApplication.processEvents() |
434 QCoreApplication.processEvents() |
419 self.changed.emit() |
435 self.changed.emit() |
420 |
436 |
|
437 |
421 # |
438 # |
422 # eflag: noqa = Y113 |
439 # eflag: noqa = Y113 |