Merge lp:~mandel/ubuntu-sso-client/keyring-integration into lp:ubuntu-sso-client

Proposed by Manuel de la Peña
Status: Merged
Approved by: Manuel de la Peña
Approved revision: 872
Merged at revision: 866
Proposed branch: lp:~mandel/ubuntu-sso-client/keyring-integration
Merge into: lp:ubuntu-sso-client
Prerequisite: lp:~mandel/ubuntu-sso-client/creds-dialog
Diff against target: 233 lines (+123/-11)
2 files modified
ubuntu_sso/qt/proxy_dialog.py (+39/-4)
ubuntu_sso/qt/tests/test_proxy_dialog.py (+84/-7)
To merge this branch: bzr merge lp:~mandel/ubuntu-sso-client/keyring-integration
Reviewer Review Type Date Requested Status
Roberto Alsina (community) Approve
Alejandro J. Cura (community) Approve
Review via email: mp+92532@code.launchpad.net

Commit message

Allows the creds dialog to store the credentials in the keyring.

Description of the change

Allows the creds dialog to store the credentials in the keyring.

To post a comment you must log in.
866. By Manuel de la Peña

Merged creds-dialog into keyring-integration.

867. By Manuel de la Peña

Merged creds-dialog into keyring-integration.

868. By Manuel de la Peña

Merged creds-dialog into keyring-integration.

869. By Manuel de la Peña

Typos...

Revision history for this message
Alejandro J. Cura (alecu) wrote :

Looks just fine.

review: Approve
870. By Manuel de la Peña

Use unicode.

Revision history for this message
Roberto Alsina (ralsina) wrote :

There is a problem in that using str(widget.text()) will cause a unicode exception if the user types a unicode character.

OTOH, the keyring uses urllib.urlencode on the credentials, which means we can't pass unicode to it:

>>> urllib.urlencode({u'á':1})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/urllib.py", line 1310, in urlencode
    k = quote_plus(str(k))
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe1' in position 0: ordinal not in range(128)

Also, it seems we are doing the same thing in other places. So, maybe this branch should land and a new bug be opened for that issue.

If that's the chosen path, feel free to land it with one review, since everything else looks ok.

871. By Manuel de la Peña

Ensure that we do pass bytes to the keyring.

872. By Manuel de la Peña

Merged with parent and fixed issues.

Revision history for this message
Roberto Alsina (ralsina) wrote :

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'ubuntu_sso/qt/proxy_dialog.py'
--- ubuntu_sso/qt/proxy_dialog.py 2012-02-13 16:19:18 +0000
+++ ubuntu_sso/qt/proxy_dialog.py 2012-02-13 16:19:18 +0000
@@ -17,7 +17,10 @@
1717
1818
19from PyQt4.QtGui import QDialog, QIcon19from PyQt4.QtGui import QDialog, QIcon
20from twisted.internet import defer
2021
22from ubuntu_sso.logger import setup_logging
23from ubuntu_sso.keyring import Keyring
21from ubuntu_sso.qt.ui.proxy_credentials_dialog_ui import Ui_ProxyCredsDialog24from ubuntu_sso.qt.ui.proxy_credentials_dialog_ui import Ui_ProxyCredsDialog
22from ubuntu_sso.utils.ui import (25from ubuntu_sso.utils.ui import (
23 PROXY_CREDS_DIALOG_TITLE,26 PROXY_CREDS_DIALOG_TITLE,
@@ -32,6 +35,12 @@
32 PROXY_CREDS_SAVE_BUTTON,35 PROXY_CREDS_SAVE_BUTTON,
33)36)
3437
38CREDS_ACQUIRED = 0
39USER_CANCELATION = -1
40EXCEPTION_RAISED = -2
41
42logger = setup_logging("ubuntu_sso.qt.proxy_dialog")
43
3544
36class ProxyCredsDialog(QDialog):45class ProxyCredsDialog(QDialog):
37 """"Dialog used to require the proxy credentials."""46 """"Dialog used to require the proxy credentials."""
@@ -39,6 +48,12 @@
39 def __init__(self, is_error=False, domain=None):48 def __init__(self, is_error=False, domain=None):
40 """Create a new instance."""49 """Create a new instance."""
41 QDialog.__init__(self)50 QDialog.__init__(self)
51
52 if domain is None:
53 logger.debug('Domain passed as None.')
54 domain = ''
55 self.domain = domain
56 self.keyring = Keyring()
42 self.ui = Ui_ProxyCredsDialog()57 self.ui = Ui_ProxyCredsDialog()
43 self.ui.setupUi(self)58 self.ui.setupUi(self)
44 # lets set the different basic contents for the ui59 # lets set the different basic contents for the ui
@@ -49,10 +64,6 @@
49 self.ui.error_label.setVisible(True)64 self.ui.error_label.setVisible(True)
50 else:65 else:
51 self.ui.error_label.setVisible(False)66 self.ui.error_label.setVisible(False)
52 if domain is None:
53 self.ui.domain_label.setText('')
54 else:
55 self.ui.domain_label.setText(domain)
5667
57 def _set_labels(self):68 def _set_labels(self):
58 """Set the labels translations."""69 """Set the labels translations."""
@@ -65,12 +76,36 @@
65 % PROXY_CREDS_ERROR)76 % PROXY_CREDS_ERROR)
66 self.ui.username_label.setText(PROXY_CREDS_USER_LABEL)77 self.ui.username_label.setText(PROXY_CREDS_USER_LABEL)
67 self.ui.password_label.setText(PROXY_CREDS_PSWD_LABEL)78 self.ui.password_label.setText(PROXY_CREDS_PSWD_LABEL)
79 self.ui.domain_label.setText(self.domain)
80
81 @defer.inlineCallbacks
82 def _on_save_clicked(self, *args):
83 """Save the new credentials."""
84 username = unicode(self.ui.username_entry.text()).encode('utf8')
85 password = unicode(self.ui.password_entry.text()).encode('utf8')
86 creds = dict(username=username, password=password)
87 # pylint: disable=W0703, W0612
88 try:
89 logger.debug('Save credentials as for domain %s.', self.domain)
90 yield self.keyring.set_credentials(self.domain, creds)
91 except Exception, e:
92 logger.error('Could not retrieve credentials.')
93 self.done(EXCEPTION_RAISED)
94 # pylint: disable=W0703, W0612
95 self.done(CREDS_ACQUIRED)
96
97 def _on_cancel_clicked(self, *args):
98 """End the dialog."""
99 logger.debug('User canceled credentials dialog.')
100 self.done(USER_CANCELATION)
68101
69 def _set_buttons(self):102 def _set_buttons(self):
70 """Set the labels of the buttons."""103 """Set the labels of the buttons."""
71 self.ui.help_button.setText(PROXY_CREDS_HELP_BUTTON)104 self.ui.help_button.setText(PROXY_CREDS_HELP_BUTTON)
72 self.ui.cancel_button.setText(PROXY_CREDS_CANCEL_BUTTON)105 self.ui.cancel_button.setText(PROXY_CREDS_CANCEL_BUTTON)
106 self.ui.cancel_button.clicked.connect(self._on_cancel_clicked)
73 self.ui.save_button.setText(PROXY_CREDS_SAVE_BUTTON)107 self.ui.save_button.setText(PROXY_CREDS_SAVE_BUTTON)
108 self.ui.save_button.clicked.connect(self._on_save_clicked)
74109
75 def _set_icon(self):110 def _set_icon(self):
76 """Set the icon used in the dialog."""111 """Set the icon used in the dialog."""
77112
=== modified file 'ubuntu_sso/qt/tests/test_proxy_dialog.py'
--- ubuntu_sso/qt/tests/test_proxy_dialog.py 2012-02-13 16:19:18 +0000
+++ ubuntu_sso/qt/tests/test_proxy_dialog.py 2012-02-13 16:19:18 +0000
@@ -20,25 +20,48 @@
2020
21from ubuntu_sso.qt import proxy_dialog21from ubuntu_sso.qt import proxy_dialog
2222
23# pylint: disable=E1101,23# pylint: disable=E1101,W0212,
2424
2525
26class FakeTextWidget(object):26class FakeSignal(object):
27 """A fake qt signal."""
28
29 def __init__(self):
30 """Create a new instance."""
31 self.called = []
32
33 def connect(self, cb):
34 """Connect signals."""
35 self.called.append(('connect', cb))
36
37
38class FakeWidget(object):
27 """A fake widget that contains text."""39 """A fake widget that contains text."""
2840
29 def __init__(self, object_name, called):41 def __init__(self, object_name, called, text=''):
30 """Create a new instance."""42 """Create a new instance."""
31 self.object_name = object_name43 self.object_name = object_name
32 self.called = called44 self.called = called
45 self.internal_text = text
46 self.clicked = FakeSignal()
3347
34 # pylint: disable=C010348 # pylint: disable=C0103
35 def setText(self, text):49 def setText(self, text):
36 """Set the text of the widget."""50 """Set the text of the widget."""
51 self.internal_text = text
37 if not self.object_name in self.called:52 if not self.object_name in self.called:
38 self.called[self.object_name] = [('setText', text)]53 self.called[self.object_name] = [('setText', text)]
39 else:54 else:
40 self.called[self.object_name].append(('setText', text))55 self.called[self.object_name].append(('setText', text))
4156
57 def text(self):
58 """Return the text."""
59 if not self.object_name in self.called:
60 self.called[self.object_name] = [('text',)]
61 else:
62 self.called[self.object_name].append(('text',))
63 return self.internal_text
64
42 def setPixmap(self, pixmap):65 def setPixmap(self, pixmap):
43 """Set a pixmap."""66 """Set a pixmap."""
44 if not self.object_name in self.called:67 if not self.object_name in self.called:
@@ -52,6 +75,7 @@
52 self.called[self.object_name] = [('setVisible', visible)]75 self.called[self.object_name] = [('setVisible', visible)]
53 else:76 else:
54 self.called[self.object_name].append(('setVisible', visible))77 self.called[self.object_name].append(('setVisible', visible))
78
55 # pylint: enable=C010379 # pylint: enable=C0103
5680
5781
@@ -62,12 +86,13 @@
62 'error_label', 'username_label', 'password_label',86 'error_label', 'username_label', 'password_label',
63 'domain_label', 'logo_label')87 'domain_label', 'logo_label')
64 ui_buttons = ('help_button', 'cancel_button', 'save_button')88 ui_buttons = ('help_button', 'cancel_button', 'save_button')
89 ui_entries = ('username_entry', 'password_entry')
6590
66 def __init__(self):91 def __init__(self):
67 """Create a new instance."""92 """Create a new instance."""
68 self.called = {}93 self.called = {}
69 for name in self.ui_labels + self.ui_buttons:94 for name in self.ui_labels + self.ui_buttons + self.ui_entries:
70 setattr(self, name, FakeTextWidget(name, self.called))95 setattr(self, name, FakeWidget(name, self.called))
7196
72 # pylint: disable=C010397 # pylint: disable=C0103
73 def setupUi(self, dialog):98 def setupUi(self, dialog):
@@ -121,6 +146,11 @@
121 dialog = proxy_dialog.ProxyCredsDialog()146 dialog = proxy_dialog.ProxyCredsDialog()
122 for label in dialog.ui.ui_buttons:147 for label in dialog.ui.ui_buttons:
123 self.assertIn(label, dialog.ui.called)148 self.assertIn(label, dialog.ui.called)
149 # assert that the clicked was set
150 self.assertIn(('connect', dialog._on_save_clicked),
151 dialog.ui.save_button.clicked.called)
152 self.assertIn(('connect', dialog._on_cancel_clicked),
153 dialog.ui.cancel_button.clicked.called)
124154
125 def test_domain_empty(self):155 def test_domain_empty(self):
126 """Test that if the domain is not passed we set ''"""156 """Test that if the domain is not passed we set ''"""
@@ -156,3 +186,50 @@
156 self.icon.called)186 self.icon.called)
157 self.assertIn(('setPixmap', self.icon),187 self.assertIn(('setPixmap', self.icon),
158 dialog.ui.called['logo_label'])188 dialog.ui.called['logo_label'])
189
190 def test_on_cancel_clicked(self):
191 """Test the on cancel clicked event."""
192 dialog = proxy_dialog.ProxyCredsDialog()
193 called = []
194
195 def fake_done(result):
196 """Fake the done method."""
197 called.append(('done', result))
198
199 self.patch(dialog, 'done', fake_done)
200
201 dialog._on_cancel_clicked()
202 self.assertIn(('done', proxy_dialog.USER_CANCELATION), called)
203
204 def assert_save_button(self, set_creds_callback, result_number):
205 """Test the save button execution."""
206 dialog = proxy_dialog.ProxyCredsDialog()
207 called = []
208
209 def fake_done(result):
210 """Fake the done method."""
211 called.append(('done', result))
212
213 self.patch(dialog, 'done', fake_done)
214
215 def fake_set_credentials(app_name, creds):
216 """Fake an error."""
217 called.append(('set_credentials', app_name, creds))
218 return set_creds_callback()
219
220 self.patch(dialog.keyring, 'set_credentials', fake_set_credentials)
221
222 dialog._on_save_clicked()
223 self.assertIn(('done', result_number), called)
224
225 def test_on_save_clicked_exception(self):
226 """Test that the exception is handled correctly."""
227 set_creds_cb = lambda: defer.fail(Exception())
228 result_number = proxy_dialog.EXCEPTION_RAISED
229 self.assert_save_button(set_creds_cb, result_number)
230
231 def test_on_save_clicked_correct(self):
232 """Test that we do save the creds."""
233 set_creds_cb = lambda: defer.succeed(True)
234 result_number = proxy_dialog.CREDS_ACQUIRED
235 self.assert_save_button(set_creds_cb, result_number)

Subscribers

People subscribed via source and target branches