Merge lp:~nataliabidart/ubuntu-sso-client/stable-3-0-update-2.99.91 into lp:ubuntu-sso-client/stable-3-0

Proposed by Natalia Bidart on 2012-03-20
Status: Merged
Approved by: Natalia Bidart on 2012-03-21
Approved revision: 831
Merged at revision: 830
Proposed branch: lp:~nataliabidart/ubuntu-sso-client/stable-3-0-update-2.99.91
Merge into: lp:ubuntu-sso-client/stable-3-0
Diff against target: 5121 lines (+2191/-925)
62 files modified
bin/ubuntu-sso-login-qt (+6/-3)
bin/ubuntu-sso-proxy-creds-qt (+6/-3)
data/qt/linux.qss (+2/-0)
data/qt/loadingoverlay.ui (+0/-5)
data/qt/proxy_credentials_dialog.ui (+0/-2)
data/qt/reset_password.ui (+199/-262)
data/qt/resources.qrc (+2/-0)
data/qt/setup_account.ui (+1/-36)
data/qt/ssl_dialog.ui (+0/-5)
data/qt/stylesheet.qss (+1/-13)
data/qt/windows.qss (+5/-0)
po/POTFILES.in (+1/-0)
run-tests (+1/-1)
ubuntu_sso/__init__.py (+8/-0)
ubuntu_sso/gtk/gui.py (+18/-33)
ubuntu_sso/gtk/tests/test_gui.py (+34/-5)
ubuntu_sso/qt/__init__.py (+33/-5)
ubuntu_sso/qt/common.py (+3/-3)
ubuntu_sso/qt/current_user_sign_in_page.py (+13/-12)
ubuntu_sso/qt/email_verification_page.py (+8/-5)
ubuntu_sso/qt/enhanced_check_box.py (+9/-3)
ubuntu_sso/qt/forgotten_password_page.py (+5/-1)
ubuntu_sso/qt/loadingoverlay.py (+3/-1)
ubuntu_sso/qt/main.py (+12/-3)
ubuntu_sso/qt/network_detection_page.py (+5/-1)
ubuntu_sso/qt/proxy_dialog.py (+4/-7)
ubuntu_sso/qt/reset_password_page.py (+6/-2)
ubuntu_sso/qt/setup_account_page.py (+19/-18)
ubuntu_sso/qt/sso_wizard_page.py (+116/-76)
ubuntu_sso/qt/tests/__init__.py (+55/-7)
ubuntu_sso/qt/tests/login_u_p.py (+0/-2)
ubuntu_sso/qt/tests/show_gui.py (+0/-2)
ubuntu_sso/qt/tests/test_common.py (+130/-2)
ubuntu_sso/qt/tests/test_current_user_sign_in_page.py (+28/-17)
ubuntu_sso/qt/tests/test_email_verification.py (+6/-7)
ubuntu_sso/qt/tests/test_enhanced_check_box.py (+19/-1)
ubuntu_sso/qt/tests/test_forgotten_password.py (+6/-4)
ubuntu_sso/qt/tests/test_loadingoverlay.py (+0/-3)
ubuntu_sso/qt/tests/test_main.py (+108/-12)
ubuntu_sso/qt/tests/test_proxy_dialog.py (+2/-2)
ubuntu_sso/qt/tests/test_reset_password.py (+9/-17)
ubuntu_sso/qt/tests/test_setup_account.py (+55/-71)
ubuntu_sso/qt/tests/test_ssl_dialog.py (+2/-1)
ubuntu_sso/qt/tests/test_sso_wizard_page.py (+98/-59)
ubuntu_sso/qt/tests/test_ubuntu_sso_wizard.py (+36/-6)
ubuntu_sso/qt/ubuntu_sso_wizard.py (+19/-2)
ubuntu_sso/utils/__init__.py (+15/-2)
ubuntu_sso/utils/linux.py (+19/-0)
ubuntu_sso/utils/runner/glib.py (+1/-1)
ubuntu_sso/utils/runner/tx.py (+1/-1)
ubuntu_sso/utils/tests/test_common.py (+10/-1)
ubuntu_sso/utils/ui.py (+7/-1)
ubuntu_sso/utils/webclient/__init__.py (+18/-2)
ubuntu_sso/utils/webclient/common.py (+110/-2)
ubuntu_sso/utils/webclient/gsettings.py (+21/-14)
ubuntu_sso/utils/webclient/libsoup.py (+48/-4)
ubuntu_sso/utils/webclient/qtnetwork.py (+124/-22)
ubuntu_sso/utils/webclient/tests/__init__.py (+74/-6)
ubuntu_sso/utils/webclient/tests/test_gsettings.py (+44/-61)
ubuntu_sso/utils/webclient/tests/test_webclient.py (+510/-10)
ubuntu_sso/utils/webclient/txweb.py (+77/-78)
ubuntu_sso/utils/windows.py (+19/-0)
To merge this branch: bzr merge lp:~nataliabidart/ubuntu-sso-client/stable-3-0-update-2.99.91
Reviewer Review Type Date Requested Status
Roberto Alsina (community) 2012-03-20 Approve on 2012-03-21
Review via email: mp+98440@code.launchpad.net

Commit message

- Updating from trunk up to revno 930:

[ Alejandro J. Cura <email address hidden> ]
  - Do not allow ssl errors to be ignored (LP: #959390).
  - Handle wrong credentials properly in qtnetwork webclient
    (LP: #957317).
  - Use HTTPClientFactory to allow replacing the reactor used to connect
    (LP: #929207).

[ Diego Sarmentero <email address hidden> ]
  - Decode the content of help_text (LP: #951371).
  - Removed the title from the reset page.
  - Adding missing file for translation (LP: #951376).
  - Adding a general error message when the argument received by
    build_general_error_message is not a dict (LP: #865176).
  - Adding some checks to setup_page (LP: #951461).
  - Adding a padding to the right margin of the reset layout, and align
    one of its layout to the left (LP: #945065).
  - Executing hide_error when the user click the refresh captcha link,
    not inside of the _refresh_captcha method, because this is executed
    automatically when a captcha error is generated, so we will always
    miss the error message (LP: #955010).
  - Removing the align property from the label that wasn't necessary
    and was breakint the work wrap. Also adjust the height of the widget
    depending if it has more than one line (LP: #940392).
  - Improve logging operations (LP: #934500).
  - Making LINK_STYLE to be unicode (LP: #950953).
  - Setting the window title equal to the app_name (LP: #949744).
  - The _move_to_email_verification_page wasn't receiving the params
    that the signal emits (LP: #945066).

[ Jeremy Bicha <email address hidden> ]
  - Improve the grammar for the CLOSE_AND_SETUP_LATER button text
    (LP: #949978).

[ Manuel de la Pena <email address hidden> ]
  - Changed the way in which we deal with proxies to work around bugs
    found in the QNetworkAccessManager (LP: #957313).
  - Stopped listening to the proxyAuthenticationRequired to avoid the
    dialog showing more than once (LP: #957170)
  - Made changes in the way the webclient is selected to ensure that qt
    is used when possible (LP: #957169)
  - Connected the WebKit browser correctly so that the tc page gets
    loaded (LP: #933081).
  - Added code to check if the browser with the t&c was already loaded.
    If it is just show the t&c page, otherwise perform the request
    (LP: #956185).
  - Added a translatable string to give more context of the ssl cert
    info to the user (LP: #948119).
  - Provided the logic required for the Qt webclient implementation to
    detect ssl errors and spawn the ssl dialog to allow the user accept
    the ssl cert exceptions (LP: #948134).
  - Changed the qt webclient implementation to use a proxy factory so
    that the correct proxy is chosen according to the request
    (LP: #950088).
  - Added the required code to allow the webclient use authenticated
    proxies and request the creds when needed (LP: #933727).

[ Natalia B. Bidart <email address hidden> ]
  - The tooltip should not be shown for titles and subtitles when
    no cut off was needed (LP: #949741).
  - Making the WizardHeader a reusable class.
  - Added some minor logging to build_general_error_msg.
  - Improved code for the 'sign in' button validation.

[ Roberto Alsina <email address hidden> ]
  - Made the ubuntu-sso-proxy-creds script crossplatform (LP: #958884).
  - Added .exe to the constant for binary names if needed (LP: #958778).
  - Enable platform-specific styling (LP: #953318).
  - Return the executable's dirname as BIN_DIR for frozen binaries
    (LP: #956187).
  - Only import DBus on Linux (LP: #956304).
  - Fixed tests so they work under non-ascii locales (LP: #951716).

[ Rodney Dawes <email address hidden> ]
  - Don't hard-code font sizes
  - Remove usage of weight property as a numeric; just use bold
    property instead (LP: #953062).

To post a comment you must log in.
Roberto Alsina (ralsina) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/ubuntu-sso-login-qt'
2--- bin/ubuntu-sso-login-qt 2012-02-13 15:43:59 +0000
3+++ bin/ubuntu-sso-login-qt 2012-03-20 16:10:09 +0000
4@@ -15,16 +15,19 @@
5 # You should have received a copy of the GNU General Public License along
6 # with this program. If not, see <http://www.gnu.org/licenses/>.
7
8-"""Start the sso GTK UI."""
9+"""Start the sso Qt UI."""
10
11 # Invalid name "ubuntu-sso-login-qt", pylint: disable=C0103
12 # Access to a protected member, pylint: disable=W0212
13
14+import sys
15+
16 from ubuntu_sso.qt.main import main
17 from ubuntu_sso.utils.ui import parse_args
18
19-from dbus.mainloop.qt import DBusQtMainLoop
20-DBusQtMainLoop(set_as_default=True)
21+if sys.platform.startswith('linux'):
22+ from dbus.mainloop.qt import DBusQtMainLoop
23+ DBusQtMainLoop(set_as_default=True)
24
25
26 if __name__ == "__main__":
27
28=== modified file 'bin/ubuntu-sso-proxy-creds-qt'
29--- bin/ubuntu-sso-proxy-creds-qt 2012-02-24 13:07:06 +0000
30+++ bin/ubuntu-sso-proxy-creds-qt 2012-03-20 16:10:09 +0000
31@@ -19,9 +19,12 @@
32
33 # Invalid name, pylint: disable=C0103
34
35-# set the dbus main loop to be used
36-from dbus.mainloop.qt import DBusQtMainLoop
37-DBusQtMainLoop(set_as_default=True)
38+import sys
39+
40+if sys.platform.startswith('linux'):
41+ # set the dbus main loop to be used
42+ from dbus.mainloop.qt import DBusQtMainLoop
43+ DBusQtMainLoop(set_as_default=True)
44
45 from ubuntu_sso.qt.proxy_dialog import main
46
47
48=== added file 'data/qt/linux.qss'
49--- data/qt/linux.qss 1970-01-01 00:00:00 +0000
50+++ data/qt/linux.qss 2012-03-20 16:10:09 +0000
51@@ -0,0 +1,2 @@
52+/* Styles specific to the linux platform */
53+
54
55=== modified file 'data/qt/loadingoverlay.ui'
56--- data/qt/loadingoverlay.ui 2012-02-22 16:58:08 +0000
57+++ data/qt/loadingoverlay.ui 2012-03-20 16:10:09 +0000
58@@ -52,11 +52,6 @@
59 <verstretch>0</verstretch>
60 </sizepolicy>
61 </property>
62- <property name="font">
63- <font>
64- <pointsize>14</pointsize>
65- </font>
66- </property>
67 <property name="text">
68 <string notr="true">Getting information, please wait...</string>
69 </property>
70
71=== modified file 'data/qt/proxy_credentials_dialog.ui'
72--- data/qt/proxy_credentials_dialog.ui 2012-02-23 11:47:00 +0000
73+++ data/qt/proxy_credentials_dialog.ui 2012-03-20 16:10:09 +0000
74@@ -113,8 +113,6 @@
75 </property>
76 <property name="font">
77 <font>
78- <pointsize>14</pointsize>
79- <weight>75</weight>
80 <bold>true</bold>
81 </font>
82 </property>
83
84=== modified file 'data/qt/reset_password.ui'
85--- data/qt/reset_password.ui 2012-03-05 20:30:57 +0000
86+++ data/qt/reset_password.ui 2012-03-20 16:10:09 +0000
87@@ -6,14 +6,14 @@
88 <rect>
89 <x>0</x>
90 <y>0</y>
91- <width>544</width>
92- <height>280</height>
93+ <width>505</width>
94+ <height>260</height>
95 </rect>
96 </property>
97- <property name="layoutDirection">
98- <enum>Qt::LeftToRight</enum>
99+ <property name="windowTitle">
100+ <string/>
101 </property>
102- <layout class="QVBoxLayout" name="verticalLayout_6">
103+ <layout class="QVBoxLayout" name="verticalLayout_4">
104 <property name="spacing">
105 <number>15</number>
106 </property>
107@@ -21,261 +21,215 @@
108 <number>0</number>
109 </property>
110 <item>
111- <layout class="QHBoxLayout" name="horizontalLayout">
112- <property name="spacing">
113- <number>0</number>
114- </property>
115- <item>
116- <layout class="QVBoxLayout" name="verticalLayout_5">
117- <property name="spacing">
118- <number>15</number>
119- </property>
120- <item>
121- <layout class="QVBoxLayout" name="verticalLayout_4">
122- <property name="spacing">
123- <number>3</number>
124- </property>
125- <item>
126- <widget class="QLabel" name="reset_code">
127- <property name="sizePolicy">
128- <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
129- <horstretch>0</horstretch>
130- <verstretch>0</verstretch>
131- </sizepolicy>
132- </property>
133- <property name="minimumSize">
134- <size>
135- <width>310</width>
136- <height>0</height>
137- </size>
138- </property>
139- <property name="maximumSize">
140- <size>
141- <width>16777215</width>
142- <height>16777215</height>
143- </size>
144- </property>
145- <property name="font">
146- <font>
147- <weight>75</weight>
148- <bold>true</bold>
149- </font>
150- </property>
151- <property name="text">
152- <string notr="true">reset_code</string>
153- </property>
154- </widget>
155- </item>
156- <item>
157- <widget class="QLineEdit" name="reset_code_line_edit">
158- <property name="sizePolicy">
159- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
160- <horstretch>0</horstretch>
161- <verstretch>0</verstretch>
162- </sizepolicy>
163- </property>
164- <property name="minimumSize">
165- <size>
166- <width>300</width>
167- <height>0</height>
168- </size>
169- </property>
170- <property name="maximumSize">
171- <size>
172- <width>300</width>
173- <height>16777215</height>
174- </size>
175- </property>
176- </widget>
177- </item>
178- </layout>
179- </item>
180- <item>
181- <layout class="QVBoxLayout" name="verticalLayout">
182- <property name="spacing">
183- <number>3</number>
184- </property>
185- <item>
186- <widget class="QLabel" name="password_label">
187- <property name="sizePolicy">
188- <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
189- <horstretch>0</horstretch>
190- <verstretch>0</verstretch>
191- </sizepolicy>
192- </property>
193- <property name="minimumSize">
194- <size>
195- <width>310</width>
196- <height>0</height>
197- </size>
198- </property>
199- <property name="maximumSize">
200- <size>
201- <width>16777215</width>
202- <height>16777215</height>
203- </size>
204- </property>
205- <property name="font">
206- <font>
207- <weight>75</weight>
208- <bold>true</bold>
209- </font>
210- </property>
211- <property name="text">
212- <string notr="true">password_label</string>
213- </property>
214- </widget>
215- </item>
216- <item>
217- <widget class="QLineEdit" name="password_line_edit">
218- <property name="sizePolicy">
219- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
220- <horstretch>0</horstretch>
221- <verstretch>0</verstretch>
222- </sizepolicy>
223- </property>
224- <property name="minimumSize">
225- <size>
226- <width>300</width>
227- <height>0</height>
228- </size>
229- </property>
230- <property name="maximumSize">
231- <size>
232- <width>300</width>
233- <height>16777215</height>
234- </size>
235- </property>
236- <property name="echoMode">
237- <enum>QLineEdit::Password</enum>
238- </property>
239- </widget>
240- </item>
241- </layout>
242- </item>
243- <item>
244- <layout class="QVBoxLayout" name="verticalLayout_2">
245- <property name="spacing">
246- <number>3</number>
247- </property>
248- <item>
249- <widget class="QLabel" name="confirm_password_label">
250- <property name="sizePolicy">
251- <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
252- <horstretch>0</horstretch>
253- <verstretch>0</verstretch>
254- </sizepolicy>
255- </property>
256- <property name="minimumSize">
257- <size>
258- <width>310</width>
259- <height>0</height>
260- </size>
261- </property>
262- <property name="font">
263- <font>
264- <weight>75</weight>
265- <bold>true</bold>
266- </font>
267- </property>
268- <property name="text">
269- <string notr="true">confirm_password_label</string>
270- </property>
271- </widget>
272- </item>
273- <item>
274- <widget class="QLineEdit" name="confirm_password_line_edit">
275- <property name="sizePolicy">
276- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
277- <horstretch>0</horstretch>
278- <verstretch>0</verstretch>
279- </sizepolicy>
280- </property>
281- <property name="minimumSize">
282- <size>
283- <width>300</width>
284- <height>0</height>
285- </size>
286- </property>
287- <property name="maximumSize">
288- <size>
289- <width>300</width>
290- <height>16777215</height>
291- </size>
292- </property>
293- <property name="echoMode">
294- <enum>QLineEdit::Password</enum>
295- </property>
296- </widget>
297- </item>
298- </layout>
299- </item>
300- </layout>
301- </item>
302- <item>
303+ <layout class="QGridLayout" name="gridLayout">
304+ <item row="0" column="0">
305+ <layout class="QVBoxLayout" name="verticalLayout">
306+ <property name="spacing">
307+ <number>3</number>
308+ </property>
309+ <item>
310+ <widget class="QLabel" name="reset_code">
311+ <property name="sizePolicy">
312+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
313+ <horstretch>0</horstretch>
314+ <verstretch>0</verstretch>
315+ </sizepolicy>
316+ </property>
317+ <property name="minimumSize">
318+ <size>
319+ <width>310</width>
320+ <height>0</height>
321+ </size>
322+ </property>
323+ <property name="maximumSize">
324+ <size>
325+ <width>16777215</width>
326+ <height>16777215</height>
327+ </size>
328+ </property>
329+ <property name="font">
330+ <font>
331+ <bold>true</bold>
332+ </font>
333+ </property>
334+ <property name="text">
335+ <string notr="true">reset_code</string>
336+ </property>
337+ </widget>
338+ </item>
339+ <item>
340+ <widget class="QLineEdit" name="reset_code_line_edit">
341+ <property name="sizePolicy">
342+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
343+ <horstretch>0</horstretch>
344+ <verstretch>0</verstretch>
345+ </sizepolicy>
346+ </property>
347+ <property name="minimumSize">
348+ <size>
349+ <width>300</width>
350+ <height>0</height>
351+ </size>
352+ </property>
353+ <property name="maximumSize">
354+ <size>
355+ <width>300</width>
356+ <height>16777215</height>
357+ </size>
358+ </property>
359+ </widget>
360+ </item>
361+ </layout>
362+ </item>
363+ <item row="1" column="0">
364+ <layout class="QVBoxLayout" name="verticalLayout_2">
365+ <property name="spacing">
366+ <number>3</number>
367+ </property>
368+ <item>
369+ <widget class="QLabel" name="password_label">
370+ <property name="sizePolicy">
371+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
372+ <horstretch>0</horstretch>
373+ <verstretch>0</verstretch>
374+ </sizepolicy>
375+ </property>
376+ <property name="minimumSize">
377+ <size>
378+ <width>310</width>
379+ <height>0</height>
380+ </size>
381+ </property>
382+ <property name="maximumSize">
383+ <size>
384+ <width>16777215</width>
385+ <height>16777215</height>
386+ </size>
387+ </property>
388+ <property name="font">
389+ <font>
390+ <bold>true</bold>
391+ </font>
392+ </property>
393+ <property name="text">
394+ <string notr="true">password_label</string>
395+ </property>
396+ </widget>
397+ </item>
398+ <item>
399+ <widget class="QLineEdit" name="password_line_edit">
400+ <property name="sizePolicy">
401+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
402+ <horstretch>0</horstretch>
403+ <verstretch>0</verstretch>
404+ </sizepolicy>
405+ </property>
406+ <property name="minimumSize">
407+ <size>
408+ <width>300</width>
409+ <height>0</height>
410+ </size>
411+ </property>
412+ <property name="maximumSize">
413+ <size>
414+ <width>300</width>
415+ <height>16777215</height>
416+ </size>
417+ </property>
418+ <property name="echoMode">
419+ <enum>QLineEdit::Password</enum>
420+ </property>
421+ </widget>
422+ </item>
423+ </layout>
424+ </item>
425+ <item row="2" column="0">
426 <layout class="QVBoxLayout" name="verticalLayout_3">
427 <property name="spacing">
428- <number>0</number>
429+ <number>3</number>
430 </property>
431 <item>
432- <widget class="QLabel" name="password_assistance">
433- <property name="sizePolicy">
434- <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
435- <horstretch>0</horstretch>
436- <verstretch>0</verstretch>
437- </sizepolicy>
438- </property>
439- <property name="minimumSize">
440- <size>
441- <width>220</width>
442- <height>100</height>
443+ <widget class="QLabel" name="confirm_password_label">
444+ <property name="sizePolicy">
445+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
446+ <horstretch>0</horstretch>
447+ <verstretch>0</verstretch>
448+ </sizepolicy>
449+ </property>
450+ <property name="minimumSize">
451+ <size>
452+ <width>310</width>
453+ <height>0</height>
454+ </size>
455+ </property>
456+ <property name="font">
457+ <font>
458+ <bold>true</bold>
459+ </font>
460+ </property>
461+ <property name="text">
462+ <string notr="true">confirm_password_label</string>
463+ </property>
464+ </widget>
465+ </item>
466+ <item>
467+ <widget class="QLineEdit" name="confirm_password_line_edit">
468+ <property name="sizePolicy">
469+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
470+ <horstretch>0</horstretch>
471+ <verstretch>0</verstretch>
472+ </sizepolicy>
473+ </property>
474+ <property name="minimumSize">
475+ <size>
476+ <width>300</width>
477+ <height>0</height>
478 </size>
479 </property>
480 <property name="maximumSize">
481 <size>
482- <width>220</width>
483+ <width>300</width>
484 <height>16777215</height>
485 </size>
486 </property>
487- <property name="text">
488- <string notr="true">password_assistance</string>
489- </property>
490- <property name="indent">
491- <number>20</number>
492+ <property name="echoMode">
493+ <enum>QLineEdit::Password</enum>
494 </property>
495 </widget>
496 </item>
497- <item>
498- <spacer name="verticalSpacer_2">
499- <property name="orientation">
500- <enum>Qt::Vertical</enum>
501- </property>
502- <property name="sizeHint" stdset="0">
503- <size>
504- <width>20</width>
505- <height>40</height>
506- </size>
507- </property>
508- </spacer>
509- </item>
510- <item>
511- <spacer name="horizontalSpacer">
512- <property name="orientation">
513- <enum>Qt::Horizontal</enum>
514- </property>
515- <property name="sizeType">
516- <enum>QSizePolicy::Ignored</enum>
517- </property>
518- <property name="sizeHint" stdset="0">
519- <size>
520- <width>220</width>
521- <height>0</height>
522- </size>
523- </property>
524- </spacer>
525- </item>
526 </layout>
527 </item>
528+ <item row="1" column="1" rowspan="2">
529+ <widget class="QLabel" name="password_assistance">
530+ <property name="sizePolicy">
531+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
532+ <horstretch>0</horstretch>
533+ <verstretch>0</verstretch>
534+ </sizepolicy>
535+ </property>
536+ <property name="minimumSize">
537+ <size>
538+ <width>185</width>
539+ <height>95</height>
540+ </size>
541+ </property>
542+ <property name="maximumSize">
543+ <size>
544+ <width>185</width>
545+ <height>95</height>
546+ </size>
547+ </property>
548+ <property name="text">
549+ <string notr="true">password_assistance</string>
550+ </property>
551+ <property name="wordWrap">
552+ <bool>true</bool>
553+ </property>
554+ <property name="indent">
555+ <number>20</number>
556+ </property>
557+ </widget>
558+ </item>
559 </layout>
560 </item>
561 <item>
562@@ -327,22 +281,5 @@
563 </layout>
564 </widget>
565 <resources/>
566- <connections>
567- <connection>
568- <sender>confirm_password_line_edit</sender>
569- <signal>returnPressed()</signal>
570- <receiver>reset_password_button</receiver>
571- <slot>click()</slot>
572- <hints>
573- <hint type="sourcelabel">
574- <x>160</x>
575- <y>81</y>
576- </hint>
577- <hint type="destinationlabel">
578- <x>541</x>
579- <y>237</y>
580- </hint>
581- </hints>
582- </connection>
583- </connections>
584+ <connections/>
585 </ui>
586
587=== modified file 'data/qt/resources.qrc'
588--- data/qt/resources.qrc 2012-02-16 14:13:36 +0000
589+++ data/qt/resources.qrc 2012-03-20 16:10:09 +0000
590@@ -6,5 +6,7 @@
591 <file>../Ubuntu-B.ttf</file>
592 <file>../balloon_shape.png</file>
593 <file>stylesheet.qss</file>
594+ <file>windows.qss</file>
595+ <file>linux.qss</file>
596 </qresource>
597 </RCC>
598
599=== modified file 'data/qt/setup_account.ui'
600--- data/qt/setup_account.ui 2012-03-05 21:52:45 +0000
601+++ data/qt/setup_account.ui 2012-03-20 16:10:09 +0000
602@@ -85,7 +85,6 @@
603 <widget class="QLabel" name="name_label">
604 <property name="font">
605 <font>
606- <weight>75</weight>
607 <bold>true</bold>
608 </font>
609 </property>
610@@ -114,11 +113,6 @@
611 <height>16777215</height>
612 </size>
613 </property>
614- <property name="font">
615- <font>
616- <pointsize>11</pointsize>
617- </font>
618- </property>
619 <property name="formError" stdset="0">
620 <bool>false</bool>
621 </property>
622@@ -135,7 +129,6 @@
623 <widget class="QLabel" name="email_label">
624 <property name="font">
625 <font>
626- <weight>75</weight>
627 <bold>true</bold>
628 </font>
629 </property>
630@@ -164,11 +157,6 @@
631 <height>16777215</height>
632 </size>
633 </property>
634- <property name="font">
635- <font>
636- <pointsize>11</pointsize>
637- </font>
638- </property>
639 <property name="placeholderText">
640 <string/>
641 </property>
642@@ -229,7 +217,6 @@
643 <widget class="QLabel" name="confirm_email_label">
644 <property name="font">
645 <font>
646- <weight>75</weight>
647 <bold>true</bold>
648 </font>
649 </property>
650@@ -258,11 +245,6 @@
651 <height>16777215</height>
652 </size>
653 </property>
654- <property name="font">
655- <font>
656- <pointsize>11</pointsize>
657- </font>
658- </property>
659 <property name="placeholderText">
660 <string/>
661 </property>
662@@ -377,7 +359,6 @@
663 <widget class="QLabel" name="password_label">
664 <property name="font">
665 <font>
666- <weight>75</weight>
667 <bold>true</bold>
668 </font>
669 </property>
670@@ -406,13 +387,8 @@
671 <height>16777215</height>
672 </size>
673 </property>
674- <property name="font">
675- <font>
676- <pointsize>11</pointsize>
677- </font>
678- </property>
679 <property name="toolTip">
680- <string notr="true">Your password must be at least 8 characters long and at least contain one number and one upper later.</string>
681+ <string notr="true">Your password must be at least 8 characters long and contain at least one number and one uppercase letter.</string>
682 </property>
683 <property name="statusTip">
684 <string/>
685@@ -477,7 +453,6 @@
686 <widget class="QLabel" name="confirm_password_label">
687 <property name="font">
688 <font>
689- <weight>75</weight>
690 <bold>true</bold>
691 </font>
692 </property>
693@@ -506,11 +481,6 @@
694 <height>16777215</height>
695 </size>
696 </property>
697- <property name="font">
698- <font>
699- <pointsize>11</pointsize>
700- </font>
701- </property>
702 <property name="echoMode">
703 <enum>QLineEdit::Password</enum>
704 </property>
705@@ -580,11 +550,6 @@
706 <height>16777215</height>
707 </size>
708 </property>
709- <property name="font">
710- <font>
711- <pointsize>11</pointsize>
712- </font>
713- </property>
714 <property name="locale">
715 <locale language="English" country="UnitedStates"/>
716 </property>
717
718=== modified file 'data/qt/ssl_dialog.ui'
719--- data/qt/ssl_dialog.ui 2012-02-24 15:22:26 +0000
720+++ data/qt/ssl_dialog.ui 2012-03-20 16:10:09 +0000
721@@ -95,11 +95,6 @@
722 </property>
723 <item>
724 <widget class="QLabel" name="title_label">
725- <property name="font">
726- <font>
727- <pointsize>14</pointsize>
728- </font>
729- </property>
730 <property name="text">
731 <string notr="true">Do you want to connect to this server</string>
732 </property>
733
734=== modified file 'data/qt/stylesheet.qss'
735--- data/qt/stylesheet.qss 2012-03-05 20:30:57 +0000
736+++ data/qt/stylesheet.qss 2012-03-20 16:10:09 +0000
737@@ -1,5 +1,4 @@
738 QWidget {
739- font-family: "Ubuntu";
740 color: #333333;
741 }
742
743@@ -16,7 +15,6 @@
744
745 QLabel#password_assistance {
746 border-image: url(":/balloon_shape.png");
747- font-size: 12px;
748 }
749
750 QLineEdit {
751@@ -91,21 +89,11 @@
752 min-height: 100px;
753 }
754
755-QFrame#frm_box > QLabel {
756- font-size: 20px;
757-}
758-
759-QLabel#title_label {
760- font-size: 20px;
761-}
762-
763-QFrame#header {
764+WizardHeader {
765 padding-top: 1px;
766 padding-bottom: 1px;
767 }
768
769 QLabel#form_errors {
770- font: bold 14px;
771- color: #df2d1f;
772 padding-bottom: 1px;
773 }
774
775=== added file 'data/qt/windows.qss'
776--- data/qt/windows.qss 1970-01-01 00:00:00 +0000
777+++ data/qt/windows.qss 2012-03-20 16:10:09 +0000
778@@ -0,0 +1,5 @@
779+/* Styles specific to the windows platform */
780+
781+QWidget {
782+ font-family: "Ubuntu";
783+}
784
785=== modified file 'po/POTFILES.in'
786--- po/POTFILES.in 2010-11-19 21:35:11 +0000
787+++ po/POTFILES.in 2012-03-20 16:10:09 +0000
788@@ -1,1 +1,2 @@
789 ubuntu_sso/gtk/gui.py
790+ubuntu_sso/utils/ui.py
791
792=== modified file 'run-tests'
793--- run-tests 2012-02-17 16:57:34 +0000
794+++ run-tests 2012-03-20 16:10:09 +0000
795@@ -57,7 +57,7 @@
796
797 echo "*** Running QT test suite for ""$MODULE"" ***"
798 ./setup.py build
799-USE_QT_MAINLOOP=True $XVFB_CMDLINE u1trial --reactor=qt4 --gui -p "$GTK_TESTS_PATH" -i "test_windows.py" "$MODULE"
800+$XVFB_CMDLINE u1trial --reactor=qt4 --gui -p "$GTK_TESTS_PATH" -i "test_windows.py" "$MODULE"
801 rm -rf _trial_temp
802 rm -rf build
803
804
805=== modified file 'ubuntu_sso/__init__.py'
806--- ubuntu_sso/__init__.py 2012-02-11 19:25:01 +0000
807+++ ubuntu_sso/__init__.py 2012-03-20 16:10:09 +0000
808@@ -15,6 +15,8 @@
809 # with this program. If not, see <http://www.gnu.org/licenses/>.
810 """Ubuntu Single Sign On client code."""
811
812+import sys
813+
814 # DBus constants
815 DBUS_BUS_NAME = "com.ubuntu.sso"
816
817@@ -29,7 +31,13 @@
818 # return codes for UIs
819 USER_SUCCESS = 0
820 USER_CANCELLATION = 10
821+EXCEPTION_RAISED = 11
822
823 # available UIs
824 UI_EXECUTABLE_GTK = 'ubuntu-sso-login-gtk'
825 UI_EXECUTABLE_QT = 'ubuntu-sso-login-qt'
826+UI_PROXY_CREDS_DIALOG = 'ubuntu-sso-proxy-creds-qt'
827+
828+if getattr(sys, "frozen", None) is not None and sys.platform == "win32":
829+ UI_EXECUTABLE_QT += ".exe"
830+ UI_PROXY_CREDS_DIALOG += ".exe"
831
832=== modified file 'ubuntu_sso/gtk/gui.py'
833--- ubuntu_sso/gtk/gui.py 2012-02-17 18:43:17 +0000
834+++ ubuntu_sso/gtk/gui.py 2012-03-20 16:10:09 +0000
835@@ -90,23 +90,6 @@
836 return c
837 # pylint: enable=C0103
838
839-
840-# To be removed when Python bindings provide these constants
841-# as per http://code.google.com/p/pywebkitgtk/issues/detail?id=44
842-# WebKitLoadStatus
843-WEBKIT_LOAD_PROVISIONAL = 0
844-WEBKIT_LOAD_COMMITTED = 1
845-WEBKIT_LOAD_FINISHED = 2
846-WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT = 3
847-WEBKIT_LOAD_FAILED = 4
848-# WebKitWebNavigationReason
849-WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED = 0
850-WEBKIT_WEB_NAVIGATION_REASON_FORM_SUBMITTED = 1
851-WEBKIT_WEB_NAVIGATION_REASON_BACK_FORWARD = 2
852-WEBKIT_WEB_NAVIGATION_REASON_RELOAD = 3
853-WEBKIT_WEB_NAVIGATION_REASON_FORM_RESUBMITTED = 4
854-WEBKIT_WEB_NAVIGATION_REASON_OTHER = 5
855-
856 DEFAULT_WIDTH = 30
857 # To be replaced by values from the theme (LP: #616526)
858 HELP_TEXT_COLOR = parse_color("#bfbfbf")
859@@ -947,23 +930,14 @@
860
861 self._set_current_page(self.processing_vbox)
862
863- def on_tc_button_clicked(self, *args, **kwargs):
864- """The T&C button was clicked, create the browser and load terms."""
865+ def _add_webkit_browser(self):
866+ """Add the webkit browser for the t&c."""
867 # delay the import of webkit to be able to build without it
868 from gi.repository import WebKit # pylint: disable=E0611
869+
870 browser = WebKit.WebView()
871
872- # The signal WebKitWebView::load-finished is deprecated and should not
873- # be used in newly-written code. Use the "load-status" property
874- # instead. Connect to "notify::load-status" to monitor loading.
875-
876- # nataliabidart (2010-10-04): connecting this signal makes the loading
877- # of the Ubuntu One terms URL to fail. So we're using the deprecated
878- # 'load-finished' for now.
879-
880- #browser.connect('notify::load-status',
881- # self.on_tc_browser_notify_load_status)
882- browser.connect('load-finished',
883+ browser.connect('notify::load-status',
884 self.on_tc_browser_notify_load_status)
885 browser.connect('navigation-policy-decision-requested',
886 self.on_tc_browser_navigation_requested)
887@@ -978,7 +952,14 @@
888 browser.load_uri(self.tc_url)
889 browser.show()
890 self.tc_browser_window.add(browser)
891- self._set_current_page(self.processing_vbox)
892+
893+ def on_tc_button_clicked(self, *args, **kwargs):
894+ """The T&C button was clicked, create the browser and load terms."""
895+ if self.tc_browser_window.get_child() is None:
896+ self._add_webkit_browser()
897+ self._set_current_page(self.processing_vbox)
898+ else:
899+ self._set_current_page(self.tc_browser_vbox)
900
901 def on_tc_back_button_clicked(self, *args, **kwargs):
902 """T & C 'back' button was clicked, return to the previous page."""
903@@ -986,14 +967,18 @@
904
905 def on_tc_browser_notify_load_status(self, browser, *args, **kwargs):
906 """The T&C page is being loaded."""
907- if browser.get_load_status() == WEBKIT_LOAD_FINISHED:
908+ from gi.repository import WebKit # pylint: disable=E0611
909+
910+ if browser.get_load_status().real == WebKit.LoadStatus.FINISHED:
911 self._set_current_page(self.tc_browser_vbox)
912
913 def on_tc_browser_navigation_requested(self, browser, frame, request,
914 action, decision, *args, **kwargs):
915 """The user wants to navigate within the T&C browser."""
916+ from gi.repository import WebKit # pylint: disable=E0611
917+
918 if action is not None and \
919- action.get_reason() == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED:
920+ action.get_reason() == WebKit.WebNavigationReason.LINK_CLICKED:
921 if decision is not None:
922 decision.ignore()
923 url = action.get_original_uri()
924
925=== modified file 'ubuntu_sso/gtk/tests/test_gui.py'
926--- ubuntu_sso/gtk/tests/test_gui.py 2012-02-17 18:43:17 +0000
927+++ ubuntu_sso/gtk/tests/test_gui.py 2012-03-20 16:10:09 +0000
928@@ -134,7 +134,7 @@
929
930 def get_load_status(self):
931 """Return the current load status."""
932- return gui.WEBKIT_LOAD_FINISHED
933+ return WebKit.LoadStatus.FINISHED
934
935 def show(self):
936 """Show this instance."""
937@@ -981,7 +981,7 @@
938 def test_notify_load_finished_connected(self):
939 """The 'load-finished' signal is connected."""
940 expected = [self.ui.on_tc_browser_notify_load_status]
941- self.assertEqual(self.browser._signals['load-finished'],
942+ self.assertEqual(self.browser._signals['notify::load-status'],
943 expected)
944
945 def test_tc_loaded_morphs_into_tc_browser_vbox(self):
946@@ -998,7 +998,7 @@
947 def test_navigation_requested_succeeds_for_no_clicking(self):
948 """The navigation request succeeds when user hasn't clicked a link."""
949 action = WebKit.WebNavigationAction()
950- action.set_reason(gui.WEBKIT_WEB_NAVIGATION_REASON_OTHER)
951+ action.set_reason(WebKit.WebNavigationReason.OTHER)
952
953 decision = WebKit.WebPolicyDecision()
954 decision.use = self._set_called
955@@ -1011,7 +1011,7 @@
956 def test_navigation_requested_ignores_clicked_links(self):
957 """The navigation request is ignored if a link was clicked."""
958 action = WebKit.WebNavigationAction()
959- action.set_reason(gui.WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED)
960+ action.set_reason(WebKit.WebNavigationReason.LINK_CLICKED)
961
962 decision = WebKit.WebPolicyDecision()
963 decision.ignore = self._set_called
964@@ -1037,7 +1037,7 @@
965 """
966 url = 'http://something.com/yadda'
967 action = WebKit.WebNavigationAction()
968- action.set_reason(gui.WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED)
969+ action.set_reason(WebKit.WebNavigationReason.LINK_CLICKED)
970 action.set_original_uri(url)
971
972 decision = WebKit.WebPolicyDecision()
973@@ -1050,6 +1050,35 @@
974 self.ui.on_tc_browser_navigation_requested(**kwargs)
975 self.assertEqual(self._called, ((url,), {}))
976
977+ def test_on_tc_button_clicked_no_child(self):
978+ """Test the tc loading with no child."""
979+ called = []
980+
981+ def fake_add_browser():
982+ """Fake add browser."""
983+ called.append('fake_add_browser')
984+
985+ self.patch(self.ui, '_add_webkit_browser', fake_add_browser)
986+ self.patch(self.ui.tc_browser_window, 'get_child', lambda: None)
987+
988+ self.ui.on_tc_button_clicked()
989+ self.assertIn('fake_add_browser', called)
990+
991+ def test_on_tc_button_clicked_child(self):
992+ """Test the tc loading with child."""
993+ called = []
994+
995+ def fake_add_browser(i_self):
996+ """Fake add browser."""
997+ called.append('fake_add_browser')
998+
999+ self.patch(self.ui, '_add_webkit_browser', fake_add_browser)
1000+
1001+ browser = WebKit.WebView()
1002+ self.ui.tc_browser_window.add(browser)
1003+ self.ui.on_tc_button_clicked()
1004+ self.assertNotIn('fake_add_browser', called)
1005+
1006
1007 class RegistrationErrorTestCase(UbuntuSSOClientTestCase):
1008 """Test suite for the user registration error handling."""
1009
1010=== modified file 'ubuntu_sso/qt/__init__.py'
1011--- ubuntu_sso/qt/__init__.py 2012-03-05 18:56:50 +0000
1012+++ ubuntu_sso/qt/__init__.py 2012-03-20 16:10:09 +0000
1013@@ -18,19 +18,27 @@
1014
1015 import collections
1016
1017-
1018-LINK_STYLE = ('<a href="{link_url}">'
1019+from PyQt4 import QtGui, QtCore
1020+
1021+from ubuntu_sso.logger import setup_gui_logging
1022+from ubuntu_sso.utils.ui import GENERIC_BACKEND_ERROR
1023+
1024+logger = setup_gui_logging('ubuntu_sso.qt')
1025+
1026+LINK_STYLE = (u'<a href="{link_url}">'
1027 '<span style="color:#df2d1f;">{link_text}</span></a>')
1028 ERROR_ALL = '__all__'
1029-ERROR_STYLE = u'<font color="#df2d1f"><b>%s</b></font>'
1030+ERROR_STYLE = u'<font color="#df2d1f" style="font-size:small"><b>%s</b></font>'
1031 ERROR_MESSAGE = 'message'
1032 PREFERED_UI_SIZE = {'width': 550, 'height': 525}
1033-TITLE_STYLE = u'<span style="font-size:24px">%s</span>'
1034+TITLE_STYLE = u'<span style="font-size:xx-large;font-weight:bold;">%s</span>'
1035+WINDOW_TITLE = 'Ubuntu Single Sign On'
1036
1037
1038 # Based on the gtk implementation
1039 def build_general_error_message(errordict):
1040 """Build a user-friendly error message from the errordict."""
1041+ logger.debug('build_general_error_message: errordict is: %r.', errordict)
1042 result = ''
1043 if isinstance(errordict, collections.Mapping):
1044 msg1 = errordict.get(ERROR_ALL)
1045@@ -50,5 +58,25 @@
1046 result = '\n'.join(
1047 [('%s: %s' % (k, v)) for k, v in errordict.iteritems()])
1048 else:
1049- result = repr(errordict)
1050+ result = GENERIC_BACKEND_ERROR
1051+ logger.error('build_general_error_message with unknown error: %r',
1052+ errordict)
1053+
1054+ logger.info('build_general_error_message: returning %r.', result)
1055 return result
1056+
1057+
1058+def maybe_elide_text(label, text, width, markup=None):
1059+ """Set 'text' to be the 'label's text.
1060+
1061+ If 'text' is longer than 'width', set the label's tooltip to be the full
1062+ text, and the text itself to be the elided version of 'text'.
1063+
1064+ """
1065+ fm = QtGui.QFontMetrics(label.font())
1066+ elided_text = fm.elidedText(text, QtCore.Qt.ElideRight, width)
1067+ if elided_text != text:
1068+ label.setToolTip(text)
1069+ if markup is not None:
1070+ elided_text = markup % elided_text
1071+ label.setText(elided_text)
1072
1073=== modified file 'ubuntu_sso/qt/common.py'
1074--- ubuntu_sso/qt/common.py 2012-02-16 14:13:36 +0000
1075+++ ubuntu_sso/qt/common.py 2012-03-20 16:10:09 +0000
1076@@ -28,9 +28,9 @@
1077 )
1078
1079 # all the text + styles that are used in the gui
1080-BAD = u'<img src=":/password_hint_warning.png" /><font> %s </font>'
1081-GOOD = u'<img src=":/password_hint_ok.png" /><font> %s </font>'
1082-NORMAL = u'<font> %s </font>'
1083+BAD = u'<img src=":/password_hint_warning.png" /><small> %s </small>'
1084+GOOD = u'<img src=":/password_hint_ok.png" /><small> %s </small>'
1085+NORMAL = u'<small> %s </small>'
1086
1087
1088 def password_assistance(line_edit, assistance, icon_type=BAD):
1089
1090=== modified file 'ubuntu_sso/qt/current_user_sign_in_page.py'
1091--- ubuntu_sso/qt/current_user_sign_in_page.py 2012-03-05 20:30:57 +0000
1092+++ ubuntu_sso/qt/current_user_sign_in_page.py 2012-03-20 16:10:09 +0000
1093@@ -61,6 +61,11 @@
1094 }
1095 return result
1096
1097+ @property
1098+ def password(self):
1099+ """Return the content of the password edit."""
1100+ return unicode(self.ui.password_edit.text())
1101+
1102 def on_user_not_validated(self, app_name, email):
1103 """Show the validate email page."""
1104 self.hide_overlay()
1105@@ -76,6 +81,7 @@
1106
1107 def initializePage(self):
1108 """Setup UI details."""
1109+ logger.debug('initializePage - About to show CurrentUserSignInPage')
1110 self.setButtonText(QtGui.QWizard.CancelButton, CANCEL_BUTTON)
1111 # Layout without custom button 1,
1112 # without finish button
1113@@ -94,10 +100,8 @@
1114
1115 def _set_translated_strings(self):
1116 """Set the translated strings."""
1117- logger.debug('CurrentUserSignInPage._set_translated_strings')
1118 self.setTitle(LOGIN_TITLE.format(app_name=self.app_name))
1119 self.setSubTitle(LOGIN_SUBTITLE % {'app_name': self.app_name})
1120-
1121 self.ui.email_label.setText(EMAIL_LABEL)
1122 self.ui.password_label.setText(LOGIN_PASSWORD_LABEL)
1123 forgotten_text = LINK_STYLE.format(link_url='#',
1124@@ -107,7 +111,6 @@
1125
1126 def _connect_ui(self):
1127 """Connect the buttons to perform actions."""
1128- logger.debug('CurrentUserSignInPage._connect_buttons')
1129 self.ui.forgot_password_label.linkActivated.connect(
1130 self.on_forgotten_password)
1131 self.ui.email_edit.textChanged.connect(self._validate)
1132@@ -116,18 +119,16 @@
1133
1134 def _validate(self):
1135 """Perform input validation."""
1136- valid = True
1137 correct_mail = is_correct_email(unicode(self.ui.email_edit.text()))
1138- password = unicode(self.ui.password_edit.text())
1139- if not correct_mail or not password:
1140- valid = False
1141- self.ui.sign_in_button.setEnabled(valid)
1142+ correct_password = len(unicode(self.ui.password_edit.text())) > 0
1143+ enabled = correct_mail and correct_password
1144+ self.ui.sign_in_button.setEnabled(enabled)
1145
1146 def login(self):
1147 """Perform the login using the self.backend."""
1148- logger.debug('CurrentUserSignInPage.login')
1149 # grab the data from the view and call the backend
1150 email = unicode(self.ui.email_edit.text())
1151+ logger.info('CurrentUserSignInPage.login for: %s', email)
1152 password = unicode(self.ui.password_edit.text())
1153 args = (self.app_name, email, password)
1154 if self.ping_url:
1155@@ -146,18 +147,18 @@
1156 # let the user know
1157 logger.error('Got error when login %s, error: %s',
1158 self.app_name, error)
1159- self.show_error(self.app_name, build_general_error_message(error))
1160+ self.show_error(build_general_error_message(error))
1161
1162 def on_logged_in(self, app_name, result):
1163 """We managed to log in."""
1164 logger.info('Logged in for %s', app_name)
1165 self.hide_overlay()
1166 email = unicode(self.ui.email_edit.text())
1167+ logger.debug('About to emit userLoggedIn signal with: (%s).', email)
1168 self.userLoggedIn.emit(email)
1169- logger.debug('Wizard.loginSuccess emitted.')
1170
1171 def on_forgotten_password(self, link=None):
1172 """Show the user the forgotten password page."""
1173- logger.info('Forgotten password')
1174 self.hide_overlay()
1175+ logger.debug('About to emit passwordForgotten signal')
1176 self.passwordForgotten.emit()
1177
1178=== modified file 'ubuntu_sso/qt/email_verification_page.py'
1179--- ubuntu_sso/qt/email_verification_page.py 2012-03-05 20:30:57 +0000
1180+++ ubuntu_sso/qt/email_verification_page.py 2012-03-20 16:10:09 +0000
1181@@ -71,7 +71,6 @@
1182
1183 def _connect_ui(self):
1184 """Set the connection of signals."""
1185- logger.debug('EmailVerificationController._connect_ui')
1186 self.ui.verification_code_edit.textChanged.connect(
1187 self.validate_form)
1188 self.next_button.clicked.connect(self.validate_email)
1189@@ -84,7 +83,6 @@
1190
1191 def _set_translated_strings(self):
1192 """Set the different titles."""
1193- logger.debug('EmailVerificationController._set_titles')
1194 self.header.set_title(VERIFY_EMAIL_TITLE)
1195 self.header.set_subtitle(VERIFY_EMAIL_CONTENT % {
1196 "app_name": self.app_name,
1197@@ -103,7 +101,8 @@
1198
1199 def validate_email(self):
1200 """Call the next action."""
1201- logger.debug('EmailVerificationController.validate_email')
1202+ logger.debug('EmailVerificationController.validate_email for: %s',
1203+ self.email)
1204 code = unicode(self.ui.verification_code_edit.text())
1205 args = (self.app_name, self.email, self.password, code)
1206 self.hide_error()
1207@@ -123,21 +122,25 @@
1208
1209 def on_email_validated(self, app_name, email):
1210 """Signal thrown after the email is validated."""
1211- logger.info('EmailVerificationController.on_email_validated')
1212+ logger.info('EmailVerificationController.on_email_validated for %s, '
1213+ 'email: %s', app_name, email)
1214 self.hide_overlay()
1215 self.registrationSuccess.emit(self.email)
1216
1217 def on_email_validation_error(self, app_name, error):
1218 """Signal thrown when there's a problem validating the email."""
1219+ logger.error('Got error on email validation %s, error: %s',
1220+ app_name, error)
1221 self.hide_overlay()
1222 msg = error.pop(ERROR_EMAIL_TOKEN, '')
1223 msg += build_general_error_message(error)
1224- self.show_error(self.app_name, msg)
1225+ self.show_error(msg)
1226
1227 # pylint: disable=C0103
1228
1229 def initializePage(self):
1230 """Called to prepare the page just before it is shown."""
1231+ logger.debug('initializePage - About to show EmailVerificationPage')
1232 self.next_button.setDefault(True)
1233 self.next_button.setEnabled(False)
1234 self.wizard().setButtonLayout([QtGui.QWizard.Stretch])
1235
1236=== modified file 'ubuntu_sso/qt/enhanced_check_box.py'
1237--- ubuntu_sso/qt/enhanced_check_box.py 2012-03-01 16:53:29 +0000
1238+++ ubuntu_sso/qt/enhanced_check_box.py 2012-03-20 16:10:09 +0000
1239@@ -24,11 +24,12 @@
1240 class EnhancedCheckBox(QtGui.QCheckBox):
1241 """Enhanced QCheckBox to support links in the message displayed."""
1242
1243- def __init__(self, text=""):
1244- QtGui.QCheckBox.__init__(self)
1245+ def __init__(self, text="", parent=None):
1246+ QtGui.QCheckBox.__init__(self, parent)
1247 hbox = QtGui.QHBoxLayout()
1248+ hbox.setAlignment(QtCore.Qt.AlignLeft)
1249 self.text_label = QtGui.QLabel(text)
1250- self.text_label.setAlignment(QtCore.Qt.AlignTop)
1251+ self.text_label.setWordWrap(True)
1252 self.text_label.setOpenExternalLinks(True)
1253 padding = self.iconSize().width()
1254 self.text_label.setStyleSheet("margin-top: -3px;"
1255@@ -37,6 +38,11 @@
1256 hbox.addWidget(self.text_label)
1257 self.setLayout(hbox)
1258
1259+ if parent is not None:
1260+ lines = self.text_label.width() / float(parent.width())
1261+ self.text_label.setMinimumWidth(parent.width())
1262+ self.setMinimumHeight(self.height() * lines)
1263+
1264 self.stateChanged.connect(self.text_label.setFocus)
1265
1266 def text(self):
1267
1268=== modified file 'ubuntu_sso/qt/forgotten_password_page.py'
1269--- ubuntu_sso/qt/forgotten_password_page.py 2012-03-05 20:31:22 +0000
1270+++ ubuntu_sso/qt/forgotten_password_page.py 2012-03-20 16:10:09 +0000
1271@@ -63,6 +63,7 @@
1272
1273 def initializePage(self):
1274 """Set the initial state of ForgottenPassword page."""
1275+ logger.debug('initializePage - About to show ForgottenPasswordPage')
1276 self.ui.send_button.setDefault(True)
1277 enabled = not self.ui.email_line_edit.text().isEmpty()
1278 self.ui.send_button.setEnabled(enabled)
1279@@ -98,6 +99,7 @@
1280 """Send the request password operation."""
1281 self.hide_error()
1282 args = (self.app_name, self.email_address)
1283+ logger.debug('Sending request new password for %s, email: %s', *args)
1284 f = self.backend.request_password_reset_token
1285
1286 error_handler = partial(self._handle_error, f,
1287@@ -113,6 +115,8 @@
1288
1289 def on_password_reset_token_sent(self, app_name, email):
1290 """Action taken when we managed to get the password reset done."""
1291+ logger.info('ForgottenPasswordPage.on_password_reset_token_sent for '
1292+ '%s, email: %s', app_name, email)
1293 # ignore the result and move to the reset page
1294 self.hide_overlay()
1295 self.passwordResetTokenSent.emit(email)
1296@@ -123,4 +127,4 @@
1297 # set the error message
1298 self.hide_overlay()
1299 msg = REQUEST_PASSWORD_TOKEN_WRONG_EMAIL
1300- self.show_error(self.app_name, msg)
1301+ self.show_error(msg)
1302
1303=== modified file 'ubuntu_sso/qt/loadingoverlay.py'
1304--- ubuntu_sso/qt/loadingoverlay.py 2012-02-27 19:00:59 +0000
1305+++ ubuntu_sso/qt/loadingoverlay.py 2012-03-20 16:10:09 +0000
1306@@ -21,6 +21,8 @@
1307 from ubuntu_sso.qt.ui import loadingoverlay_ui
1308 from ubuntu_sso.utils.ui import LOADING_OVERLAY
1309
1310+LOADING_STYLE = u'<span style="font-size:x-large;">{0}</span>'
1311+
1312
1313 class LoadingOverlay(QtGui.QFrame):
1314 """The widget that shows a loading animation and disable the widget below.
1315@@ -43,7 +45,7 @@
1316 self.counter = 0
1317 self.orientation = False
1318
1319- self.ui.label.setText(LOADING_OVERLAY)
1320+ self.ui.label.setText(LOADING_STYLE.format(LOADING_OVERLAY))
1321
1322 # Invalid name "paintEvent", "eventFilter", "showEvent", "timerEvent"
1323 # pylint: disable=C0103
1324
1325=== modified file 'ubuntu_sso/qt/main.py'
1326--- ubuntu_sso/qt/main.py 2012-02-24 19:54:48 +0000
1327+++ ubuntu_sso/qt/main.py 2012-03-20 16:10:09 +0000
1328@@ -25,6 +25,7 @@
1329 from ubuntu_sso.qt.ui import resources_rc
1330 # pylint: enable=W0611
1331 from ubuntu_sso.qt.ubuntu_sso_wizard import UbuntuSSOClientGUI
1332+from ubuntu_sso.utils import PLATFORM_QSS
1333
1334
1335 def main(**kwargs):
1336@@ -34,9 +35,17 @@
1337 QtGui.QFontDatabase.addApplicationFont(':/Ubuntu-R.ttf')
1338 QtGui.QFontDatabase.addApplicationFont(':/Ubuntu-B.ttf')
1339
1340- # Apply Style Sheet -- The windows version may be different
1341- qss = QtCore.QResource(":/stylesheet.qss")
1342- app.setStyleSheet(qss.data())
1343+ data = []
1344+ for qss_name in (PLATFORM_QSS, ":/stylesheet.qss"):
1345+ qss = QtCore.QResource(qss_name)
1346+ data.append(unicode(qss.data()))
1347+ app.setStyleSheet('\n'.join(data))
1348+
1349+ # Fix the string that contains unicode chars.
1350+ for key in kwargs:
1351+ value = kwargs[key]
1352+ if isinstance(value, str):
1353+ kwargs[key] = value.decode('utf-8')
1354
1355 # Unused variable 'ui', pylint: disable=W0612
1356 ui = UbuntuSSOClientGUI(close_callback=app.exit, **kwargs)
1357
1358=== modified file 'ubuntu_sso/qt/network_detection_page.py'
1359--- ubuntu_sso/qt/network_detection_page.py 2012-02-29 14:00:12 +0000
1360+++ ubuntu_sso/qt/network_detection_page.py 2012-03-20 16:10:09 +0000
1361@@ -20,7 +20,7 @@
1362 from PyQt4 import QtGui
1363
1364 from ubuntu_sso import networkstate
1365-
1366+from ubuntu_sso.logger import setup_logging
1367 from ubuntu_sso.qt.sso_wizard_page import SSOWizardPage
1368 from ubuntu_sso.qt.ui import network_detection_ui
1369 from ubuntu_sso.utils.ui import (
1370@@ -31,6 +31,9 @@
1371 )
1372
1373
1374+logger = setup_logging('ubuntu_sso.network_detection_page')
1375+
1376+
1377 class NetworkDetectionPage(SSOWizardPage):
1378
1379 """Widget to show if we don't detect a network connection."""
1380@@ -48,6 +51,7 @@
1381
1382 def initializePage(self):
1383 """Set UI details."""
1384+ logger.debug('initializePage - About to show NetworkDetectionPage')
1385 self.wizard()._next_id = None
1386
1387 self.setButtonText(QtGui.QWizard.CustomButton1, TRY_AGAIN_BUTTON)
1388
1389=== modified file 'ubuntu_sso/qt/proxy_dialog.py'
1390--- ubuntu_sso/qt/proxy_dialog.py 2012-03-05 20:30:57 +0000
1391+++ ubuntu_sso/qt/proxy_dialog.py 2012-03-20 16:10:09 +0000
1392@@ -21,6 +21,7 @@
1393 from PyQt4.QtGui import QApplication, QDialog, QIcon
1394 from twisted.internet import defer
1395
1396+from ubuntu_sso import EXCEPTION_RAISED, USER_SUCCESS, USER_CANCELLATION
1397 from ubuntu_sso.logger import setup_gui_logging
1398 from ubuntu_sso.keyring import Keyring
1399 from ubuntu_sso.qt.ui.proxy_credentials_dialog_ui import Ui_ProxyCredsDialog
1400@@ -37,10 +38,6 @@
1401 PROXY_CREDS_SAVE_BUTTON,
1402 )
1403
1404-CREDS_ACQUIRED = 0
1405-USER_CANCELATION = -1
1406-EXCEPTION_RAISED = -2
1407-
1408 logger = setup_gui_logging("ubuntu_sso.qt.proxy_dialog")
1409
1410
1411@@ -107,15 +104,15 @@
1412 logger.debug('Save credentials as for domain %s.', self.domain)
1413 yield self.keyring.set_credentials(self.domain, creds)
1414 except Exception, e:
1415- logger.error('Could not retrieve credentials.')
1416+ logger.exception('Could not set credentials:')
1417 self.done(EXCEPTION_RAISED)
1418 # pylint: disable=W0703, W0612
1419- self.done(CREDS_ACQUIRED)
1420+ self.done(USER_SUCCESS)
1421
1422 def _on_cancel_clicked(self, *args):
1423 """End the dialog."""
1424 logger.debug('User canceled credentials dialog.')
1425- self.done(USER_CANCELATION)
1426+ self.done(USER_CANCELLATION)
1427
1428 def _set_buttons(self):
1429 """Set the labels of the buttons."""
1430
1431=== modified file 'ubuntu_sso/qt/reset_password_page.py'
1432--- ubuntu_sso/qt/reset_password_page.py 2012-03-05 20:30:57 +0000
1433+++ ubuntu_sso/qt/reset_password_page.py 2012-03-20 16:10:09 +0000
1434@@ -18,7 +18,7 @@
1435
1436 from functools import partial
1437
1438-from PyQt4.QtCore import SIGNAL, pyqtSignal
1439+from PyQt4.QtCore import Qt, SIGNAL, pyqtSignal
1440 from PyQt4.QtGui import QApplication
1441
1442 from ubuntu_sso import NO_OP
1443@@ -73,7 +73,9 @@
1444
1445 def initializePage(self):
1446 """Extends QWizardPage initializePage method."""
1447+ logger.debug('initializePage - About to show ResetPasswordPage')
1448 super(ResetPasswordPage, self).initializePage()
1449+ self.ui.gridLayout.setAlignment(Qt.AlignLeft)
1450 common.password_default_assistance(self.ui.password_assistance)
1451 self.ui.password_assistance.setVisible(False)
1452 self.setTitle(RESET_TITLE)
1453@@ -158,6 +160,8 @@
1454
1455 def on_password_changed(self, app_name, email):
1456 """Let user know that the password was changed."""
1457+ logger.info('ResetPasswordPage.on_password_changed for %s, email: %s',
1458+ app_name, email)
1459 self.hide_overlay()
1460 email = unicode(self.wizard().forgotten.ui.email_line_edit.text())
1461 self.passwordChanged.emit(email)
1462@@ -166,7 +170,7 @@
1463 """Let the user know that there was an error."""
1464 logger.error('Got error changing password for %s, error: %s',
1465 self.app_name, error)
1466- self.show_error(self.app_name, build_general_error_message(error))
1467+ self.show_error(build_general_error_message(error))
1468
1469 def set_new_password(self):
1470 """Request a new password to be set."""
1471
1472=== modified file 'ubuntu_sso/qt/setup_account_page.py'
1473--- ubuntu_sso/qt/setup_account_page.py 2012-03-06 14:13:10 +0000
1474+++ ubuntu_sso/qt/setup_account_page.py 2012-03-20 16:10:09 +0000
1475@@ -31,7 +31,7 @@
1476 from PyQt4 import QtGui, QtCore
1477
1478 from ubuntu_sso import NO_OP
1479-from ubuntu_sso.logger import setup_gui_logging
1480+from ubuntu_sso.logger import setup_gui_logging, log_call
1481 from ubuntu_sso.qt import (
1482 LINK_STYLE,
1483 build_general_error_message,
1484@@ -112,11 +112,17 @@
1485 }
1486 return result
1487
1488+ @property
1489+ def password(self):
1490+ """Return the content of the password edit."""
1491+ return unicode(self.ui.password_edit.text())
1492+
1493 # Invalid name "initializePage"
1494 # pylint: disable=C0103
1495
1496 def initializePage(self):
1497 """Setup UI details."""
1498+ logger.debug('initializePage - About to show SetupAccountPage')
1499 # Set Setup Account button
1500 self.wizard().setOption(QtGui.QWizard.HaveCustomButton3, True)
1501 try:
1502@@ -152,7 +158,6 @@
1503
1504 def _set_translated_strings(self):
1505 """Set the strings."""
1506- logger.debug('SetUpAccountPage._set_translated_strings')
1507 # set the translated string
1508 title_page = REGISTER_TITLE.format(app_name=self.app_name)
1509 self.setTitle(title_page)
1510@@ -190,7 +195,7 @@
1511 terms = AGREE_TO_PRIVACY_POLICY.format(app_name=self.app_name,
1512 privacy_policy=privacy_policy_link)
1513
1514- self.terms_checkbox = enhanced_check_box.EnhancedCheckBox(terms)
1515+ self.terms_checkbox = enhanced_check_box.EnhancedCheckBox(terms, self)
1516 self.ui.hlayout_check.addWidget(self.terms_checkbox)
1517 self.terms_checkbox.setVisible(bool(self.tc_url or self.policy_url))
1518
1519@@ -220,7 +225,6 @@
1520
1521 def _connect_ui(self):
1522 """Set the connection of signals."""
1523- logger.debug('SetUpAccountPage._connect_ui')
1524 self._set_line_edits_validations()
1525
1526 self.ui.captcha_view.setPixmap(QtGui.QPixmap())
1527@@ -229,6 +233,7 @@
1528 self.ui.password_assistance,
1529 common.NORMAL))
1530
1531+ self.ui.refresh_label.linkActivated.connect(self.hide_error)
1532 self.ui.refresh_label.linkActivated.connect(lambda url: \
1533 self._refresh_captcha())
1534 # We need to check if we enable the button on many signals
1535@@ -271,7 +276,6 @@
1536 def _refresh_captcha(self):
1537 """Refresh the captcha image shown in the ui."""
1538 logger.debug('SetUpAccountPage._refresh_captcha')
1539- self.hide_error()
1540 # lets clean behind us, do we have the old file arround?
1541 if self.captcha_file and os.path.exists(self.captcha_file):
1542 os.unlink(self.captcha_file)
1543@@ -297,11 +301,9 @@
1544 self.registerField('email_address', self.ui.email_edit)
1545 self.registerField('password', self.ui.password_edit)
1546
1547+ @log_call(logger.debug)
1548 def on_captcha_generated(self, app_name, result):
1549 """A new image was generated."""
1550- logger.debug('SetUpAccountPage.on_captcha_generated for %r '
1551- '(captcha id %r, filename %r).',
1552- app_name, result, self.captcha_file)
1553 self.captcha_id = result
1554 # HACK: First, let me apologize before hand, you can mention my mother
1555 # if needed I would do the same (mandel)
1556@@ -320,28 +322,28 @@
1557 self.captcha_image = pixmap_image
1558 self.on_captcha_refresh_complete()
1559
1560- def on_captcha_generation_error(self, error, *args, **kwargs):
1561+ @log_call(logger.error)
1562+ def on_captcha_generation_error(self, app_name, error):
1563 """An error ocurred."""
1564- logger.debug('SetUpAccountPage.on_captcha_generation_error')
1565- self.show_error(self.app_name, CAPTCHA_LOAD_ERROR)
1566+ self.show_error(CAPTCHA_LOAD_ERROR)
1567 self.on_captcha_refresh_complete()
1568
1569+ @log_call(logger.error)
1570 def on_user_registration_error(self, app_name, error):
1571 """Let the user know we could not register."""
1572- logger.debug('SetUpAccountPage.on_user_registration_error')
1573 # errors are returned as a dict with the data we want to show.
1574 msg = error.pop(ERROR_EMAIL, '')
1575 if msg:
1576 self.set_error_message(self.ui.email_assistance, msg)
1577 error_msg = build_general_error_message(error)
1578 if error_msg:
1579- self.show_error(self.app_name, error_msg)
1580+ self.show_error(error_msg)
1581 self._refresh_captcha()
1582
1583+ @log_call(logger.info)
1584 def on_user_registered(self, app_name, email):
1585 """Execute when the user did register."""
1586 self.hide_overlay()
1587- logger.debug('SetUpAccountPage.on_user_registered')
1588 email = unicode(self.ui.email_edit.text())
1589 self.userRegistered.emit(email)
1590
1591@@ -376,7 +378,7 @@
1592 messages.append(CAPTCHA_REQUIRED_ERROR)
1593 if len(messages) > 0:
1594 condition = False
1595- self.show_error(self.app_name, '\n'.join(messages))
1596+ self.show_error('\n'.join(messages))
1597 return condition
1598
1599 def set_next_validation(self):
1600@@ -401,17 +403,14 @@
1601
1602 def is_correct_email(self, email_address):
1603 """Return if the email is correct."""
1604- logger.debug('SetUpAccountPage.is_correct_email')
1605 return '@' in email_address
1606
1607 def is_correct_email_confirmation(self, email_address):
1608 """Return that the email is the same."""
1609- logger.debug('SetUpAccountPage.is_correct_email_confirmation')
1610 return unicode(self.ui.email_edit.text()) == email_address
1611
1612 def is_correct_password_confirmation(self, password):
1613 """Return that the passwords are correct."""
1614- logger.debug('SetUpAccountPage.is_correct_password_confirmation')
1615 return unicode(self.ui.password_edit.text()) == password
1616
1617 def focus_changed(self, old, now):
1618@@ -500,12 +499,14 @@
1619
1620 def on_captcha_refreshing(self):
1621 """Show overlay when captcha is refreshing."""
1622+ logger.info('SetUpAccountPage.on_captcha_refreshing')
1623 if self.isVisible():
1624 self.show_overlay()
1625 self.captcha_received = False
1626
1627 def on_captcha_refresh_complete(self):
1628 """Hide overlay when captcha finished refreshing."""
1629+ logger.info('SetUpAccountPage.on_captcha_refresh_complete')
1630 self.hide_overlay()
1631 self.captcha_received = True
1632
1633
1634=== modified file 'ubuntu_sso/qt/sso_wizard_page.py'
1635--- ubuntu_sso/qt/sso_wizard_page.py 2012-03-05 20:30:57 +0000
1636+++ ubuntu_sso/qt/sso_wizard_page.py 2012-03-20 16:10:09 +0000
1637@@ -23,31 +23,38 @@
1638 QApplication,
1639 QCursor,
1640 QFrame,
1641- QFontMetrics,
1642 QHBoxLayout,
1643+ QLabel,
1644+ QStyle,
1645 QVBoxLayout,
1646- QStyle,
1647 QWizardPage,
1648- QLabel,
1649 )
1650 from twisted.internet import defer
1651
1652 from ubuntu_sso import main
1653-from ubuntu_sso.logger import setup_gui_logging
1654-from ubuntu_sso.qt import PREFERED_UI_SIZE, TITLE_STYLE
1655+from ubuntu_sso.logger import setup_gui_logging, log_call
1656+from ubuntu_sso.qt import (
1657+ ERROR_STYLE,
1658+ maybe_elide_text,
1659+ PREFERED_UI_SIZE,
1660+ TITLE_STYLE,
1661+)
1662 from ubuntu_sso.utils.ui import GENERIC_BACKEND_ERROR
1663
1664
1665 logger = setup_gui_logging('ubuntu_sso.sso_wizard_page')
1666
1667
1668-class Header(QFrame):
1669- """Header Class for Title and Subtitle in all wizard pages."""
1670+class WizardHeader(QFrame):
1671+ """WizardHeader Class for Title and Subtitle in all wizard pages."""
1672
1673- def __init__(self):
1674+ def __init__(self, max_width, parent=None):
1675 """Create a new instance."""
1676- super(Header, self).__init__()
1677- self.setObjectName('header')
1678+ super(WizardHeader, self).__init__(parent=parent)
1679+ self.max_width = max_width
1680+ self.max_title_width = self.max_width * 0.95
1681+ self.max_subtitle_width = self.max_width * 1.8
1682+
1683 vbox = QVBoxLayout(self)
1684 vbox.setContentsMargins(0, 0, 0, 0)
1685 self.title_label = QLabel()
1686@@ -64,11 +71,8 @@
1687 def set_title(self, title):
1688 """Set the Title of the page or hide it otherwise"""
1689 if title:
1690- fm = QFontMetrics(self.subtitle_label.font())
1691- width = PREFERED_UI_SIZE['width'] * 0.95
1692- elided_text = fm.elidedText(title, Qt.ElideRight, width)
1693- self.title_label.setToolTip(title)
1694- self.title_label.setText(elided_text)
1695+ maybe_elide_text(self.title_label, title, self.max_title_width,
1696+ markup=TITLE_STYLE)
1697 self.title_label.setVisible(True)
1698 else:
1699 self.title_label.setVisible(False)
1700@@ -76,28 +80,23 @@
1701 def set_subtitle(self, subtitle):
1702 """Set the Subtitle of the page or hide it otherwise"""
1703 if subtitle:
1704- fm = QFontMetrics(self.subtitle_label.font())
1705- width = PREFERED_UI_SIZE['width'] * 1.8
1706- elided_text = fm.elidedText(subtitle, Qt.ElideRight, width)
1707- self.subtitle_label.setText(elided_text)
1708- self.subtitle_label.setToolTip(subtitle)
1709+ maybe_elide_text(self.subtitle_label, subtitle,
1710+ self.max_subtitle_width)
1711 self.subtitle_label.setVisible(True)
1712 else:
1713 self.subtitle_label.setVisible(False)
1714
1715
1716-class SSOWizardPage(QWizardPage):
1717- """Root class for all wizard pages."""
1718+class BaseWizardPage(QWizardPage):
1719+ """Base class for all wizard pages."""
1720
1721 ui_class = None
1722- _signals = {} # override in children
1723+ max_width = 0
1724 processingStarted = pyqtSignal()
1725 processingFinished = pyqtSignal()
1726
1727- def __init__(self, app_name, **kwargs):
1728- """Create a new instance."""
1729- parent = kwargs.pop('parent', None)
1730- super(SSOWizardPage, self).__init__(parent=parent)
1731+ def __init__(self, parent=None):
1732+ super(BaseWizardPage, self).__init__(parent=parent)
1733
1734 self.ui = None
1735 if self.ui_class is not None:
1736@@ -105,64 +104,39 @@
1737 self.ui = self.ui_class()
1738 self.ui.setupUi(self)
1739
1740- # store common useful data provided by the app
1741- self.app_name = app_name
1742- self.ping_url = kwargs.get('ping_url', '')
1743- self.tc_url = kwargs.get('tc_url', '')
1744- self.policy_url = kwargs.get('policy_url', '')
1745- self.help_text = kwargs.get('help_text', '')
1746+ if self.layout() is None:
1747+ self.setLayout(QVBoxLayout(self))
1748
1749 # Set the error area
1750- self.form_errors_label = QLabel(' ')
1751+ self.form_errors_label = QLabel()
1752 self.form_errors_label.setObjectName('form_errors')
1753 self.form_errors_label.setAlignment(Qt.AlignBottom)
1754 self.layout().insertWidget(0, self.form_errors_label)
1755+
1756 # Set the header
1757- self.header = Header()
1758+ self.header = WizardHeader(max_width=self.max_width)
1759 self.header.set_title(title='')
1760 self.header.set_subtitle(subtitle='')
1761 self.layout().insertWidget(0, self.header)
1762- self._signals_receivers = {}
1763- self.backend = None
1764
1765 self.layout().setAlignment(Qt.AlignLeft)
1766
1767- self.setup_page()
1768-
1769- def show_error(self, app_name, message):
1770- """Show an error message inside the page."""
1771- self.hide_overlay()
1772- fm = QFontMetrics(self.form_errors_label.font())
1773- width = PREFERED_UI_SIZE['width'] * 0.95
1774- elided_text = fm.elidedText(message, Qt.ElideRight, width)
1775- self.form_errors_label.setText(elided_text)
1776- self.form_errors_label.setToolTip(message)
1777-
1778- def hide_error(self):
1779- """Hide the label errors in the current page."""
1780- # We actually want the label with one chat, because if it is an
1781- # empty string, the height of the label is 0
1782- self.form_errors_label.setText(' ')
1783-
1784- def hide_overlay(self):
1785- """Emit the signal to notify the upper container that ends loading."""
1786- self.setEnabled(True)
1787- self.processingFinished.emit()
1788-
1789- def show_overlay(self):
1790- """Emit the signal to notify the upper container that is loading."""
1791- self.setEnabled(False)
1792- self.processingStarted.emit()
1793-
1794- @defer.inlineCallbacks
1795- def setup_page(self):
1796- """Setup the widget components."""
1797- client = yield main.get_sso_client()
1798- self.backend = client.sso_login
1799-
1800- self._setup_signals()
1801- self._set_translated_strings()
1802- self._connect_ui()
1803+ self._is_processing = False
1804+
1805+ def _get_is_processing(self):
1806+ """Is this widget processing any request?"""
1807+ return self._is_processing
1808+
1809+ def _set_is_processing(self, new_value):
1810+ """Set this widget to be processing a request."""
1811+ self._is_processing = new_value
1812+ self.setEnabled(not new_value)
1813+ if not self._is_processing:
1814+ self.processingFinished.emit()
1815+ else:
1816+ self.processingStarted.emit()
1817+
1818+ is_processing = property(fget=_get_is_processing, fset=_set_is_processing)
1819
1820 # pylint: disable=C0103
1821
1822@@ -172,7 +146,7 @@
1823
1824 def setTitle(self, title=''):
1825 """Set the Wizard Page Title."""
1826- self.header.set_title(TITLE_STYLE % title)
1827+ self.header.set_title(title)
1828
1829 def setSubTitle(self, subtitle=''):
1830 """Set the Wizard Page Subtitle."""
1831@@ -188,6 +162,74 @@
1832
1833 # pylint: enable=C0103
1834
1835+ @log_call(logger.error)
1836+ def show_error(self, message):
1837+ """Show an error message inside the page."""
1838+ self.is_processing = False
1839+ maybe_elide_text(self.form_errors_label, message,
1840+ self.max_width * 0.95, markup=ERROR_STYLE)
1841+
1842+ def hide_error(self):
1843+ """Hide the label errors in the current page."""
1844+ # We actually want the label with one empty char, because if it is an
1845+ # empty string, the height of the label is 0
1846+ self.form_errors_label.setText(' ')
1847+
1848+
1849+class SSOWizardPage(BaseWizardPage):
1850+ """Root class for all SSO specific wizard pages."""
1851+
1852+ _signals = {} # override in children
1853+ max_width = PREFERED_UI_SIZE['width']
1854+
1855+ def __init__(self, app_name, **kwargs):
1856+ """Create a new instance."""
1857+ parent = kwargs.pop('parent', None)
1858+ super(SSOWizardPage, self).__init__(parent=parent)
1859+
1860+ # store common useful data provided by the app
1861+ self.app_name = app_name
1862+ self.ping_url = kwargs.get('ping_url', '')
1863+ self.tc_url = kwargs.get('tc_url', '')
1864+ self.policy_url = kwargs.get('policy_url', '')
1865+ self.help_text = kwargs.get('help_text', '')
1866+
1867+ self._signals_receivers = {}
1868+ self.backend = None
1869+
1870+ self.setup_page()
1871+
1872+ def hide_overlay(self):
1873+ """Emit the signal to notify the upper container that ends loading."""
1874+ self.is_processing = False
1875+
1876+ def show_overlay(self):
1877+ """Emit the signal to notify the upper container that is loading."""
1878+ self.is_processing = True
1879+
1880+ @defer.inlineCallbacks
1881+ def setup_page(self):
1882+ """Setup the widget components."""
1883+ logger.info('Starting setup_page for: %r', self)
1884+ # pylint: disable=W0702,W0703
1885+ try:
1886+ # Get Backend
1887+ client = yield main.get_sso_client()
1888+ self.backend = client.sso_login
1889+ self._set_translated_strings()
1890+ self._connect_ui()
1891+ # Call _setup_signals at the end, so we ensure that the UI
1892+ # is at least styled as expected if the operations with the
1893+ # backend fails.
1894+ self._setup_signals()
1895+ except:
1896+ message = 'There was a problem trying to setup the page %r' % self
1897+ self.show_error(message)
1898+ logger.exception(message)
1899+ self.setEnabled(False)
1900+ # pylint: enable=W0702,W0703
1901+ logger.info('%r - setup_page ends, backend is %r.', self, self.backend)
1902+
1903 def _filter_by_app_name(self, f):
1904 """Excecute the decorated function only for 'self.app_name'."""
1905
1906@@ -218,11 +260,9 @@
1907
1908 def _set_translated_strings(self):
1909 """Implement in each child."""
1910- raise NotImplementedError()
1911
1912 def _connect_ui(self):
1913 """Implement in each child."""
1914- raise NotImplementedError()
1915
1916 def _handle_error(self, remote_call, handler, error):
1917 """Handle any error when calling the remote backend."""
1918
1919=== modified file 'ubuntu_sso/qt/tests/__init__.py'
1920--- ubuntu_sso/qt/tests/__init__.py 2012-03-05 20:30:57 +0000
1921+++ ubuntu_sso/qt/tests/__init__.py 2012-03-20 16:10:09 +0000
1922@@ -21,7 +21,7 @@
1923 from twisted.trial.unittest import TestCase
1924
1925 from ubuntu_sso import main, NO_OP
1926-from ubuntu_sso.qt import TITLE_STYLE
1927+from ubuntu_sso.qt import ERROR_STYLE, maybe_elide_text, TITLE_STYLE
1928 from ubuntu_sso.tests import (
1929 APP_NAME,
1930 HELP_TEXT,
1931@@ -38,6 +38,15 @@
1932 # pylint: disable=W0212
1933
1934
1935+def build_string_for_pixels(label, width):
1936+ """Return a random string that will be as big as with in pixels."""
1937+ char = 'a'
1938+ fm = QtGui.QFontMetrics(label.font())
1939+ pixel_width = fm.width(char)
1940+ chars = int(width / pixel_width)
1941+ return char * chars
1942+
1943+
1944 class FakedObject(object):
1945 """Fake an object, record every call."""
1946
1947@@ -439,6 +448,9 @@
1948 self.wizard = self.ui_wizard_class()
1949 self.patch(self.ui, 'wizard', lambda: self.wizard)
1950
1951+ self.ui.show()
1952+ self.addCleanup(self.ui.hide)
1953+
1954 def _set_called(self, *args, **kwargs):
1955 """Store 'args' and 'kwargs' for test assertions."""
1956 self._called = (args, kwargs)
1957@@ -467,6 +479,38 @@
1958
1959 self.assertEqual(self.signal_results, [signal_args])
1960
1961+ def assert_title_correct(self, title_label, expected, max_width):
1962+ """Check that the label's text is equal to 'expected'."""
1963+ label = QtGui.QLabel()
1964+ maybe_elide_text(label, expected, max_width)
1965+
1966+ self.assertEqual(TITLE_STYLE % unicode(label.text()),
1967+ unicode(title_label.text()))
1968+ self.assertEqual(unicode(label.toolTip()),
1969+ unicode(title_label.toolTip()))
1970+ self.assertTrue(title_label.isVisible())
1971+
1972+ def assert_subtitle_correct(self, subtitle_label, expected, max_width):
1973+ """Check that the subtitle is equal to 'expected'."""
1974+ label = QtGui.QLabel()
1975+ maybe_elide_text(label, expected, max_width)
1976+
1977+ self.assertEqual(unicode(label.text()), unicode(subtitle_label.text()))
1978+ self.assertEqual(unicode(label.toolTip()),
1979+ unicode(subtitle_label.toolTip()))
1980+ self.assertTrue(subtitle_label.isVisible())
1981+
1982+ def assert_error_correct(self, error_label, expected, max_width):
1983+ """Check that the error 'error_label' displays 'expected' as text."""
1984+ label = QtGui.QLabel()
1985+ maybe_elide_text(label, expected, max_width)
1986+
1987+ self.assertEqual(ERROR_STYLE % unicode(label.text()),
1988+ unicode(error_label.text()))
1989+ self.assertEqual(unicode(label.toolTip()),
1990+ unicode(error_label.toolTip()))
1991+ self.assertTrue(error_label.isVisible())
1992+
1993 def get_pixmap_data(self, pixmap):
1994 """Get the raw data of a QPixmap."""
1995 byte_array = QtCore.QByteArray()
1996@@ -507,7 +551,7 @@
1997 self.assertIn(signal, self.ui._signals)
1998 self.assertTrue(callable(self.ui._signals[signal]))
1999
2000- expected = ['_setup_signals', '_set_translated_strings', '_connect_ui']
2001+ expected = ['_set_translated_strings', '_connect_ui', '_setup_signals']
2002 self.assertEqual(expected, called)
2003
2004
2005@@ -550,14 +594,18 @@
2006 self.assertEqual(self._overlay_hide_counter, 1)
2007 self.assertTrue(self.ui.isEnabled())
2008
2009+ # pylint: disable=W0221
2010+
2011 def assert_title_correct(self, expected):
2012 """Check that the title is equal to 'expected'."""
2013- self.assertEqual(TITLE_STYLE % expected, unicode(self.ui.title()))
2014+ check = super(PageBaseTestCase, self).assert_title_correct
2015+ check(self.ui.header.title_label, expected,
2016+ self.ui.header.max_title_width)
2017
2018 def assert_subtitle_correct(self, expected):
2019 """Check that the subtitle is equal to 'expected'."""
2020- elided_text = unicode(self.ui.subTitle())
2021- elided_text = elided_text[:len(elided_text) - 1]
2022+ check = super(PageBaseTestCase, self).assert_subtitle_correct
2023+ check(self.ui.header.subtitle_label, expected,
2024+ self.ui.header.max_subtitle_width)
2025
2026- self.assertTrue(expected.startswith(elided_text))
2027- self.assertEqual(self.ui.header.subtitle_label.toolTip(), expected)
2028+ # pylint: enable=W0221
2029
2030=== modified file 'ubuntu_sso/qt/tests/login_u_p.py'
2031--- ubuntu_sso/qt/tests/login_u_p.py 2012-01-26 15:34:16 +0000
2032+++ ubuntu_sso/qt/tests/login_u_p.py 2012-03-20 16:10:09 +0000
2033@@ -39,7 +39,6 @@
2034
2035 def found(*args):
2036 """The result was received."""
2037- print "result received", args
2038 d.callback(args)
2039
2040 client.cred_manager.connect_to_signal('CredentialsFound', found)
2041@@ -61,7 +60,6 @@
2042 yield cleared
2043
2044 yield client.cred_manager.login_email_password('SUPER', args)
2045- print "called ok"
2046 yield d
2047
2048 yield client.disconnect()
2049
2050=== modified file 'ubuntu_sso/qt/tests/show_gui.py'
2051--- ubuntu_sso/qt/tests/show_gui.py 2012-02-10 17:18:22 +0000
2052+++ ubuntu_sso/qt/tests/show_gui.py 2012-03-20 16:10:09 +0000
2053@@ -46,7 +46,6 @@
2054
2055 def found(*args):
2056 """The result was received."""
2057- print "result received", args
2058 d.callback(args)
2059
2060 client.cred_manager.connect_to_signal('CredentialsFound', found)
2061@@ -62,7 +61,6 @@
2062 TC_URL_KEY: u'http://www.google.com/',
2063 UI_EXECUTABLE_KEY: 'ubuntu-sso-login-gtk',
2064 })
2065- print "called ok"
2066 yield d
2067
2068 yield client.disconnect()
2069
2070=== modified file 'ubuntu_sso/qt/tests/test_common.py'
2071--- ubuntu_sso/qt/tests/test_common.py 2011-10-28 10:41:18 +0000
2072+++ ubuntu_sso/qt/tests/test_common.py 2012-03-20 16:10:09 +0000
2073@@ -20,7 +20,13 @@
2074 from twisted.internet import defer
2075 from twisted.trial.unittest import TestCase
2076
2077-from ubuntu_sso.qt.common import (check_as_invalid,
2078+from ubuntu_sso.qt import (
2079+ build_general_error_message,
2080+ maybe_elide_text,
2081+ GENERIC_BACKEND_ERROR,
2082+)
2083+from ubuntu_sso.qt.common import (
2084+ check_as_invalid,
2085 check_as_valid,
2086 password_assistance,
2087 password_check_match,
2088@@ -30,7 +36,9 @@
2089 PASSWORD_DIGIT,
2090 PASSWORD_LENGTH,
2091 PASSWORD_MATCH,
2092- PASSWORD_UPPER)
2093+ PASSWORD_UPPER,
2094+)
2095+from ubuntu_sso.qt.tests import build_string_for_pixels
2096
2097
2098 class PasswordTestCase(TestCase):
2099@@ -234,3 +242,123 @@
2100 line_edit = QtGui.QLineEdit()
2101 check_as_invalid(line_edit)
2102 self.assertTrue(line_edit.property("formError").toBool())
2103+
2104+
2105+class ElidedTextTestCase(TestCase):
2106+ """The test case for the maybe_elide_text function."""
2107+
2108+ max_width = 100
2109+
2110+ @defer.inlineCallbacks
2111+ def setUp(self):
2112+ """Setup tests."""
2113+ yield super(ElidedTextTestCase, self).setUp()
2114+ self.ui = QtGui.QLabel()
2115+
2116+ def test_text_not_elided_if_too_short(self):
2117+ """If text is shorter than max_width, do not elide."""
2118+ text = build_string_for_pixels(self.ui, self.max_width - 1)
2119+
2120+ maybe_elide_text(self.ui, text, self.max_width)
2121+
2122+ self.assertEqual(self.ui.toolTip(), '')
2123+ self.assertEqual(self.ui.text(), text)
2124+ self.assertNotIn(u'\u2026', self.ui.text())
2125+
2126+ def test_text_not_elided_if_equals_max_width(self):
2127+ """If text is equal than max_width, do not elide."""
2128+ text = build_string_for_pixels(self.ui, self.max_width)
2129+
2130+ maybe_elide_text(self.ui, text, self.max_width)
2131+
2132+ self.assertEqual(self.ui.toolTip(), '')
2133+ self.assertEqual(self.ui.text(), text)
2134+ self.assertNotIn(u'\u2026', self.ui.text())
2135+
2136+ def test_text_elided_if_bigger_than_max_width(self):
2137+ """If text is equal than max_width, do not elide."""
2138+ text = build_string_for_pixels(self.ui, self.max_width + 10)
2139+
2140+ maybe_elide_text(self.ui, text, self.max_width)
2141+
2142+ self.assertEqual(self.ui.toolTip(), text)
2143+ expected = unicode(self.ui.text())
2144+ self.assertTrue(expected.endswith(u'\u2026'))
2145+ self.assertTrue(text.startswith(expected[:-1]))
2146+
2147+
2148+class BuildGeneralErrorMessageTestCase(TestCase):
2149+ """Test passwords conditions."""
2150+
2151+ def test_with_message(self):
2152+ """Test build_general_error_message with 'message' key."""
2153+ error = "error message"
2154+ err_dict = {'message': error}
2155+
2156+ result = build_general_error_message(err_dict)
2157+
2158+ self.assertEqual(result, error)
2159+
2160+ def test_with_all(self):
2161+ """Test build_general_error_message with 'all' key."""
2162+ error = "error message"
2163+ err_dict = {'__all__': error}
2164+
2165+ result = build_general_error_message(err_dict)
2166+
2167+ self.assertEqual(result, error)
2168+
2169+ def test_with_message_and_all(self):
2170+ """Test build_general_error_message with 'all' and 'message' key."""
2171+ error = "error message"
2172+ error2 = "error message2"
2173+ err_dict = {'__all__': error, 'message': error2}
2174+
2175+ result = build_general_error_message(err_dict)
2176+
2177+ expected = '\n'.join((error, error2))
2178+ self.assertEqual(result, expected)
2179+
2180+ def test_with_all_and_error_message(self):
2181+ """Test for 'all' and 'error_message' key."""
2182+ error = "error message"
2183+ error2 = "error message2"
2184+ err_dict = {'__all__': error, 'error_message': error2}
2185+ result = build_general_error_message(err_dict)
2186+ expected = '\n'.join((error, error2))
2187+ self.assertEqual(result, expected)
2188+
2189+ def test_with_random_keys(self):
2190+ """Test build_general_error_message with random keys."""
2191+ error = "error message"
2192+ error2 = "error message2"
2193+ err_dict = {'my_bad': error, 'odd_error': error2}
2194+
2195+ result = build_general_error_message(err_dict)
2196+
2197+ expected = '\n'.join(
2198+ [('%s: %s' % (k, v)) for k, v in err_dict.iteritems()])
2199+ self.assertEqual(result, expected)
2200+
2201+ def test_with_random_keys_with_errtype(self):
2202+ """Test build_general_error_message with random keys and errtype."""
2203+ error = "error message"
2204+ error2 = "error message2"
2205+ err_dict = {'my_bad': error, 'odd_error': error2, 'errtype': 'Danger'}
2206+
2207+ result = build_general_error_message(err_dict)
2208+
2209+ expected = '\n'.join(
2210+ [('%s: %s' % (k, v)) \
2211+ for k, v in {'my_bad': error, 'odd_error': error2}.iteritems()])
2212+ self.assertEqual(result, expected)
2213+
2214+ def test_with_not_dict(self):
2215+ """Test build_general_error_message with argument not dict."""
2216+ error = "error message"
2217+ err_dict = Exception(error)
2218+
2219+ result = build_general_error_message(err_dict)
2220+
2221+ expected = GENERIC_BACKEND_ERROR
2222+ self.assertEqual(result, expected)
2223
2224=== modified file 'ubuntu_sso/qt/tests/test_current_user_sign_in_page.py'
2225--- ubuntu_sso/qt/tests/test_current_user_sign_in_page.py 2012-03-05 20:30:57 +0000
2226+++ ubuntu_sso/qt/tests/test_current_user_sign_in_page.py 2012-03-20 16:10:09 +0000
2227@@ -24,6 +24,7 @@
2228 FakePageUiStyle,
2229 FakeWizardButtonStyle,
2230 )
2231+from ubuntu_sso.tests import EMAIL
2232
2233
2234 # pylint: disable=W0212
2235@@ -51,19 +52,32 @@
2236 self.assertTrue(button.properties['default'])
2237 self.assertFalse(button.isEnabled())
2238
2239+ def test_unicode_in_forgotten_password_link(self):
2240+ """Ensure that this label supports unicode."""
2241+ forgot_fr = u"J'ai oublié mon mot de passe"
2242+ self.patch(gui, "FORGOTTEN_PASSWORD_BUTTON", forgot_fr)
2243+ forgotten_text = gui.LINK_STYLE.format(link_url='#',
2244+ link_text=forgot_fr)
2245+ self.ui._set_translated_strings()
2246+ self.assertEqual(unicode(self.ui.ui.forgot_password_label.text()),
2247+ forgotten_text)
2248+
2249 def test_set_translated_strings(self):
2250 """Test the translated string method."""
2251 expected = gui.LOGIN_TITLE.format(app_name=self.app_name)
2252 self.assert_title_correct(expected)
2253 expected = gui.LOGIN_SUBTITLE % dict(app_name=self.app_name)
2254 self.assert_subtitle_correct(expected)
2255- self.assertEqual(self.ui.ui.email_label.text(), gui.EMAIL_LABEL)
2256- self.assertEqual(self.ui.ui.password_label.text(),
2257+ self.assertEqual(unicode(self.ui.ui.email_label.text()),
2258+ gui.EMAIL_LABEL)
2259+ self.assertEqual(unicode(self.ui.ui.password_label.text()),
2260 gui.LOGIN_PASSWORD_LABEL)
2261 text = gui.LINK_STYLE.format(link_url='#',
2262 link_text=gui.FORGOTTEN_PASSWORD_BUTTON)
2263- self.assertEqual(self.ui.ui.forgot_password_label.text(), text)
2264- self.assertEqual(self.ui.ui.sign_in_button.text(), gui.SIGN_IN_BUTTON)
2265+ self.assertEqual(unicode(self.ui.ui.forgot_password_label.text()),
2266+ text)
2267+ self.assertEqual(unicode(self.ui.ui.sign_in_button.text()),
2268+ gui.SIGN_IN_BUTTON)
2269
2270 def test_connect_ui(self):
2271 """Test the connect ui method."""
2272@@ -128,30 +142,27 @@
2273 def test_on_login_error(self):
2274 """Test the on_login_error method."""
2275 self.patch(self.ui, "show_error", self._set_called)
2276- app_name = 'my_app'
2277- self.ui.app_name = app_name
2278 error = {'errtype': 'UserNotValidated'}
2279- self.ui.on_login_error(app_name, error)
2280+
2281+ self.ui.on_login_error(self.app_name, error)
2282+
2283 self.assertEqual(self._overlay_hide_counter, 0)
2284 self.assertTrue(self.ui.isEnabled())
2285- expected = ((self.ui, 'my_app', ''), {})
2286+ expected = ((self.ui, self.app_name, ''), {})
2287 self.assertTrue(expected, self._called)
2288
2289 def test_on_logged_in(self):
2290 """Test the on_login_in method."""
2291- email = 'email@example'
2292- self.ui.ui.email_edit.setText(email)
2293-
2294- self.assert_signal_emitted(self.ui.userLoggedIn, (email,),
2295- self.ui.on_logged_in, self.app_name, email)
2296+ self.ui.ui.email_edit.setText(EMAIL)
2297+ self.assert_signal_emitted(self.ui.userLoggedIn, (EMAIL,),
2298+ self.ui.on_logged_in, self.app_name, EMAIL)
2299 self.assertTrue(self.ui.isEnabled())
2300
2301 def test_on_user_not_validated(self):
2302 """Test the navigation flow on user not validated."""
2303- email = 'email@example'
2304- self.ui.ui.email_edit.setText(email)
2305- self.assert_signal_emitted(self.ui.userNotValidated, (email,),
2306- self.ui.on_user_not_validated, self.app_name, email)
2307+ self.ui.ui.email_edit.setText(EMAIL)
2308+ self.assert_signal_emitted(self.ui.userNotValidated, (EMAIL,),
2309+ self.ui.on_user_not_validated, self.app_name, EMAIL)
2310
2311 def test_on_forgotten_password(self):
2312 """Test the on_forgotten_password method."""
2313
2314=== modified file 'ubuntu_sso/qt/tests/test_email_verification.py'
2315--- ubuntu_sso/qt/tests/test_email_verification.py 2012-03-05 18:56:50 +0000
2316+++ ubuntu_sso/qt/tests/test_email_verification.py 2012-03-20 16:10:09 +0000
2317@@ -78,14 +78,12 @@
2318 email = 'mail@example'
2319 self.ui.email = email
2320 self.ui.set_titles(email)
2321- self.assertEqual(self.ui.header.title_label.text(),
2322- email_verification_page.VERIFY_EMAIL_TITLE)
2323+ self.assert_title_correct(email_verification_page.VERIFY_EMAIL_TITLE)
2324 expected = email_verification_page.VERIFY_EMAIL_CONTENT % {
2325 "app_name": self.app_name,
2326 "email": email,
2327 }
2328- self.assertEqual(unicode(self.ui.header.subtitle_label.toolTip()),
2329- expected)
2330+ self.assert_subtitle_correct(expected)
2331
2332 def test_initialize_page(self):
2333 """Test the initialization method."""
2334@@ -100,10 +98,11 @@
2335 def test_on_email_validation_error(self):
2336 """Test the validate_email method."""
2337 self.patch(self.ui, "show_error", self._set_called)
2338- app_name = 'my_app'
2339 error = {email_verification_page: 'error in email_verification_page'}
2340- self.ui.on_email_validation_error(app_name, error)
2341- expected = ((self.ui, app_name, ''), {})
2342+
2343+ self.ui.on_email_validation_error(self.app_name, error)
2344+
2345+ expected = ((self.ui, ''), {})
2346 self.assertTrue(expected, self._called)
2347 self.assertEqual(self._overlay_hide_counter, 1)
2348
2349
2350=== modified file 'ubuntu_sso/qt/tests/test_enhanced_check_box.py'
2351--- ubuntu_sso/qt/tests/test_enhanced_check_box.py 2012-02-16 18:40:41 +0000
2352+++ ubuntu_sso/qt/tests/test_enhanced_check_box.py 2012-03-20 16:10:09 +0000
2353@@ -16,7 +16,7 @@
2354
2355 """Tests for the EnhancedCheckBox widget."""
2356
2357-from PyQt4 import QtCore
2358+from PyQt4 import QtGui, QtCore
2359
2360 from ubuntu_sso.qt import enhanced_check_box
2361 from ubuntu_sso.qt.tests import BaseTestCase
2362@@ -40,3 +40,21 @@
2363 check.setText("text")
2364 self.assertEqual(check.text(), "text")
2365 self.assertEqual(check.text(), check.text_label.text())
2366+
2367+ def test_enhanced_check_size_adjust_with_small_height(self):
2368+ """Check if the size of the EnhancedCheckBox is adjusted correctly."""
2369+ text = 't' * 200
2370+ height = 10
2371+ widget = QtGui.QWidget()
2372+ widget.setFixedSize(200, height)
2373+ check = enhanced_check_box.EnhancedCheckBox(text, widget)
2374+ self.assertTrue(check.height() > height)
2375+
2376+ def test_enhanced_check_size_adjust_with_big_height(self):
2377+ """Check if the size of the EnhancedCheckBox is adjusted correctly."""
2378+ text = 't' * 20
2379+ height = 200
2380+ widget = QtGui.QWidget()
2381+ widget.setFixedSize(200, height)
2382+ check = enhanced_check_box.EnhancedCheckBox(text, widget)
2383+ self.assertTrue(check.height() < height)
2384
2385=== modified file 'ubuntu_sso/qt/tests/test_forgotten_password.py'
2386--- ubuntu_sso/qt/tests/test_forgotten_password.py 2012-03-05 20:30:57 +0000
2387+++ ubuntu_sso/qt/tests/test_forgotten_password.py 2012-03-20 16:10:09 +0000
2388@@ -75,9 +75,10 @@
2389 subtitle = gui.FORGOTTEN_PASSWORD_SUBTITLE
2390 self.assert_subtitle_correct(subtitle.format(app_name=self.app_name))
2391
2392- self.assertEqual(self.ui.ui.email_address_label.text(),
2393+ self.assertEqual(unicode(self.ui.ui.email_address_label.text()),
2394 gui.EMAIL_LABEL)
2395- self.assertEqual(self.ui.ui.send_button.text(), gui.RESET_PASSWORD)
2396+ self.assertEqual(unicode(self.ui.ui.send_button.text()),
2397+ gui.RESET_PASSWORD)
2398
2399 def test_connect_ui(self):
2400 """Test the connect ui method."""
2401@@ -112,7 +113,8 @@
2402 """Test on_password_reset_error method."""
2403 self.patch(self.ui, "show_error", self._set_called)
2404 error = {'errtype': 'FooBarBaz'}
2405+
2406 self.ui.on_password_reset_error(self.app_name, error)
2407- expected = ((self.ui, self.app_name,
2408- gui.REQUEST_PASSWORD_TOKEN_WRONG_EMAIL), {})
2409+
2410+ expected = ((self.ui, gui.REQUEST_PASSWORD_TOKEN_WRONG_EMAIL), {})
2411 self.assertTrue(expected, self._called)
2412
2413=== modified file 'ubuntu_sso/qt/tests/test_loadingoverlay.py'
2414--- ubuntu_sso/qt/tests/test_loadingoverlay.py 2012-02-23 19:49:02 +0000
2415+++ ubuntu_sso/qt/tests/test_loadingoverlay.py 2012-03-20 16:10:09 +0000
2416@@ -29,8 +29,5 @@
2417
2418 def test_status_correct(self):
2419 """Test if the necessary variables for the animation exists"""
2420- self.ui.show()
2421- self.addCleanup(self.ui.hide)
2422-
2423 self.assertTrue(self.ui.counter is not None)
2424 self.assertTrue(self.ui.orientation is not None)
2425
2426=== modified file 'ubuntu_sso/qt/tests/test_main.py'
2427--- ubuntu_sso/qt/tests/test_main.py 2012-02-13 15:43:59 +0000
2428+++ ubuntu_sso/qt/tests/test_main.py 2012-03-20 16:10:09 +0000
2429@@ -16,27 +16,123 @@
2430
2431 """Tests for the main module."""
2432
2433+from PyQt4 import QtCore
2434+from twisted.internet import defer
2435 from twisted.trial.unittest import TestCase
2436
2437 from ubuntu_sso.qt import main
2438+from ubuntu_sso import tests
2439+
2440+
2441+# pylint: disable=C0103
2442+class FakeUi(object):
2443+
2444+ """A fake UI."""
2445+
2446+ def size(self):
2447+ """Fake size."""
2448+ return QtCore.QSize(100, 100)
2449+
2450+ def setGeometry(self, *args):
2451+ """Fake setGeometry."""
2452+
2453+ show = setGeometry
2454+
2455+
2456+class FakeDesktop(object):
2457+
2458+ """Fake Desktop Widget."""
2459+
2460+ def availableGeometry(self):
2461+ """Fake availableGeometry for desktop.-"""
2462+ return QtCore.QRect(100, 100, 100, 100)
2463+
2464+
2465+class FakeApplication(object):
2466+
2467+ """Fake QApplication."""
2468+
2469+ called = {}
2470+ __instance = None
2471+
2472+ def __init__(self, args):
2473+ self.called['args'] = args
2474+ FakeApplication.__instance = self
2475+
2476+ def setStyleSheet(self, style):
2477+ """Fake setStyleSheet."""
2478+ self.called["setStyleSheet"] = style
2479+
2480+ def styleSheet(self):
2481+ """Fake get style sheet."""
2482+ return self.called.get("setStyleSheet", '')
2483+
2484+ def desktop(self):
2485+ """Fake Desktop."""
2486+ return FakeDesktop()
2487+
2488+ def exec_(self):
2489+ """Fake exec_."""
2490+
2491+ def exit(self):
2492+ """Fake exit."""
2493+
2494+ @classmethod
2495+ def instance(cls):
2496+ """Fake instance."""
2497+ return FakeApplication.__instance
2498+# pylint: enable=C0103
2499
2500
2501 class BasicTestCase(TestCase):
2502 """Test case with a helper tracker."""
2503
2504+ @defer.inlineCallbacks
2505+ def setUp(self):
2506+ yield super(BasicTestCase, self).setUp()
2507+ self.called = []
2508+
2509+ def called_ui(**kw):
2510+ """record ui call."""
2511+ self.called.append(('GUI', kw))
2512+ return FakeUi()
2513+
2514+ self.patch(main, 'UbuntuSSOClientGUI', called_ui)
2515+ self.patch(main.QtGui, 'QApplication', FakeApplication)
2516+
2517 def test_main(self):
2518 """Calling main.main() a UI instance is created."""
2519- called = []
2520- self.patch(main, 'UbuntuSSOClientGUI',
2521- lambda **kw: called.append(('GUI', kw)))
2522- self.patch(main.QtGui.QApplication, 'exec_',
2523- lambda *a: called.append('main'))
2524-
2525+ kwargs = dict(app_name='APP_NAME', foo='foo', bar='bar',
2526+ baz='yadda', yadda=0)
2527+ main.main(**kwargs)
2528+
2529+ kwargs['close_callback'] = main.QtGui.QApplication.instance().exit
2530+ self.assertEqual(self.called, [('GUI', kwargs)])
2531+
2532+ def test_main_args(self):
2533+ """Calling main.main() a UI instance is created."""
2534+ arg_list = (tests.APP_NAME, tests.NAME, tests.PASSWORD,
2535+ tests.EMAIL_TOKEN)
2536+ kwargs = dict(app_name=arg_list[0].encode('utf-8'),
2537+ foo=arg_list[1].encode('utf-8'),
2538+ bar=arg_list[2].encode('utf-8'),
2539+ baz=arg_list[3].encode('utf-8'))
2540+ main.main(**kwargs)
2541+
2542+ kwargs['close_callback'] = main.QtGui.QApplication.instance().exit
2543+ expected = dict(app_name=arg_list[0], foo=arg_list[1],
2544+ bar=arg_list[2], baz=arg_list[3],
2545+ close_callback=main.QtGui.QApplication.instance().exit)
2546+ self.assertEqual(self.called, [('GUI', expected)])
2547+
2548+ def test_styles_load(self):
2549+ """Test that all stylesheets load."""
2550 kwargs = dict(foo='foo', bar='bar', baz='yadda', yadda=0)
2551 main.main(**kwargs)
2552-
2553- kwargs['close_callback'] = main.QtGui.QApplication.instance().exit
2554- self.assertEqual(called, [('GUI', kwargs), 'main'])
2555-
2556- test_main.skip = 'Failing with QObject::startTimer: QTimer can only be ' \
2557- 'used with threads started with QThread'
2558+ data = []
2559+ for qss_name in (main.PLATFORM_QSS, ":/stylesheet.qss"):
2560+ qss = QtCore.QResource(qss_name)
2561+ data.append(unicode(qss.data()))
2562+ self.assertEqual(
2563+ unicode(main.QtGui.QApplication.instance().styleSheet()),
2564+ '\n'.join(data))
2565
2566=== modified file 'ubuntu_sso/qt/tests/test_proxy_dialog.py'
2567--- ubuntu_sso/qt/tests/test_proxy_dialog.py 2012-02-13 16:16:03 +0000
2568+++ ubuntu_sso/qt/tests/test_proxy_dialog.py 2012-03-20 16:10:09 +0000
2569@@ -256,7 +256,7 @@
2570 self.patch(dialog, 'done', fake_done)
2571
2572 dialog._on_cancel_clicked()
2573- self.assertIn(('done', proxy_dialog.USER_CANCELATION), called)
2574+ self.assertIn(('done', proxy_dialog.USER_CANCELLATION), called)
2575
2576 def assert_save_button(self, set_creds_callback, result_number):
2577 """Test the save button execution."""
2578@@ -288,7 +288,7 @@
2579 def test_on_save_clicked_correct(self):
2580 """Test that we do save the creds."""
2581 set_creds_cb = lambda: defer.succeed(True)
2582- result_number = proxy_dialog.CREDS_ACQUIRED
2583+ result_number = proxy_dialog.USER_SUCCESS
2584 self.assert_save_button(set_creds_cb, result_number)
2585
2586
2587
2588=== modified file 'ubuntu_sso/qt/tests/test_reset_password.py'
2589--- ubuntu_sso/qt/tests/test_reset_password.py 2012-03-05 18:56:50 +0000
2590+++ ubuntu_sso/qt/tests/test_reset_password.py 2012-03-20 16:10:09 +0000
2591@@ -70,27 +70,23 @@
2592
2593 def test_initialize(self):
2594 """Check the Title and Subtitle."""
2595- self.ui.show()
2596 self.ui.initializePage()
2597- self.addCleanup(self.ui.hide)
2598 self.assert_title_correct(RESET_TITLE)
2599 self.assert_subtitle_correct(RESET_SUBTITLE)
2600- self.assertEqual(self.ui.ui.password_label.text(), PASSWORD1_ENTRY)
2601- self.assertEqual(self.ui.ui.confirm_password_label.text(),
2602+ self.assertEqual(unicode(self.ui.ui.password_label.text()),
2603+ PASSWORD1_ENTRY)
2604+ self.assertEqual(unicode(self.ui.ui.confirm_password_label.text()),
2605 PASSWORD2_ENTRY)
2606- self.assertEqual(self.ui.ui.reset_code.text(), RESET_CODE_ENTRY)
2607+ self.assertEqual(unicode(self.ui.ui.reset_code.text()),
2608+ RESET_CODE_ENTRY)
2609
2610 def test_focus_changed_password_visibility(self):
2611 """Check visibility changes when focus_changed() is executed."""
2612- self.ui.show()
2613- self.addCleanup(self.ui.hide)
2614 self.ui.focus_changed(None, self.ui.ui.password_line_edit)
2615 self.assertTrue(self.ui.ui.password_assistance.isVisible())
2616
2617 def test_show_hide_event(self):
2618 """Check connections to focusChanged on show and hide event."""
2619- self.ui.show()
2620- self.addCleanup(self.ui.hide)
2621 self.assertEqual(QtGui.QApplication.instance().receivers(
2622 QtCore.SIGNAL("focusChanged(QWidget*, QWidget*)")), 1)
2623 self.ui.hide()
2624@@ -102,24 +98,20 @@
2625 def test_focus_changed_1(self):
2626 """Check functions execution when focus_changed() is executed."""
2627 self.patch(common, 'password_default_assistance', self._set_called)
2628-
2629- self.ui.show()
2630- self.addCleanup(self.ui.hide)
2631-
2632 self.assertFalse(self._called)
2633+
2634 self.ui.focus_changed(None, self.ui.ui.password_line_edit)
2635+
2636 self.assertTrue(self.ui.ui.password_assistance.isVisible())
2637 self.assertTrue(self._called)
2638
2639 def test_focus_changed_2(self):
2640 """Check functions execution when focus_changed() is executed."""
2641 self.patch(common, 'password_check_match', self._set_called)
2642-
2643- self.ui.show()
2644- self.addCleanup(self.ui.hide)
2645-
2646 self.assertFalse(self._called)
2647+
2648 self.ui.focus_changed(None, self.ui.ui.confirm_password_line_edit)
2649+
2650 self.assertTrue(self.ui.ui.password_assistance.isVisible())
2651 self.assertTrue(self._called)
2652
2653
2654=== modified file 'ubuntu_sso/qt/tests/test_setup_account.py'
2655--- ubuntu_sso/qt/tests/test_setup_account.py 2012-03-06 14:13:10 +0000
2656+++ ubuntu_sso/qt/tests/test_setup_account.py 2012-03-20 16:10:09 +0000
2657@@ -43,13 +43,20 @@
2658 """
2659 self.ui.ui.name_edit.setText("")
2660 self.ui.name_assistance()
2661- self.ui.show()
2662- self.addCleanup(self.ui.hide)
2663 self.assertTrue(self.ui.ui.name_assistance.isVisible())
2664- self.assertEqual(
2665- unicode(self.ui.ui.name_assistance.text()),
2666- gui.ERROR_STYLE % gui.EMPTY_NAME)
2667- self.ui.hide()
2668+ self.assert_error_correct(self.ui.ui.name_assistance, gui.EMPTY_NAME,
2669+ max_width=self.ui.header.max_title_width)
2670+
2671+ def test_hide_error_on_refresh_clicked(self):
2672+ """Hide form errors when the user click to refresh the captcha."""
2673+ self.ui.show_error('error')
2674+ self.assert_error_correct(self.ui.form_errors_label, 'error',
2675+ max_width=self.ui.header.max_title_width)
2676+
2677+ self.ui.ui.refresh_label.linkActivated.emit('error')
2678+
2679+ message = unicode(self.ui.form_errors_label.text())
2680+ self.assertEqual(message, ' ')
2681
2682 def test_enable_setup_button_with_visible_check(self):
2683 """Test _enable_setup_button method with terms check visible."""
2684@@ -65,8 +72,6 @@
2685 self.ui.ui.captcha_solution_edit.setText('captcha solution')
2686 self.ui.terms_checkbox.setChecked(True)
2687
2688- self.ui.show()
2689- self.addCleanup(self.ui.hide)
2690 self.ui.terms_checkbox.setVisible(True)
2691 self.ui.ui.captcha_solution_edit.textEdited.emit('')
2692 self.assertTrue(self.ui.set_up_button.isEnabled())
2693@@ -84,8 +89,6 @@
2694 self.ui.ui.confirm_password_edit.setText(password)
2695 self.ui.ui.captcha_solution_edit.setText('captcha solution')
2696
2697- self.ui.show()
2698- self.addCleanup(self.ui.hide)
2699 self.ui.terms_checkbox.setVisible(False)
2700 self.ui.ui.captcha_solution_edit.textEdited.emit('')
2701 self.assertTrue(self.ui.set_up_button.isEnabled())
2702@@ -114,8 +117,6 @@
2703
2704 def test_password_focus_gain(self):
2705 """Check functions execution when focus_changed() is executed."""
2706- self.ui.show()
2707- self.addCleanup(self.ui.hide)
2708 self.ui.ui.password_assistance.setVisible(False)
2709 self.assertFalse(self.ui.ui.password_assistance.isVisible())
2710 self.patch(self.ui, 'name_assistance', self._set_called)
2711@@ -174,7 +175,6 @@
2712 """Test on_user_registered method."""
2713 email = 'email@example'
2714 self.ui.ui.email_edit.setText(email)
2715-
2716 self.assert_signal_emitted(self.ui.userRegistered, (email,),
2717 self.ui.on_user_registered, self.app_name, email)
2718
2719@@ -193,8 +193,6 @@
2720 def test_initialize_page(self):
2721 """Widgets are properly initialized."""
2722 self.ui.initializePage()
2723- self.ui.show()
2724- self.addCleanup(self.ui.hide)
2725
2726 # set up account button
2727 expected = [QtGui.QWizard.BackButton, QtGui.QWizard.Stretch,
2728@@ -210,12 +208,15 @@
2729 self.assertFalse(self.ui.captcha_received)
2730
2731 # labels
2732- self.assertEqual(self.ui.ui.name_label.text(), gui.NAME_ENTRY)
2733- self.assertEqual(self.ui.ui.email_label.text(), gui.EMAIL)
2734- self.assertEqual(self.ui.ui.confirm_email_label.text(),
2735+ self.assertEqual(unicode(self.ui.ui.name_label.text()),
2736+ gui.NAME_ENTRY)
2737+ self.assertEqual(unicode(self.ui.ui.email_label.text()),
2738+ gui.EMAIL)
2739+ self.assertEqual(unicode(self.ui.ui.confirm_email_label.text()),
2740 gui.RETYPE_EMAIL)
2741- self.assertEqual(self.ui.ui.password_label.text(), gui.PASSWORD)
2742- self.assertEqual(self.ui.ui.confirm_password_label.text(),
2743+ self.assertEqual(unicode(self.ui.ui.password_label.text()),
2744+ gui.PASSWORD)
2745+ self.assertEqual(unicode(self.ui.ui.confirm_password_label.text()),
2746 gui.RETYPE_PASSWORD)
2747
2748 # assistants
2749@@ -230,8 +231,6 @@
2750 self.patch(self.ui, 'set_next_validation', self._set_called)
2751 self.ui.initializePage()
2752 self.ui.captcha_received = True
2753- self.ui.show()
2754- self.addCleanup(self.ui.hide)
2755
2756 self.ui.set_up_button.clicked.emit(False)
2757 self.assertEqual(self._called, ((False,), {}))
2758@@ -239,13 +238,10 @@
2759 def test_set_error_message(self):
2760 """Check the state of the label after calling: set_error_message."""
2761 self.ui.email_assistance()
2762- self.ui.show()
2763- self.addCleanup(self.ui.hide)
2764 self.ui.set_error_message(self.ui.ui.email_assistance, "message")
2765 self.assertTrue(self.ui.ui.email_assistance.isVisible())
2766- self.assertEqual(
2767- unicode(self.ui.ui.email_assistance.text()),
2768- gui.ERROR_STYLE % "message")
2769+ self.assert_error_correct(self.ui.ui.email_assistance, "message",
2770+ max_width=self.ui.header.max_title_width)
2771
2772 def test_blank_name(self):
2773 """Status when the name field is blank (spaces).
2774@@ -255,13 +251,9 @@
2775 """
2776 self.ui.ui.name_edit.setText(" ")
2777 self.ui.name_assistance()
2778- self.ui.show()
2779- self.addCleanup(self.ui.hide)
2780 self.assertTrue(self.ui.ui.name_assistance.isVisible())
2781- self.assertEqual(
2782- unicode(self.ui.ui.name_assistance.text()),
2783- gui.ERROR_STYLE % gui.EMPTY_NAME)
2784- self.ui.hide()
2785+ self.assert_error_correct(self.ui.ui.name_assistance, gui.EMPTY_NAME,
2786+ max_width=self.ui.header.max_title_width)
2787
2788 def test_valid_name(self):
2789 """Status when the name field is valid.
2790@@ -270,10 +262,7 @@
2791 """
2792 self.ui.ui.name_edit.setText("John Doe")
2793 self.ui.name_assistance()
2794- self.ui.show()
2795- self.addCleanup(self.ui.hide)
2796 self.assertFalse(self.ui.ui.name_assistance.isVisible())
2797- self.ui.hide()
2798
2799 def test_invalid_email(self):
2800 """Status when the email field has no @.
2801@@ -283,12 +272,10 @@
2802 """
2803 self.ui.ui.email_edit.setText("foobar")
2804 self.ui.email_assistance()
2805- self.ui.show()
2806- self.addCleanup(self.ui.hide)
2807 self.assertTrue(self.ui.ui.email_assistance.isVisible())
2808- self.assertEqual(
2809- unicode(self.ui.ui.email_assistance.text()),
2810- gui.ERROR_STYLE % gui.INVALID_EMAIL)
2811+ self.assert_error_correct(self.ui.ui.email_assistance,
2812+ gui.INVALID_EMAIL,
2813+ max_width=self.ui.header.max_title_width)
2814
2815 def test_valid_email(self):
2816 """Status when the email field has a @.
2817@@ -297,10 +284,7 @@
2818 """
2819 self.ui.ui.email_edit.setText("foo@bar")
2820 self.ui.email_assistance()
2821- self.ui.show()
2822- self.addCleanup(self.ui.hide)
2823 self.assertFalse(self.ui.ui.email_assistance.isVisible())
2824- self.ui.hide()
2825
2826 def test_matching_emails(self):
2827 """Status when the email fields match.
2828@@ -310,10 +294,7 @@
2829 self.ui.ui.email_edit.setText("foo@bar")
2830 self.ui.ui.confirm_email_edit.setText("foo@bar")
2831 self.ui.confirm_email_assistance()
2832- self.ui.show()
2833- self.addCleanup(self.ui.hide)
2834 self.assertFalse(self.ui.ui.confirm_email_assistance.isVisible())
2835- self.ui.hide()
2836
2837 def test_not_matching_emails(self):
2838 """Status when the email fields don't match.
2839@@ -324,18 +305,13 @@
2840 self.ui.ui.email_edit.setText("foo@bar")
2841 self.ui.ui.confirm_email_edit.setText("foo@baz")
2842 self.ui.confirm_email_assistance()
2843- self.ui.show()
2844- self.addCleanup(self.ui.hide)
2845 self.assertTrue(self.ui.ui.confirm_email_assistance.isVisible())
2846- self.assertEqual(
2847- unicode(self.ui.ui.confirm_email_assistance.text()),
2848- gui.ERROR_STYLE % gui.EMAIL_MATCH)
2849- self.ui.hide()
2850+ self.assert_error_correct(self.ui.ui.confirm_email_assistance,
2851+ gui.EMAIL_MATCH,
2852+ max_width=self.ui.header.max_title_width)
2853
2854 def test_focus_changed_password_visibility(self):
2855 """Check visibility changes when focus_changed() is executed."""
2856- self.ui.show()
2857- self.addCleanup(self.ui.hide)
2858 self.ui.focus_changed(None, self.ui.ui.password_edit)
2859 self.assertTrue(self.ui.ui.password_assistance.isVisible())
2860
2861@@ -350,33 +326,41 @@
2862 self.ui.showEvent(QtGui.QShowEvent())
2863 self.ui.hideEvent(QtGui.QHideEvent())
2864
2865- def test_on_captcha_refreshing(self):
2866+ def test_on_captcha_refreshing_visible(self):
2867 """Check the state of the overlay on captcha refreshing."""
2868- self.assertEqual(self._overlay_show_counter, 0)
2869- self.ui.on_captcha_refreshing()
2870- self.assertEqual(self._overlay_show_counter, 0)
2871- self.assertTrue(self.ui.isEnabled())
2872- self.ui.captcha_received = True
2873- self.ui.show()
2874- self.addCleanup(self.ui.hide)
2875- self.assertEqual(self._overlay_show_counter, 0)
2876- self.assertTrue(self.ui.isEnabled())
2877- self.ui.on_captcha_refreshing()
2878+ self.ui.hide_overlay()
2879+
2880+ self.assertEqual(self._overlay_show_counter, 0)
2881+ self.assertTrue(self.ui.isEnabled())
2882+
2883+ self.ui.on_captcha_refreshing()
2884+
2885 self.assertFalse(self.ui.isEnabled())
2886 self.assertEqual(self._overlay_show_counter, 1)
2887
2888+ def test_on_captcha_refreshing_not_visible(self):
2889+ """Check the state of the overlay on captcha refreshing."""
2890+ self.ui.hide_overlay()
2891+
2892+ self.assertEqual(self._overlay_show_counter, 0)
2893+ self.assertTrue(self.ui.isEnabled())
2894+
2895+ self.ui.hide()
2896+ self.ui.on_captcha_refreshing()
2897+
2898+ self.assertEqual(self._overlay_show_counter, 0)
2899+ self.assertTrue(self.ui.isEnabled())
2900+
2901 def test_on_captcha_refresh_complete(self):
2902 """Check the state of the overlay on captcha refreshing complete."""
2903 self.assertEqual(self._overlay_hide_counter, 0)
2904- self.assertTrue(self.ui.isEnabled())
2905+
2906 self.ui.on_captcha_refresh_complete()
2907+
2908 self.assertEqual(self._overlay_hide_counter, 1)
2909- self.assertTrue(self.ui.isEnabled())
2910
2911 def test_hide_error_on_refresh_captcha(self):
2912 """Test that the errors are hidden on refresh captcha."""
2913- self.ui.show()
2914- self.addCleanup(self.ui.hide)
2915- self.ui.show_error(self.app_name, 'error-message')
2916+ self.ui.show_error('error-message')
2917 self.ui.ui.refresh_label.linkActivated.emit('link')
2918 self.assertEqual(self.ui.form_errors_label.text(), ' ')
2919
2920=== modified file 'ubuntu_sso/qt/tests/test_ssl_dialog.py'
2921--- ubuntu_sso/qt/tests/test_ssl_dialog.py 2012-03-05 16:32:37 +0000
2922+++ ubuntu_sso/qt/tests/test_ssl_dialog.py 2012-03-20 16:10:09 +0000
2923@@ -142,7 +142,8 @@
2924
2925 def test_set_expander(self):
2926 """Test that the expander is correctly set."""
2927- self.assertEqual(SSL_CERT_DETAILS, self.dialog.expander.text())
2928+ self.assertEqual(SSL_CERT_DETAILS,
2929+ unicode(self.dialog.expander.text()))
2930 self.assertNotEqual(None, self.dialog.expander.content)
2931 self.assertEqual(2, self.dialog.ui.expander_layout.indexOf(
2932 self.dialog.expander))
2933
2934=== modified file 'ubuntu_sso/qt/tests/test_sso_wizard_page.py'
2935--- ubuntu_sso/qt/tests/test_sso_wizard_page.py 2012-03-05 18:56:50 +0000
2936+++ ubuntu_sso/qt/tests/test_sso_wizard_page.py 2012-03-20 16:10:09 +0000
2937@@ -16,98 +16,137 @@
2938
2939 """Test the SSOWizardPage and related."""
2940
2941-from twisted.internet import defer
2942-
2943-from ubuntu_sso.qt import PREFERED_UI_SIZE
2944-from ubuntu_sso.qt.setup_account_page import SetupAccountPage
2945-from ubuntu_sso.qt.sso_wizard_page import Header
2946-from ubuntu_sso.qt.tests import BaseTestCase, PageBaseTestCase
2947-
2948-
2949-class HeaderTest(BaseTestCase):
2950+from ubuntu_sso.qt import PREFERED_UI_SIZE, sso_wizard_page as gui
2951+from ubuntu_sso.qt.tests import (
2952+ APP_NAME,
2953+ BaseTestCase,
2954+ build_string_for_pixels,
2955+ PageBaseTestCase,
2956+)
2957+
2958+
2959+MAX_WIDTH = 100
2960+
2961+
2962+class WizardHeaderTestCase(BaseTestCase):
2963
2964 """Tests for injected Header in each Wizard Page."""
2965
2966- @defer.inlineCallbacks
2967- def setUp(self):
2968- yield super(HeaderTest, self).setUp()
2969- self.header = Header()
2970+ kwargs = dict(max_width=MAX_WIDTH)
2971+ ui_class = gui.WizardHeader
2972+ ui_wizard_class = None
2973
2974 def test_label_state(self):
2975 """Check the title and subtitle properties."""
2976- self.assertTrue(self.header.title_label.wordWrap())
2977- self.assertTrue(self.header.subtitle_label.wordWrap())
2978- self.assertFalse(self.header.title_label.isVisible())
2979- self.assertFalse(self.header.subtitle_label.isVisible())
2980+ self.assertTrue(self.ui.title_label.wordWrap())
2981+ self.assertTrue(self.ui.subtitle_label.wordWrap())
2982+ self.assertFalse(self.ui.title_label.isVisible())
2983+ self.assertFalse(self.ui.subtitle_label.isVisible())
2984
2985 def test_set_title(self):
2986 """Check if set_title works ok, showing the widget if necessary."""
2987- self.header.set_title('title')
2988- self.assertEqual(self.header.title_label.text(), 'title')
2989- self.header.show()
2990- self.assertTrue(self.header.title_label.isVisible())
2991- self.header.hide()
2992+ max_width = self.ui.max_title_width
2993+ text = build_string_for_pixels(self.ui.title_label, max_width)
2994+
2995+ self.ui.set_title(text)
2996+
2997+ self.assert_title_correct(self.ui.title_label, text, max_width)
2998
2999 def test_set_elided_title(self):
3000 """Check if set_title adds the ellipsis when necessary."""
3001 # add an extra letter so we ensure this needs to be trimmed
3002- title = 'a' * int(PREFERED_UI_SIZE['width'] * 0.95) + 'a'
3003- self.header.set_title(title)
3004- self.assertEqual(self.header.title_label.toolTip(), title)
3005- expected = unicode(self.header.title_label.text())
3006- self.assertTrue(expected.endswith(u'\u2026'))
3007+ max_width = self.ui.max_title_width
3008+ text = build_string_for_pixels(self.ui.title_label, max_width + 10)
3009+
3010+ self.ui.set_title(text)
3011+
3012+ self.assert_title_correct(self.ui.title_label, text, max_width)
3013
3014 def test_set_empty_title(self):
3015 """Check if the widget is hidden for empty title."""
3016- self.header.set_title('')
3017- self.assertFalse(self.header.title_label.isVisible())
3018+ self.ui.set_title('')
3019+
3020+ self.assertEqual(self.ui.title_label.toolTip(), '')
3021+ self.assertFalse(self.ui.title_label.isVisible())
3022
3023 def test_set_subtitle(self):
3024 """Check if set_subtitle works ok, showing the widget if necessary."""
3025- self.header.set_subtitle('subtitle')
3026- self.assertEqual(self.header.subtitle_label.text(), 'subtitle')
3027- self.header.show()
3028- self.assertTrue(self.header.subtitle_label.isVisible())
3029- self.header.hide()
3030+ max_width = self.ui.max_subtitle_width
3031+ text = build_string_for_pixels(self.ui.subtitle_label, max_width)
3032+
3033+ self.ui.set_subtitle(text)
3034+
3035+ self.assert_subtitle_correct(self.ui.subtitle_label, text, max_width)
3036
3037 def test_set_elided_subtitle(self):
3038 """Check if set_subtitle adds the ellipsis when necessary."""
3039- subtitle = 'a' * int(PREFERED_UI_SIZE['width'] * 0.95) + 'a'
3040- self.header.set_subtitle(subtitle)
3041- self.assertEqual(self.header.subtitle_label.toolTip(), subtitle)
3042- expected = unicode(self.header.subtitle_label.text())
3043- self.assertTrue(expected.endswith(u'\u2026'))
3044+ max_width = self.ui.max_subtitle_width
3045+ text = build_string_for_pixels(self.ui.subtitle_label, max_width + 10)
3046+
3047+ self.ui.set_subtitle(text)
3048+
3049+ self.assert_subtitle_correct(self.ui.subtitle_label, text, max_width)
3050
3051 def test_set_empty_subtitle(self):
3052 """Check if the widget is hidden for empty subtitle."""
3053- self.header.set_title('')
3054- self.assertFalse(self.header.title_label.isVisible())
3055-
3056-
3057-class SSOWizardPageTest(PageBaseTestCase):
3058-
3059- """Tests for SSOWizardPage."""
3060-
3061- ui_class = SetupAccountPage
3062+ self.ui.set_subtitle('')
3063+
3064+ self.assertEqual(self.ui.subtitle_label.toolTip(), '')
3065+ self.assertFalse(self.ui.subtitle_label.isVisible())
3066+
3067+
3068+class BaseWizardPageTestCase(PageBaseTestCase):
3069+
3070+ """Tests for SSOWizardPage."""
3071+
3072+ kwargs = {}
3073+ ui_class = gui.BaseWizardPage
3074+
3075+ def test_max_width(self):
3076+ """The max_width is correct."""
3077+ self.assertEqual(self.ui.max_width, 0)
3078+
3079+
3080+class SSOWizardPageTestCase(BaseWizardPageTestCase):
3081+
3082+ """Tests for SSOWizardPage."""
3083+
3084+ kwargs = dict(app_name=APP_NAME)
3085+ ui_class = gui.SSOWizardPage
3086+
3087+ def test_max_width(self):
3088+ """The max_width is correct."""
3089+ self.assertEqual(self.ui.max_width, PREFERED_UI_SIZE['width'])
3090
3091 def test_show_error(self):
3092 """Test show_error with a normal length string."""
3093 message = 'error-message'
3094- self.ui.show_error(self.app_name, message)
3095- self.assertEqual(self.ui.form_errors_label.toolTip(), message)
3096- expected = unicode(self.ui.form_errors_label.text())
3097- self.assertEqual(expected, message)
3098+ self.ui.show_error(message)
3099+
3100+ self.assert_error_correct(self.ui.form_errors_label, message,
3101+ self.ui.header.max_title_width)
3102
3103 def test_show_error_long_text(self):
3104 """Test show_error with a long length string."""
3105- message = 'a' * int(PREFERED_UI_SIZE['width'] * 0.95) + 'a'
3106- self.ui.show_error(self.app_name, message)
3107- self.assertEqual(self.ui.form_errors_label.toolTip(), message)
3108- expected = unicode(self.ui.form_errors_label.text())
3109- self.assertTrue(expected.endswith(u'\u2026'))
3110+ message = build_string_for_pixels(self.ui.form_errors_label,
3111+ self.ui.header.max_title_width + 10)
3112+
3113+ self.ui.show_error(message)
3114+ self.assert_error_correct(self.ui.form_errors_label, message,
3115+ self.ui.header.max_title_width)
3116
3117 def test_hide_error(self):
3118 """Test show_error with a long length string."""
3119- message = ' '
3120 self.ui.hide_error()
3121- self.assertEqual(self.ui.form_errors_label.text(), message)
3122+
3123+ self.assertEqual(self.ui.form_errors_label.text(), ' ')
3124+
3125+ def test_setup_page_with_failing_backend(self):
3126+ """Test how the ui react with an invalid backend."""
3127+ self.patch(gui.main, "get_sso_client", lambda: None)
3128+ self.patch(self.ui, "show_error", self._set_called)
3129+ self.ui.setup_page()
3130+ reason = 'There was a problem trying to setup the page %r' % self.ui
3131+ expected = ((reason,), {})
3132+ self.assertEqual(expected, self._called)
3133+ self.assertFalse(self.ui.isEnabled())
3134
3135=== modified file 'ubuntu_sso/qt/tests/test_ubuntu_sso_wizard.py'
3136--- ubuntu_sso/qt/tests/test_ubuntu_sso_wizard.py 2012-03-05 14:37:43 +0000
3137+++ ubuntu_sso/qt/tests/test_ubuntu_sso_wizard.py 2012-03-20 16:10:09 +0000
3138@@ -19,6 +19,11 @@
3139 from PyQt4 import QtGui
3140 from twisted.internet import defer
3141
3142+from ubuntu_sso.tests import (
3143+ APP_NAME,
3144+ EMAIL,
3145+ PASSWORD,
3146+)
3147 from ubuntu_sso.qt import PREFERED_UI_SIZE, ubuntu_sso_wizard
3148 from ubuntu_sso.qt.tests import (
3149 BaseTestCase,
3150@@ -86,6 +91,11 @@
3151 finish_button.clicked.emit(True)
3152 self.assertEqual(self._called, ((None,), {}))
3153
3154+ def test_window_title(self):
3155+ """Check the window title for the application."""
3156+ title = unicode(self.ui.windowTitle())
3157+ self.assertEqual(title, ubuntu_sso_wizard.WINDOW_TITLE)
3158+
3159
3160 class UbuntuSSOWizardTestCase(BaseTestCase):
3161
3162@@ -102,13 +112,9 @@
3163
3164 def test_window_size(self):
3165 """check the window size."""
3166- self.ui.show()
3167- self.addCleanup(self.ui.hide)
3168 size = self.ui.size()
3169- # pylint: disable=E1101
3170- self.assertGreaterEqual(size.height(), PREFERED_UI_SIZE['height'])
3171- self.assertGreaterEqual(size.width(), PREFERED_UI_SIZE['width'])
3172- # pylint: enable=E1101
3173+ self.assertTrue(size.height() >= PREFERED_UI_SIZE['height'])
3174+ self.assertTrue(size.width() >= PREFERED_UI_SIZE['width'])
3175
3176 def test_move_to_success_page_state(self):
3177 """Test _move_to_success_page method."""
3178@@ -123,6 +129,9 @@
3179
3180 def test_overlay_shows(self):
3181 """Test if the signals call the overlay.show properly."""
3182+ # reset the counter
3183+ self.ui.overlay.show_counter = 0
3184+
3185 for page in self.ui._pages:
3186 page.show_overlay()
3187
3188@@ -130,6 +139,9 @@
3189
3190 def test_overlay_hides(self):
3191 """Test if the signals call the overlay.show properly."""
3192+ # reset the counter
3193+ self.ui.overlay.show_counter = 0
3194+
3195 for page in self.ui._pages:
3196 page.hide_overlay()
3197
3198@@ -146,3 +158,21 @@
3199 self.ui.reset_password.passwordChanged.emit('')
3200 expected = ((self.ui.current_user,), {})
3201 self.assertEqual(expected, self._called)
3202+
3203+ def test_email_verification_page_params_from_current_user(self):
3204+ """Tests that email_verification_page receives the proper params."""
3205+ self.ui._next_id = self.ui.current_user_page_id
3206+ self.ui.next()
3207+ self.ui.current_user.ui.email_edit.setText(EMAIL)
3208+ self.ui.current_user.ui.password_edit.setText(PASSWORD)
3209+ self.ui.current_user.on_user_not_validated(APP_NAME, EMAIL)
3210+ self.assertEqual(EMAIL, self.ui.email_verification.email)
3211+ self.assertEqual(PASSWORD, self.ui.email_verification.password)
3212+
3213+ def test_email_verification_page_params_from_setup(self):
3214+ """Tests that email_verification_page receives the proper params."""
3215+ self.ui.setup_account.ui.email_edit.setText(EMAIL)
3216+ self.ui.setup_account.ui.password_edit.setText(PASSWORD)
3217+ self.ui.setup_account.on_user_registered(APP_NAME, {})
3218+ self.assertEqual(EMAIL, self.ui.email_verification.email)
3219+ self.assertEqual(PASSWORD, self.ui.email_verification.password)
3220
3221=== modified file 'ubuntu_sso/qt/ubuntu_sso_wizard.py'
3222--- ubuntu_sso/qt/ubuntu_sso_wizard.py 2012-03-05 20:30:57 +0000
3223+++ ubuntu_sso/qt/ubuntu_sso_wizard.py 2012-03-20 16:10:09 +0000
3224@@ -30,7 +30,7 @@
3225 USER_SUCCESS,
3226 )
3227 from ubuntu_sso.logger import setup_gui_logging
3228-from ubuntu_sso.qt import PREFERED_UI_SIZE
3229+from ubuntu_sso.qt import PREFERED_UI_SIZE, WINDOW_TITLE
3230 from ubuntu_sso.qt.current_user_sign_in_page import CurrentUserSignInPage
3231 from ubuntu_sso.qt.email_verification_page import EmailVerificationPage
3232 from ubuntu_sso.qt.error_page import ErrorPage
3233@@ -141,6 +141,8 @@
3234
3235 def _go_back_to_page(self, page):
3236 """Move back until it reaches the 'page'."""
3237+ logger.debug('Moving back from page: %s, to page: %s',
3238+ self.currentPage(), page)
3239 page_id = self._pages[page]
3240 visited_pages = self.visitedPages()
3241 for index in reversed(visited_pages):
3242@@ -150,30 +152,42 @@
3243
3244 def _move_to_reset_password_page(self):
3245 """Move to the reset password page wizard."""
3246+ logger.debug('Moving to ResetPasswordPage from: %s',
3247+ self.currentPage())
3248 self._next_id = self.reset_password_page_id
3249 self.next()
3250 self._next_id = -1
3251
3252- def _move_to_email_verification_page(self):
3253+ def _move_to_email_verification_page(self, email):
3254 """Move to the email verification page wizard."""
3255+ logger.debug('Moving to EmailVerificationPage from: %s',
3256+ self.currentPage())
3257 self._next_id = self.email_verification_page_id
3258+ self.email_verification.email = unicode(email)
3259+ self.email_verification.password = self.currentPage().password
3260 self.next()
3261 self._next_id = -1
3262
3263 def _move_to_setup_account_page(self):
3264 """Move to the setup account page wizard."""
3265+ logger.debug('Moving to SetupAccountPage from: %s',
3266+ self.currentPage())
3267 self._next_id = self.setup_account_page_id
3268 self.next()
3269 self._next_id = -1
3270
3271 def _move_to_login_page(self):
3272 """Move to the login page wizard."""
3273+ logger.debug('Moving to CurrentUserSignInPage from: %s',
3274+ self.currentPage())
3275 self._next_id = self.current_user_page_id
3276 self.next()
3277 self._next_id = -1
3278
3279 def _move_to_success_page(self):
3280 """Move to the success page wizard."""
3281+ logger.debug('Moving to SuccessPage from: %s',
3282+ self.currentPage())
3283 self._next_id = self.success_page_id
3284 self.next()
3285 self.setButtonLayout([
3286@@ -186,6 +200,8 @@
3287
3288 def _move_to_forgotten_page(self):
3289 """Move to the forgotten page wizard."""
3290+ logger.debug('Moving to ForgottenPasswordPage from: %s',
3291+ self.currentPage())
3292 self._next_id = self.forgotten_password_page_id
3293 self.next()
3294 self._next_id = -1
3295@@ -258,6 +274,7 @@
3296 logger.debug('UbuntuSSOClientGUI: app_name %r, kwargs %r.',
3297 app_name, kwargs)
3298 self.app_name = app_name
3299+ self.setWindowTitle(WINDOW_TITLE)
3300 # create the controller and the ui, then set the cb and call the show
3301 # method so that we can work
3302 self.wizard = UbuntuSSOWizard(app_name=app_name, **kwargs)
3303
3304=== modified file 'ubuntu_sso/utils/__init__.py'
3305--- ubuntu_sso/utils/__init__.py 2012-02-17 20:48:27 +0000
3306+++ ubuntu_sso/utils/__init__.py 2012-03-20 16:10:09 +0000
3307@@ -32,8 +32,14 @@
3308
3309
3310 logger = setup_logging("ubuntu_sso.utils")
3311+BIN_SUFFIX = 'bin'
3312 DATA_SUFFIX = 'data'
3313-BIN_SUFFIX = 'bin'
3314+
3315+if sys.platform == "win32":
3316+ from ubuntu_sso.utils import windows as source
3317+else:
3318+ from ubuntu_sso.utils import linux as source
3319+PLATFORM_QSS = source.PLATFORM_QSS
3320
3321
3322 def _get_dir(dir_name, dir_constant):
3323@@ -88,8 +94,15 @@
3324 found, return the value of the BIN_DIR.
3325
3326 """
3327- result = _get_dir(dir_name=BIN_SUFFIX, dir_constant='BIN_DIR')
3328+ # If sys is frozen, this is an .exe, and all binaries are in
3329+ # the same place
3330+ if getattr(sys, "frozen", None) is not None:
3331+ exec_path = os.path.abspath(sys.executable)
3332+ result = os.path.dirname(exec_path)
3333+ else:
3334+ result = _get_dir(dir_name=BIN_SUFFIX, dir_constant='BIN_DIR')
3335 assert result is not None, '%r dir can not be None.' % BIN_SUFFIX
3336+ logger.info('get_bin_dir: returning dir located at %r.', result)
3337 return result
3338
3339
3340
3341=== added file 'ubuntu_sso/utils/linux.py'
3342--- ubuntu_sso/utils/linux.py 1970-01-01 00:00:00 +0000
3343+++ ubuntu_sso/utils/linux.py 2012-03-20 16:10:09 +0000
3344@@ -0,0 +1,19 @@
3345+# -*- coding: utf-8 -*-
3346+#
3347+# Copyright 2010-2012 Canonical Ltd.
3348+#
3349+# This program is free software: you can redistribute it and/or modify it
3350+# under the terms of the GNU General Public License version 3, as published
3351+# by the Free Software Foundation.
3352+#
3353+# This program is distributed in the hope that it will be useful, but
3354+# WITHOUT ANY WARRANTY; without even the implied warranties of
3355+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3356+# PURPOSE. See the GNU General Public License for more details.
3357+#
3358+# You should have received a copy of the GNU General Public License along
3359+# with this program. If not, see <http://www.gnu.org/licenses/>.
3360+
3361+"""Platform specific constants and functions (for Linux)."""
3362+
3363+PLATFORM_QSS = ":/linux.qss"
3364
3365=== modified file 'ubuntu_sso/utils/runner/glib.py'
3366--- ubuntu_sso/utils/runner/glib.py 2012-02-17 18:43:17 +0000
3367+++ ubuntu_sso/utils/runner/glib.py 2012-03-20 16:10:09 +0000
3368@@ -27,7 +27,7 @@
3369 logger = setup_logging("ubuntu_sso.utils.runner.glib")
3370
3371
3372-NO_SUCH_FILE_OR_DIR = 'No such file or directory'
3373+NO_SUCH_FILE_OR_DIR = '[Errno 2]'
3374
3375
3376 def spawn_program(args, reply_handler, error_handler):
3377
3378=== modified file 'ubuntu_sso/utils/runner/tx.py'
3379--- ubuntu_sso/utils/runner/tx.py 2012-02-17 19:13:15 +0000
3380+++ ubuntu_sso/utils/runner/tx.py 2012-03-20 16:10:09 +0000
3381@@ -26,7 +26,7 @@
3382
3383 logger = setup_logging("ubuntu_sso.utils.runner.tx")
3384
3385-NO_SUCH_FILE_OR_DIR = 'OSError: [Errno 2] No such file or directory'
3386+NO_SUCH_FILE_OR_DIR = 'OSError: [Errno 2]'
3387
3388
3389 EXE_EXT = ''
3390
3391=== modified file 'ubuntu_sso/utils/tests/test_common.py'
3392--- ubuntu_sso/utils/tests/test_common.py 2012-02-15 20:21:52 +0000
3393+++ ubuntu_sso/utils/tests/test_common.py 2012-03-20 16:10:09 +0000
3394@@ -17,6 +17,7 @@
3395 """Tests for the oauth_headers helper function."""
3396
3397 import logging
3398+import os
3399 import sys
3400 import time
3401
3402@@ -146,13 +147,21 @@
3403 self.assertEqual(expected, result)
3404
3405
3406-class GetBinDirTestCase(TestCase):
3407+class GetBinDirTestCase(GetProjectDirTestCase):
3408 """Test case for get_bin_dir when constants module is not defined."""
3409
3410 DIR_NAME = utils.BIN_SUFFIX
3411 DIR_CONSTANT = 'BIN_DIR'
3412 DIR_GETTER = 'get_bin_dir'
3413
3414+ def test_frozen_binary(self):
3415+ """Test that frozen binaries return a valid path."""
3416+ sys.frozen = True
3417+ self.addCleanup(delattr, sys, "frozen")
3418+ expected = os.path.dirname(os.path.abspath(sys.executable))
3419+ result = self.get_dir()
3420+ self.assertEqual(expected, result)
3421+
3422
3423 class GetBinDirWithConstantsTestCase(GetProjectDirWithConstantsTestCase):
3424 """Test case for get_bin_dir when constants module is not defined."""
3425
3426=== modified file 'ubuntu_sso/utils/ui.py'
3427--- ubuntu_sso/utils/ui.py 2012-03-06 14:13:10 +0000
3428+++ ubuntu_sso/utils/ui.py 2012-03-20 16:10:09 +0000
3429@@ -43,7 +43,7 @@
3430 CAPTCHA_RELOAD_TEXT = _('refresh')
3431 CAPTCHA_RELOAD_TOOLTIP = _('Reload')
3432 CAPTCHA_REQUIRED_ERROR = _('The captcha is a required field')
3433-CLOSE_AND_SETUP_LATER = _('Close window and setup later')
3434+CLOSE_AND_SETUP_LATER = _('Close window and set up later')
3435 CONGRATULATIONS = _("Congratulations, {app_name} is installed!")
3436 CONNECT_HELP_LABEL = _('To connect this computer to %(app_name)s enter your '
3437 'details below.')
3438@@ -133,6 +133,12 @@
3439 SSL_CERT_DETAILS = _('Certificate details')
3440 SSL_CONNECT_BUTTON = _('Connect')
3441 SSL_DETAILS_HELP = _('the details ssl certificate we are going to show.')
3442+SSL_DETAILS_TEMPLATE = ('Organization:\t%(organization)s\n'
3443+ 'Common Name:\t%(common_name)s\n'
3444+ 'Locality Name:\t%(locality_name)s\n'
3445+ 'Unit:\t%(unit)s\n'
3446+ 'Country:\t%(country_name)s\n'
3447+ 'State or Province:\t%(state_name)s')
3448 SSL_DESCRIPTION = _('Open the SSL certificate UI.')
3449 SSL_DIALOG_TITLE = _('SSL Certificate Not Valid')
3450 SSL_DOMAIN_HELP = _('the domain whose ssl certificate we are going to show.')
3451
3452=== modified file 'ubuntu_sso/utils/webclient/__init__.py'
3453--- ubuntu_sso/utils/webclient/__init__.py 2012-02-09 18:11:02 +0000
3454+++ ubuntu_sso/utils/webclient/__init__.py 2012-03-20 16:10:09 +0000
3455@@ -19,6 +19,7 @@
3456
3457 # pylint: disable=W0611
3458 from ubuntu_sso.utils.webclient.common import (
3459+ ProxyUnauthorizedError,
3460 UnauthorizedError,
3461 WebClientError,
3462 )
3463@@ -26,8 +27,23 @@
3464
3465 def is_qt4reactor_installed():
3466 """Check if the qt4reactor is installed."""
3467- reactor = sys.modules.get("twisted.internet.reactor")
3468- return reactor and getattr(reactor, "qApp", None)
3469+ result = False
3470+
3471+ if not 'PyQt4' in sys.modules:
3472+ return result
3473+
3474+ try:
3475+ from PyQt4.QtCore import QCoreApplication
3476+ from PyQt4.QtGui import QApplication
3477+
3478+ # we could be running a process with or without ui, and those are diff
3479+ # apps.
3480+ result = (QCoreApplication.instance() is not None
3481+ or QApplication.instance() is not None)
3482+ except ImportError:
3483+ pass
3484+
3485+ return result
3486
3487
3488 def webclient_module():
3489
3490=== modified file 'ubuntu_sso/utils/webclient/common.py'
3491--- ubuntu_sso/utils/webclient/common.py 2012-02-09 18:11:02 +0000
3492+++ ubuntu_sso/utils/webclient/common.py 2012-03-20 16:10:09 +0000
3493@@ -17,14 +17,23 @@
3494
3495 import cgi
3496 import collections
3497+import os
3498
3499 from httplib2 import iri2uri
3500 from oauth import oauth
3501 from twisted.internet import defer
3502 from urlparse import urlparse
3503
3504+from ubuntu_sso import USER_SUCCESS, UI_PROXY_CREDS_DIALOG
3505+from ubuntu_sso.logger import setup_logging
3506+from ubuntu_sso.utils.runner import spawn_program
3507+from ubuntu_sso.utils.ui import SSL_DETAILS_TEMPLATE
3508 from ubuntu_sso.utils.webclient.timestamp import TimestampChecker
3509
3510+SSL_DIALOG = 'ubuntu-sso-ssl-certificate-qt'
3511+
3512+logger = setup_logging("ubuntu_sso.utils.webclient.common")
3513+
3514
3515 class WebClientError(Exception):
3516 """An http error happened while calling the webservice."""
3517@@ -34,8 +43,12 @@
3518 """The request ended with bad_request, unauthorized or forbidden."""
3519
3520
3521+class ProxyUnauthorizedError(WebClientError):
3522+ """Failure raised when there is an issue with the proxy auth."""
3523+
3524+
3525 class Response(object):
3526- """A reponse object."""
3527+ """A response object."""
3528
3529 def __init__(self, content, headers=None):
3530 """Initialize this instance."""
3531@@ -77,10 +90,14 @@
3532
3533 timestamp_checker = None
3534
3535- def __init__(self, username=None, password=None, oauth_sign_plain=False):
3536+ def __init__(self, appname='', username=None, password=None,
3537+ oauth_sign_plain=False):
3538 """Initialize this instance."""
3539+ self.appname = appname
3540 self.username = username
3541 self.password = password
3542+ self.proxy_username = None
3543+ self.proxy_password = None
3544 self.oauth_sign_plain = oauth_sign_plain
3545
3546 def request(self, iri, method="GET", extra_headers=None,
3547@@ -154,3 +171,94 @@
3548
3549 def shutdown(self):
3550 """Shut down all pending requests (if possible)."""
3551+
3552+ @defer.inlineCallbacks
3553+ def _load_proxy_creds_from_keyring(self, domain):
3554+ """Load the proxy creds from the keyring."""
3555+ from ubuntu_sso.keyring import Keyring
3556+ keyring = Keyring()
3557+ try:
3558+ creds = yield keyring.get_credentials(str(domain))
3559+ logger.debug('Got credentials from keyring.')
3560+ except Exception, e:
3561+ logger.error('Error when retrieving the creds.')
3562+ raise WebClientError('Error when retrieving the creds.', e)
3563+ if creds is not None:
3564+ # if we are loading the same creds it means that we got the wrong
3565+ # ones
3566+ if (self.proxy_username == creds['username'] and
3567+ self.proxy_password == creds['password']):
3568+ defer.returnValue(False)
3569+ else:
3570+ self.proxy_username = creds['username']
3571+ self.proxy_password = creds['password']
3572+ defer.returnValue(True)
3573+ logger.debug('Proxy creds not in keyring.')
3574+ defer.returnValue(False)
3575+
3576+ def _launch_proxy_creds_dialog(self, domain, retry):
3577+ """Launch the dialog used to get the creds."""
3578+ from ubuntu_sso.utils import get_bin_dir
3579+
3580+ bin_dir = get_bin_dir()
3581+ args = (os.path.join(bin_dir, UI_PROXY_CREDS_DIALOG), '--domain',
3582+ domain)
3583+ if retry:
3584+ args += ('--retry',)
3585+ return spawn_program(args)
3586+
3587+ @defer.inlineCallbacks
3588+ def request_proxy_auth_credentials(self, domain, retry):
3589+ """Request the auth creds to the user."""
3590+ if not retry:
3591+ if (self.proxy_username is not None
3592+ and self.proxy_password is not None):
3593+ logger.debug('Not retry and credentials are present.')
3594+ defer.returnValue(True)
3595+ else:
3596+ creds_loaded = yield self._load_proxy_creds_from_keyring(
3597+ domain)
3598+ if creds_loaded:
3599+ defer.returnValue(True)
3600+
3601+ try:
3602+ return_code = yield self._launch_proxy_creds_dialog(domain, retry)
3603+ except Exception, e:
3604+ logger.error('Error when running external ui process.')
3605+ raise WebClientError('Error when running external ui process.', e)
3606+
3607+ if return_code == USER_SUCCESS:
3608+ creds_loaded = yield self._load_proxy_creds_from_keyring(domain)
3609+ defer.returnValue(creds_loaded)
3610+ else:
3611+ logger.debug('Could not retrieve the credentials. Return code: %r',
3612+ return_code)
3613+ defer.returnValue(False)
3614+
3615+ def format_ssl_details(self, details):
3616+ """Return a formatted string with the details."""
3617+ return SSL_DETAILS_TEMPLATE % details
3618+
3619+ def _launch_ssl_dialog(self, domain, details):
3620+ """Launch a dialog used to approve the ssl cert."""
3621+ from ubuntu_sso.utils import get_bin_dir
3622+
3623+ bin_dir = get_bin_dir()
3624+ args = (os.path.join(bin_dir, SSL_DIALOG), '--domain', domain,
3625+ '--details', details,
3626+ '--appname', self.appname)
3627+ return spawn_program(args)
3628+
3629+ def _was_ssl_accepted(self, cert_details):
3630+ """Return if the cert was already accepted."""
3631+ # TODO: Ensure that we look at pinned certs in a following branch
3632+ return False
3633+
3634+ @defer.inlineCallbacks
3635+ def request_ssl_cert_approval(self, domain, details):
3636+ """Request the user for ssl approval."""
3637+ if self._was_ssl_accepted(details):
3638+ defer.returnValue(True)
3639+
3640+ return_code = yield self._launch_ssl_dialog(domain, details)
3641+ defer.returnValue(return_code == USER_SUCCESS)
3642
3643=== modified file 'ubuntu_sso/utils/webclient/gsettings.py'
3644--- ubuntu_sso/utils/webclient/gsettings.py 2012-02-10 14:46:16 +0000
3645+++ ubuntu_sso/utils/webclient/gsettings.py 2012-03-20 16:10:09 +0000
3646@@ -31,6 +31,24 @@
3647 return hostname, username, password
3648
3649
3650+def parse_manual_proxy_settings(scheme, gsettings):
3651+ """Parse the settings for a given scheme."""
3652+ host, username, pwd = parse_proxy_host(gsettings[scheme + ".host"])
3653+ settings = {
3654+ "host": host,
3655+ "port": gsettings[scheme + ".port"],
3656+ }
3657+ if scheme == "http" and gsettings["http.use-authentication"]:
3658+ username = gsettings["http.authentication-user"]
3659+ pwd = gsettings["http.authentication-password"]
3660+ if username is not None and pwd is not None:
3661+ settings.update({
3662+ "username": username,
3663+ "password": pwd,
3664+ })
3665+ return settings
3666+
3667+
3668 def get_proxy_settings():
3669 """Parse the proxy settings as returned by the gsettings executable."""
3670 output = subprocess.check_output(GSETTINGS_CMDLINE.split())
3671@@ -56,20 +74,9 @@
3672 if mode == "none":
3673 settings = {}
3674 elif mode == "manual":
3675- # attempt to parse the host
3676- host, username, pwd = parse_proxy_host(gsettings["http.host"])
3677- settings = {
3678- "host": host,
3679- "port": gsettings["http.port"],
3680- }
3681- if gsettings["http.use-authentication"]:
3682- username = gsettings["http.authentication-user"]
3683- pwd = gsettings["http.authentication-password"]
3684- if username is not None and pwd is not None:
3685- settings.update({
3686- "username": username,
3687- "password": pwd,
3688- })
3689+ settings = {}
3690+ for scheme in ["http", "https"]:
3691+ settings[scheme] = parse_manual_proxy_settings(scheme, gsettings)
3692 else:
3693 # If mode is automatic the PAC javascript should be interpreted
3694 # on each request. That is out of scope so it's ignored for now
3695
3696=== modified file 'ubuntu_sso/utils/webclient/libsoup.py'
3697--- ubuntu_sso/utils/webclient/libsoup.py 2012-02-07 19:36:50 +0000
3698+++ ubuntu_sso/utils/webclient/libsoup.py 2012-03-20 16:10:09 +0000
3699@@ -19,10 +19,12 @@
3700
3701 from twisted.internet import defer
3702
3703+from ubuntu_sso.logger import setup_logging
3704 from ubuntu_sso.utils.webclient.common import (
3705 BaseWebClient,
3706 HeaderDict,
3707 Response,
3708+ ProxyUnauthorizedError,
3709 UnauthorizedError,
3710 WebClientError,
3711 )
3712@@ -30,6 +32,8 @@
3713 URI_ANONYMOUS_TEMPLATE = "http://{host}:{port}/"
3714 URI_USERNAME_TEMPLATE = "http://{username}:{password}@{host}:{port}/"
3715
3716+logger = setup_logging("ubuntu_sso.utils.webclient.libsoup")
3717+
3718
3719 class WebClient(BaseWebClient):
3720 """A webclient with a libsoup backend."""
3721@@ -41,11 +45,12 @@
3722 from gi.repository import Soup, SoupGNOME
3723 self.soup = Soup
3724 self.session = Soup.SessionAsync()
3725- self.session.add_feature_by_type(SoupGNOME.ProxyResolverGNOME)
3726+ self.session.add_feature(SoupGNOME.ProxyResolverGNOME())
3727 self.session.connect("authenticate", self._on_authenticate)
3728
3729 def _on_message(self, session, message, d):
3730 """Handle the result of an http message."""
3731+ logger.debug('_on_message status code is %s', message.status_code)
3732 if message.status_code == httplib.OK:
3733 headers = HeaderDict()
3734 response_headers = message.get_property("response-headers")
3735@@ -57,14 +62,51 @@
3736 elif message.status_code == httplib.UNAUTHORIZED:
3737 e = UnauthorizedError(message.reason_phrase)
3738 d.errback(e)
3739+ elif message.status_code == httplib.PROXY_AUTHENTICATION_REQUIRED:
3740+ e = ProxyUnauthorizedError(message.reason_phrase)
3741+ d.errback(e)
3742 else:
3743 e = WebClientError(message.reason_phrase)
3744 d.errback(e)
3745
3746- def _on_authenticate(self, sesion, message, auth, retrying, data=None):
3747+ @defer.inlineCallbacks
3748+ def _on_authenticate(self, session, message, auth, retrying, data=None):
3749 """Handle the "authenticate" signal."""
3750- if not retrying and self.username and self.password:
3751- auth.authenticate(self.username, self.password)
3752+ self.session.pause_message(message)
3753+ try:
3754+ logger.debug('_on_authenticate: message status code is %s',
3755+ message.status_code)
3756+ if not retrying and self.username and self.password:
3757+ auth.authenticate(self.username, self.password)
3758+ if auth.is_for_proxy():
3759+ logger.debug('_on_authenticate auth is for proxy.')
3760+ got_creds = yield self.request_proxy_auth_credentials(
3761+ self.session.props.proxy_uri.host,
3762+ retrying)
3763+ if got_creds:
3764+ logger.debug('Got proxy credentials from user.')
3765+ auth.authenticate(self.proxy_username, self.proxy_password)
3766+ finally:
3767+ self.session.unpause_message(message)
3768+
3769+ @defer.inlineCallbacks
3770+ def _on_proxy_authenticate(self, failure, iri, method="GET",
3771+ extra_headers=None, oauth_credentials=None, post_content=None):
3772+ """Deal with wrong settings."""
3773+ failure.trap(ProxyUnauthorizedError)
3774+ logger.debug('Proxy settings are wrong.')
3775+ got_creds = yield self.request_proxy_auth_credentials(
3776+ self.session.props.proxy_uri.host,
3777+ True)
3778+ if got_creds:
3779+ settings = dict(host=self.session.props.proxy_uri.host,
3780+ port=self.session.props.proxy_uri.port,
3781+ username=self.proxy_username,
3782+ password=self.proxy_password)
3783+ self.force_use_proxy(settings)
3784+ response = yield self.request(iri, method, extra_headers,
3785+ oauth_credentials, post_content)
3786+ defer.returnValue(response)
3787
3788 @defer.inlineCallbacks
3789 def request(self, iri, method="GET", extra_headers=None,
3790@@ -92,6 +134,8 @@
3791 message.request_body.append(post_content)
3792
3793 self.session.queue_message(message, self._on_message, d)
3794+ d.addErrback(self._on_proxy_authenticate, iri, method, extra_headers,
3795+ oauth_credentials, post_content)
3796 response = yield d
3797 defer.returnValue(response)
3798
3799
3800=== modified file 'ubuntu_sso/utils/webclient/qtnetwork.py'
3801--- ubuntu_sso/utils/webclient/qtnetwork.py 2012-02-07 19:36:50 +0000
3802+++ ubuntu_sso/utils/webclient/qtnetwork.py 2012-03-20 16:10:09 +0000
3803@@ -30,29 +30,61 @@
3804 QNetworkProxyFactory,
3805 QNetworkReply,
3806 QNetworkRequest,
3807+ QSslCertificate,
3808 )
3809 from twisted.internet import defer
3810
3811+from ubuntu_sso.logger import setup_logging
3812 from ubuntu_sso.utils.webclient.common import (
3813 BaseWebClient,
3814 HeaderDict,
3815+ ProxyUnauthorizedError,
3816 Response,
3817 UnauthorizedError,
3818 WebClientError,
3819 )
3820 from ubuntu_sso.utils.webclient import gsettings
3821
3822+logger = setup_logging("ubuntu_sso.utils.webclient.qtnetwork")
3823+
3824+
3825+def build_proxy(settings_groups):
3826+ """Create a QNetworkProxy from these settings."""
3827+ proxy_groups = [
3828+ ("socks", QNetworkProxy.Socks5Proxy),
3829+ ("https", QNetworkProxy.HttpProxy),
3830+ ("http", QNetworkProxy.HttpProxy),
3831+ ]
3832+ for group, proxy_type in proxy_groups:
3833+ if group not in settings_groups:
3834+ continue
3835+ settings = settings_groups[group]
3836+ if "host" in settings and "port" in settings:
3837+ return QNetworkProxy(proxy_type,
3838+ hostName=settings.get("host", ""),
3839+ port=settings.get("port", 0),
3840+ user=settings.get("username", ""),
3841+ password=settings.get("password", ""))
3842+ logger.error("No proxy correctly configured.")
3843+ return QNetworkProxy(QNetworkProxy.DefaultProxy)
3844+
3845
3846 class WebClient(BaseWebClient):
3847 """A webclient with a qtnetwork backend."""
3848
3849+ proxy_instance = None
3850+
3851 def __init__(self, *args, **kwargs):
3852 """Initialize this instance."""
3853 super(WebClient, self).__init__(*args, **kwargs)
3854 self.nam = QNetworkAccessManager(QCoreApplication.instance())
3855 self.nam.finished.connect(self._handle_finished)
3856 self.nam.authenticationRequired.connect(self._handle_authentication)
3857+ self.nam.proxyAuthenticationRequired.connect(self.handle_proxy_auth)
3858+ # Disabled until we make this a per-instance option
3859+ #self.nam.sslErrors.connect(self._handle_ssl_errors)
3860 self.replies = {}
3861+ self.proxy_retry = False
3862 self.setup_proxy()
3863
3864 def setup_proxy(self):
3865@@ -60,11 +92,43 @@
3866 # QtNetwork knows how to use the system settings on both Win and Mac
3867 if sys.platform.startswith("linux"):
3868 settings = gsettings.get_proxy_settings()
3869- if settings:
3870- self.force_use_proxy(settings)
3871+ enabled = len(settings) > 0
3872+ if enabled and WebClient.proxy_instance is None:
3873+ proxy = build_proxy(settings)
3874+ QNetworkProxy.setApplicationProxy(proxy)
3875+ WebClient.proxy_instance = proxy
3876+ elif enabled and WebClient.proxy_instance:
3877+ logger.info("Proxy already in use.")
3878+ else:
3879+ logger.info("Proxy is disabled.")
3880 else:
3881 QNetworkProxyFactory.setUseSystemConfiguration(True)
3882
3883+ def handle_proxy_auth(self, proxy, authenticator):
3884+ """Proxy authentication is required."""
3885+ logger.info("auth_required %r, %r", self.proxy_username,
3886+ proxy.hostName())
3887+ if (self.proxy_username is not None and
3888+ self.proxy_username != str(authenticator.user())):
3889+ authenticator.setUser(self.proxy_username)
3890+ WebClient.proxy_instance.setUser(self.proxy_username)
3891+ if (self.proxy_password is not None and
3892+ self.proxy_password != str(authenticator.password())):
3893+ authenticator.setPassword(self.proxy_password)
3894+ WebClient.proxy_instance.setPassword(self.proxy_password)
3895+
3896+ def _perform_request(self, request, method, post_buffer):
3897+ """Return a deferred that will be fired with a Response object."""
3898+ d = defer.Deferred()
3899+ if method == "GET":
3900+ reply = self.nam.get(request)
3901+ elif method == "HEAD":
3902+ reply = self.nam.head(request)
3903+ else:
3904+ reply = self.nam.sendCustomRequest(request, method, post_buffer)
3905+ self.replies[reply] = d
3906+ return d
3907+
3908 @defer.inlineCallbacks
3909 def request(self, iri, method="GET", extra_headers=None,
3910 oauth_credentials=None, post_content=None):
3911@@ -86,23 +150,30 @@
3912 for key, value in headers.iteritems():
3913 request.setRawHeader(key, value)
3914
3915- d = defer.Deferred()
3916- if method == "GET":
3917- reply = self.nam.get(request)
3918- elif method == "HEAD":
3919- reply = self.nam.head(request)
3920- else:
3921- post_buffer = QBuffer()
3922- post_buffer.setData(post_content)
3923- reply = self.nam.sendCustomRequest(request, method, post_buffer)
3924- self.replies[reply] = d
3925- result = yield d
3926+ post_buffer = QBuffer()
3927+ post_buffer.setData(post_content)
3928+ try:
3929+ result = yield self._perform_request(request, method, post_buffer)
3930+ except ProxyUnauthorizedError, e:
3931+ app_proxy = QNetworkProxy.applicationProxy()
3932+ proxy_host = app_proxy.hostName() if app_proxy else "proxy server"
3933+ got_creds = yield self.request_proxy_auth_credentials(
3934+ proxy_host, self.proxy_retry)
3935+ if got_creds:
3936+ self.proxy_retry = True
3937+ result = yield self.request(iri, method, extra_headers,
3938+ oauth_credentials, post_content)
3939+ else:
3940+ excp = WebClientError('Proxy creds needed.', e)
3941+ defer.returnValue(excp)
3942 defer.returnValue(result)
3943
3944 def _handle_authentication(self, reply, authenticator):
3945 """The reply needs authentication."""
3946- authenticator.setUser(self.username)
3947- authenticator.setPassword(self.password)
3948+ if authenticator.user() != self.username:
3949+ authenticator.setUser(self.username)
3950+ if authenticator.password() != self.password:
3951+ authenticator.setPassword(self.password)
3952
3953 def _handle_finished(self, reply):
3954 """The reply has finished processing."""
3955@@ -118,20 +189,51 @@
3956 d.callback(response)
3957 else:
3958 error_string = reply.errorString()
3959+ logger.debug('_handle_finished error (%s,%s).', error,
3960+ error_string)
3961 if error == QNetworkReply.AuthenticationRequiredError:
3962 exception = UnauthorizedError(error_string, content)
3963+ elif error == QNetworkReply.ProxyAuthenticationRequiredError:
3964+ # we are going thru a proxy and we did not auth
3965+ exception = ProxyUnauthorizedError(error_string, content)
3966 else:
3967 exception = WebClientError(error_string, content)
3968 d.errback(exception)
3969
3970- def force_use_proxy(self, settings):
3971+ def _get_certificate_details(self, cert):
3972+ """Return an string with the details of the certificate."""
3973+ detail_titles = {QSslCertificate.Organization: 'organization',
3974+ QSslCertificate.CommonName: 'common_name',
3975+ QSslCertificate.LocalityName: 'locality_name',
3976+ QSslCertificate.OrganizationalUnitName: 'unit',
3977+ QSslCertificate.CountryName: 'country_name',
3978+ QSslCertificate.StateOrProvinceName: 'state_name'}
3979+ details = {}
3980+ for info, title in detail_titles.iteritems():
3981+ details[title] = str(cert.issuerInfo(info))
3982+ return self.format_ssl_details(details)
3983+
3984+ def _get_certificate_host(self, cert):
3985+ """Return the host of the cert."""
3986+ return str(cert.issuerInfo(QSslCertificate.CommonName))
3987+
3988+ @defer.inlineCallbacks
3989+ def _handle_ssl_errors(self, reply, errors):
3990+ """Handle the case in which we got an ssl error."""
3991+ # ask the user if the cer should be trusted
3992+ cert = errors[0].certificate()
3993+ trust_cert = yield self.request_ssl_cert_approval(
3994+ self._get_certificate_host(cert),
3995+ self._get_certificate_details(cert))
3996+ if trust_cert:
3997+ reply.ignoreSslErrors()
3998+
3999+ def force_use_proxy(self, https_settings):
4000 """Setup this webclient to use the given proxy settings."""
4001- proxy = QNetworkProxy(QNetworkProxy.HttpProxy,
4002- hostName=settings.get("host", ""),
4003- port=settings.get("port", 0),
4004- user=settings.get("username", ""),
4005- password=settings.get("password", ""))
4006- self.nam.setProxy(proxy)
4007+ settings = {"https": https_settings}
4008+ proxy = build_proxy(settings)
4009+ QNetworkProxy.setApplicationProxy(proxy)
4010+ WebClient.proxy_instance = proxy
4011
4012 def shutdown(self):
4013 """Shut down all pending requests (if possible)."""
4014
4015=== modified file 'ubuntu_sso/utils/webclient/tests/__init__.py'
4016--- ubuntu_sso/utils/webclient/tests/__init__.py 2012-02-07 19:36:50 +0000
4017+++ ubuntu_sso/utils/webclient/tests/__init__.py 2012-03-20 16:10:09 +0000
4018@@ -17,9 +17,48 @@
4019 """Tests for the proxy-aware webclient."""
4020
4021 from twisted.application import internet, service
4022+from twisted.internet import ssl
4023 from twisted.web import http, server
4024
4025
4026+# Some settings are not used as described in:
4027+# https://bugzilla.gnome.org/show_bug.cgi?id=648237
4028+
4029+TEMPLATE_GSETTINGS_OUTPUT = """\
4030+org.gnome.system.proxy autoconfig-url '{autoconfig_url}'
4031+org.gnome.system.proxy ignore-hosts {ignore_hosts:s}
4032+org.gnome.system.proxy mode '{mode}'
4033+org.gnome.system.proxy.ftp host '{ftp_host}'
4034+org.gnome.system.proxy.ftp port {ftp_port}
4035+org.gnome.system.proxy.http authentication-password '{auth_password}'
4036+org.gnome.system.proxy.http authentication-user '{auth_user}'
4037+org.gnome.system.proxy.http host '{http_host}'
4038+org.gnome.system.proxy.http port {http_port}
4039+org.gnome.system.proxy.http use-authentication {http_use_auth}
4040+org.gnome.system.proxy.https host '{https_host}'
4041+org.gnome.system.proxy.https port {https_port}
4042+org.gnome.system.proxy.socks host '{socks_host}'
4043+org.gnome.system.proxy.socks port {socks_port}
4044+"""
4045+
4046+BASE_GSETTINGS_VALUES = {
4047+ "autoconfig_url": "",
4048+ "ignore_hosts": ["localhost", "127.0.0.0/8"],
4049+ "mode": "none",
4050+ "ftp_host": "",
4051+ "ftp_port": 0,
4052+ "auth_password": "",
4053+ "auth_user": "",
4054+ "http_host": "",
4055+ "http_port": 0,
4056+ "http_use_auth": "false",
4057+ "https_host": "",
4058+ "https_port": 0,
4059+ "socks_host": "",
4060+ "socks_port": 0,
4061+}
4062+
4063+
4064 class SaveHTTPChannel(http.HTTPChannel):
4065 """A save protocol to be used in tests."""
4066
4067@@ -48,15 +87,26 @@
4068 class BaseMockWebServer(object):
4069 """A mock webserver for testing"""
4070
4071- def __init__(self):
4072+ def __init__(self, ssl_settings=None):
4073 """Start up this instance."""
4074 self.root = self.get_root_resource()
4075 self.site = SaveSite(self.root)
4076 application = service.Application('web')
4077 self.service_collection = service.IServiceCollection(application)
4078 #pylint: disable=E1101
4079- self.tcpserver = internet.TCPServer(0, self.site)
4080- self.tcpserver.setServiceParent(self.service_collection)
4081+ ssl_context = None
4082+ if (ssl_settings is not None
4083+ and 'key' in ssl_settings
4084+ and 'cert' in ssl_settings):
4085+ ssl_context = ssl.DefaultOpenSSLContextFactory(ssl_settings['key'],
4086+ ssl_settings['cert'])
4087+ self.ssl_server = internet.SSLServer(0, self.site, ssl_context)
4088+ else:
4089+ self.ssl_server = None
4090+ self.server = internet.TCPServer(0, self.site)
4091+ self.server.setServiceParent(self.service_collection)
4092+ if self.ssl_server:
4093+ self.ssl_server.setServiceParent(self.service_collection)
4094 self.service_collection.startService()
4095
4096 def get_root_resource(self):
4097@@ -65,9 +115,27 @@
4098
4099 def get_iri(self):
4100 """Build the iri for this mock server."""
4101- #pylint: disable=W0212
4102- port_num = self.tcpserver._port.getHost().port
4103- return u"http://127.0.0.1:%d/" % port_num
4104+ url = u"http://127.0.0.1:%d/"
4105+ return url % self.get_port()
4106+
4107+ def get_ssl_iri(self):
4108+ """Build the ssl iri for this mock server."""
4109+ if self.ssl_server:
4110+ url = u"https://127.0.0.1:%d/"
4111+ return url % self.get_ssl_port()
4112+
4113+ def get_port(self):
4114+ """Return the port where we are listening."""
4115+ # pylint: disable=W0212
4116+ return self.server._port.getHost().port
4117+ # pylint: enable=W0212
4118+
4119+ def get_ssl_port(self):
4120+ """Return the ssl port where we are listening."""
4121+ # pylint: disable=W0212
4122+ if self.ssl_server:
4123+ return self.ssl_server._port.getHost().port
4124+ # pylint: enable=W0212
4125
4126 def stop(self):
4127 """Shut it down."""
4128
4129=== modified file 'ubuntu_sso/utils/webclient/tests/test_gsettings.py'
4130--- ubuntu_sso/utils/webclient/tests/test_gsettings.py 2012-02-10 19:20:33 +0000
4131+++ ubuntu_sso/utils/webclient/tests/test_gsettings.py 2012-03-20 16:10:09 +0000
4132@@ -18,43 +18,10 @@
4133 from twisted.trial.unittest import TestCase
4134
4135 from ubuntu_sso.utils.webclient import gsettings
4136-
4137-# Some settings are not used as described in:
4138-# https://bugzilla.gnome.org/show_bug.cgi?id=648237
4139-
4140-TEMPLATE_GSETTINGS_OUTPUT = """\
4141-org.gnome.system.proxy autoconfig-url '{autoconfig_url}'
4142-org.gnome.system.proxy ignore-hosts {ignore_hosts:s}
4143-org.gnome.system.proxy mode '{mode}'
4144-org.gnome.system.proxy.ftp host '{ftp_host}'
4145-org.gnome.system.proxy.ftp port {ftp_port}
4146-org.gnome.system.proxy.http authentication-password '{auth_password}'
4147-org.gnome.system.proxy.http authentication-user '{auth_user}'
4148-org.gnome.system.proxy.http host '{http_host}'
4149-org.gnome.system.proxy.http port {http_port}
4150-org.gnome.system.proxy.http use-authentication {http_use_auth}
4151-org.gnome.system.proxy.https host '{https_host}'
4152-org.gnome.system.proxy.https port {https_port}
4153-org.gnome.system.proxy.socks host '{socks_host}'
4154-org.gnome.system.proxy.socks port {socks_port}
4155-"""
4156-
4157-BASE_GSETTINGS_VALUES = {
4158- "autoconfig_url": "",
4159- "ignore_hosts": ["localhost", "127.0.0.0/8"],
4160- "mode": "none",
4161- "ftp_host": "",
4162- "ftp_port": 0,
4163- "auth_password": "",
4164- "auth_user": "",
4165- "http_host": "",
4166- "http_port": 0,
4167- "http_use_auth": "false",
4168- "https_host": "",
4169- "https_port": 0,
4170- "socks_host": "",
4171- "socks_port": 0,
4172-}
4173+from ubuntu_sso.utils.webclient.tests import (
4174+ BASE_GSETTINGS_VALUES,
4175+ TEMPLATE_GSETTINGS_OUTPUT,
4176+)
4177
4178
4179 class ProxySettingsTestCase(TestCase):
4180@@ -83,25 +50,33 @@
4181 ps = gsettings.get_proxy_settings()
4182 self.assertEqual(ps, expected)
4183
4184+ def _assert_parser_anonymous(self, scheme):
4185+ """Assert the parsing of anonymous settings."""
4186+ template_values = dict(BASE_GSETTINGS_VALUES)
4187+ expected_host = "expected_host"
4188+ expected_port = 54321
4189+ expected = {
4190+ "host": expected_host,
4191+ "port": expected_port,
4192+ }
4193+ template_values.update({
4194+ "mode": "manual",
4195+ scheme + "_host": expected_host,
4196+ scheme + "_port": expected_port,
4197+ })
4198+ fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
4199+ self.patch(gsettings.subprocess, "check_output",
4200+ lambda _: fake_output)
4201+ ps = gsettings.get_proxy_settings()
4202+ self.assertEqual(ps[scheme], expected)
4203+
4204 def test_gsettings_parser_http_anonymous(self):
4205 """Test a parser of gsettings."""
4206- template_values = dict(BASE_GSETTINGS_VALUES)
4207- expected_host = "expected_host"
4208- expected_port = 54321
4209- expected = {
4210- "host": expected_host,
4211- "port": expected_port,
4212- }
4213- template_values.update({
4214- "mode": "manual",
4215- "http_host": expected_host,
4216- "http_port": expected_port,
4217- })
4218- fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
4219- self.patch(gsettings.subprocess, "check_output",
4220- lambda _: fake_output)
4221- ps = gsettings.get_proxy_settings()
4222- self.assertEqual(ps, expected)
4223+ self._assert_parser_anonymous('http')
4224+
4225+ def test_gsettings_parser_https_anonymus(self):
4226+ """Test a parser of gsettings."""
4227+ self._assert_parser_anonymous('https')
4228
4229 def test_gsettings_parser_http_authenticated(self):
4230 """Test a parser of gsettings."""
4231@@ -128,9 +103,9 @@
4232 self.patch(gsettings.subprocess, "check_output",
4233 lambda _: fake_output)
4234 ps = gsettings.get_proxy_settings()
4235- self.assertEqual(ps, expected)
4236+ self.assertEqual(ps["http"], expected)
4237
4238- def test_gsettings_parser_authenticated_url(self):
4239+ def _assert_parser_authenticated_url(self, scheme):
4240 """Test a parser of gsettings with creds in the url."""
4241 template_values = dict(BASE_GSETTINGS_VALUES)
4242 expected_host = "expected_host"
4243@@ -147,15 +122,23 @@
4244 }
4245 template_values.update({
4246 "mode": "manual",
4247- "http_host": composed_url,
4248- "http_port": expected_port,
4249+ scheme + "_host": composed_url,
4250+ scheme + "_port": expected_port,
4251 "http_use_auth": "false",
4252 })
4253 fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
4254 self.patch(gsettings.subprocess, "check_output",
4255 lambda _: fake_output)
4256 ps = gsettings.get_proxy_settings()
4257- self.assertEqual(ps, expected)
4258+ self.assertEqual(ps[scheme], expected)
4259+
4260+ def test_gsettings_parser_http_authenticated_url(self):
4261+ """Test a parser of gsettings with creds in the url."""
4262+ self._assert_parser_authenticated_url('http')
4263+
4264+ def test_gsettings_parser_https_authenticated_url(self):
4265+ """Test a parser of gsettings with creds in the url."""
4266+ self._assert_parser_authenticated_url('https')
4267
4268 def test_gsettings_auth_over_url(self):
4269 """Test that the settings are more important that the url."""
4270@@ -166,7 +149,7 @@
4271 expected_password = "very secret password"
4272 composed_url = '%s:%s@%s' % ('user', 'random',
4273 expected_host)
4274- expected = {
4275+ http_expected = {
4276 "host": expected_host,
4277 "port": expected_port,
4278 "username": expected_user,
4279@@ -184,7 +167,7 @@
4280 self.patch(gsettings.subprocess, "check_output",
4281 lambda _: fake_output)
4282 ps = gsettings.get_proxy_settings()
4283- self.assertEqual(ps, expected)
4284+ self.assertEqual(ps["http"], http_expected)
4285
4286
4287 class ParseProxyHostTestCase(TestCase):
4288
4289=== modified file 'ubuntu_sso/utils/webclient/tests/test_webclient.py'
4290--- ubuntu_sso/utils/webclient/tests/test_webclient.py 2012-02-13 13:14:18 +0000
4291+++ ubuntu_sso/utils/webclient/tests/test_webclient.py 2012-03-20 16:10:09 +0000
4292@@ -16,9 +16,12 @@
4293 """Integration tests for the proxy-enabled webclient."""
4294
4295 import os
4296+import shutil
4297 import sys
4298 import urllib
4299
4300+from OpenSSL import crypto
4301+from socket import gethostname
4302 from twisted.cred import checkers, portal
4303 from twisted.internet import defer
4304 from twisted.web import guard, http, resource
4305@@ -27,7 +30,15 @@
4306 from ubuntuone.devtools.testcases import TestCase
4307 from ubuntuone.devtools.testcases.squid import SquidTestCase
4308
4309+from ubuntu_sso import (
4310+ keyring,
4311+ EXCEPTION_RAISED,
4312+ USER_SUCCESS,
4313+ USER_CANCELLATION,
4314+)
4315 from ubuntu_sso.utils import webclient
4316+from ubuntu_sso.utils.ui import SSL_DETAILS_TEMPLATE
4317+from ubuntu_sso.utils.webclient import gsettings, txweb
4318 from ubuntu_sso.utils.webclient.common import BaseWebClient, HeaderDict, oauth
4319 from ubuntu_sso.utils.webclient.tests import BaseMockWebServer
4320
4321@@ -188,9 +199,13 @@
4322 return root
4323
4324
4325-class FakeReactor(object):
4326- """A fake reactor object."""
4327- qApp = "Sample qapp"
4328+class FakeQApplication(object):
4329+ """A fake Qt module."""
4330+
4331+ @classmethod
4332+ def instance(cls):
4333+ """Return the instance."""
4334+ return cls
4335
4336
4337 class ModuleSelectionTestCase(TestCase):
4338@@ -201,10 +216,11 @@
4339 self.patch(sys, "modules", {})
4340 self.assertFalse(webclient.is_qt4reactor_installed())
4341
4342- def test_is_qt4reactor_installed_installed(self):
4343+ def test_is_qt4reactor_installed_installed_core(self):
4344 """When the qt4reactor is installed, it returns true."""
4345- fake_sysmodules = {"twisted.internet.reactor": FakeReactor()}
4346- self.patch(sys, "modules", fake_sysmodules)
4347+ from PyQt4 import QtCore
4348+
4349+ self.patch(QtCore, 'QCoreApplication', FakeQApplication)
4350 self.assertTrue(webclient.is_qt4reactor_installed())
4351
4352 def assert_module_name(self, module, expected_name):
4353@@ -230,6 +246,7 @@
4354 """Test for the webclient."""
4355
4356 timeout = 1
4357+ webclient_factory = webclient.webclient_factory
4358
4359 @defer.inlineCallbacks
4360 def setUp(self):
4361@@ -237,7 +254,7 @@
4362 self.ws = MockWebServer()
4363 self.addCleanup(self.ws.stop)
4364 self.base_iri = self.ws.get_iri()
4365- self.wc = webclient.webclient_factory()
4366+ self.wc = self.webclient_factory()
4367 self.addCleanup(self.wc.shutdown)
4368
4369 @defer.inlineCallbacks
4370@@ -306,13 +323,22 @@
4371 @defer.inlineCallbacks
4372 def test_send_basic_auth(self):
4373 """The basic authentication headers are sent."""
4374- other_wc = webclient.webclient_factory(username=SAMPLE_USERNAME,
4375- password=SAMPLE_PASSWORD)
4376+ other_wc = self.webclient_factory(username=SAMPLE_USERNAME,
4377+ password=SAMPLE_PASSWORD)
4378 self.addCleanup(other_wc.shutdown)
4379 result = yield other_wc.request(self.base_iri + GUARDED)
4380 self.assertEqual(SAMPLE_RESOURCE, result.content)
4381
4382 @defer.inlineCallbacks
4383+ def test_send_basic_auth_wrong_credentials(self):
4384+ """Wrong credentials returns a webclient error."""
4385+ other_wc = self.webclient_factory(username=SAMPLE_USERNAME,
4386+ password="wrong password!")
4387+ self.addCleanup(other_wc.shutdown)
4388+ yield self.assertFailure(other_wc.request(self.base_iri + GUARDED),
4389+ webclient.UnauthorizedError)
4390+
4391+ @defer.inlineCallbacks
4392 def test_request_is_oauth_signed(self):
4393 """The request is oauth signed."""
4394 tsc = self.wc.get_timestamp_checker()
4395@@ -348,6 +374,57 @@
4396 "The type of %r must be bytes" % result.content)
4397
4398
4399+class FakeSavingReactor(object):
4400+ """A fake reactor that saves connection attempts."""
4401+
4402+ def __init__(self):
4403+ """Initialize this fake instance."""
4404+ self.connections = []
4405+
4406+ def connectTCP(self, host, port, factory, *args):
4407+ """Fake the connection."""
4408+ self.connections.append((host, port, args))
4409+ factory.response_headers = {}
4410+ factory.deferred = defer.succeed("response content")
4411+
4412+ def connectSSL(self, host, port, factory, *args):
4413+ """Fake the connection."""
4414+ self.connections.append((host, port, args))
4415+ factory.response_headers = {}
4416+ factory.deferred = defer.succeed("response content")
4417+
4418+
4419+class TxWebClientTestCase(WebClientTestCase):
4420+ """Test case for txweb."""
4421+
4422+ webclient_factory = txweb.WebClient
4423+
4424+
4425+class TxWebClientReactorReplaceableTestCase(TestCase):
4426+ """In the txweb client the reactor is replaceable."""
4427+
4428+ timeout = 3
4429+ FAKE_HOST = u"fake"
4430+ FAKE_IRI_TEMPLATE = u"%%s://%s/fake_page" % FAKE_HOST
4431+
4432+ @defer.inlineCallbacks
4433+ def _test_replaceable_reactor(self, iri):
4434+ """The reactor can be replaced with the tunnel client."""
4435+ fake_reactor = FakeSavingReactor()
4436+ wc = txweb.WebClient(fake_reactor)
4437+ _response = yield wc.request(iri)
4438+ host, _port, _args = fake_reactor.connections[0]
4439+ self.assertEqual(host, self.FAKE_HOST)
4440+
4441+ def test_replaceable_reactor_http(self):
4442+ """Test the replaceable reactor with an http iri."""
4443+ return self._test_replaceable_reactor(self.FAKE_IRI_TEMPLATE % "http")
4444+
4445+ def test_replaceable_reactor_https(self):
4446+ """Test the replaceable reactor with an https iri."""
4447+ return self._test_replaceable_reactor(self.FAKE_IRI_TEMPLATE % "https")
4448+
4449+
4450 class TimestampCheckerTestCase(TestCase):
4451 """Tests for the timestampchecker classmethod."""
4452
4453@@ -375,6 +452,8 @@
4454 class BasicProxyTestCase(SquidTestCase):
4455 """Test that the proxy works at all."""
4456
4457+ timeout = 3
4458+
4459 @defer.inlineCallbacks
4460 def setUp(self):
4461 yield super(BasicProxyTestCase, self).setUp()
4462@@ -404,10 +483,119 @@
4463 result = yield self.wc.request(self.base_iri + SIMPLERESOURCE)
4464 self.assert_header_contains(result.headers["Via"], "squid")
4465
4466+ @defer.inlineCallbacks
4467+ def test_auth_proxy_is_used_creds_requested(self):
4468+ """The authenticated proxy is used by the webclient."""
4469+ settings = self.get_auth_proxy_settings()
4470+ partial_settings = dict(host=settings['host'], port=settings['port'])
4471+
4472+ def fake_creds_request(domain, retry):
4473+ """Fake user interaction."""
4474+ self.wc.proxy_username = settings['username']
4475+ self.wc.proxy_password = settings['password']
4476+ return defer.succeed(True)
4477+
4478+ self.patch(self.wc, 'request_proxy_auth_credentials',
4479+ fake_creds_request)
4480+
4481+ self.wc.force_use_proxy(partial_settings)
4482+ result = yield self.wc.request(self.base_iri + SIMPLERESOURCE)
4483+ self.assert_header_contains(result.headers["Via"], "squid")
4484+
4485+ @defer.inlineCallbacks
4486+ def test_auth_proxy_is_requested_creds_bad_details(self):
4487+ """Test using wrong credentials with the proxy."""
4488+ settings = self.get_auth_proxy_settings()
4489+ wrong_settings = dict(host=settings['host'], port=settings['port'],
4490+ username=settings['password'],
4491+ password=settings['username'])
4492+
4493+ def fake_creds_request(domain, retry):
4494+ """Fake user interaction."""
4495+ self.wc.proxy_username = settings['username']
4496+ self.wc.proxy_password = settings['password']
4497+ return defer.succeed(True)
4498+
4499+ self.patch(self.wc, 'request_proxy_auth_credentials',
4500+ fake_creds_request)
4501+
4502+ self.wc.force_use_proxy(wrong_settings)
4503+ result = yield self.wc.request(self.base_iri + SIMPLERESOURCE)
4504+ self.assert_header_contains(result.headers["Via"], "squid")
4505+
4506+ @defer.inlineCallbacks
4507+ def test_auth_proxy_is_requested_creds_bad_details_user(self):
4508+ """Test using no creds and user providing the wrong ones."""
4509+ settings = self.get_auth_proxy_settings()
4510+ partial_settings = dict(host=settings['host'], port=settings['port'])
4511+
4512+ def fake_creds_request(domain, retry):
4513+ """Fake user interaction."""
4514+ if retry:
4515+ self.wc.proxy_username = settings['username']
4516+ self.wc.proxy_password = settings['password']
4517+ else:
4518+ self.wc.proxy_username = settings['password']
4519+ self.wc.proxy_password = settings['username']
4520+ return defer.succeed(True)
4521+
4522+ self.patch(self.wc, 'request_proxy_auth_credentials',
4523+ fake_creds_request)
4524+
4525+ self.wc.force_use_proxy(partial_settings)
4526+ result = yield self.wc.request(self.base_iri + SIMPLERESOURCE)
4527+ self.assert_header_contains(result.headers["Via"], "squid")
4528+
4529+ @defer.inlineCallbacks
4530+ def test_auth_proxy_is_requested_creds_bad_details_everywhere(self):
4531+ """Test when we pass the wrong settings and get the wrong settings."""
4532+ settings = self.get_auth_proxy_settings()
4533+ wrong_settings = dict(host=settings['host'], port=settings['port'],
4534+ username=settings['password'],
4535+ password=settings['username'])
4536+
4537+ def fake_creds_request(domain, retry):
4538+ """Fake user interaction."""
4539+ if retry:
4540+ self.wc.proxy_username = settings['username']
4541+ self.wc.proxy_password = settings['password']
4542+ else:
4543+ self.wc.proxy_username = settings['password']
4544+ self.wc.proxy_password = settings['username']
4545+ return defer.succeed(True)
4546+
4547+ self.patch(self.wc, 'request_proxy_auth_credentials',
4548+ fake_creds_request)
4549+
4550+ self.wc.force_use_proxy(wrong_settings)
4551+ result = yield self.wc.request(self.base_iri + SIMPLERESOURCE)
4552+ self.assert_header_contains(result.headers["Via"], "squid")
4553+
4554+ def test_auth_proxy_is_requested_user_cancels(self):
4555+ """Test when the user cancels the creds dialog."""
4556+ settings = self.get_auth_proxy_settings()
4557+ partial_settings = dict(host=settings['host'], port=settings['port'])
4558+
4559+ def fake_creds_request(domain, retry):
4560+ """Fake user interaction."""
4561+ return defer.succeed(False)
4562+
4563+ self.patch(self.wc, 'request_proxy_auth_credentials',
4564+ fake_creds_request)
4565+
4566+ self.wc.force_use_proxy(partial_settings)
4567+ self.failUnlessFailure(self.wc.request(self.base_iri + SIMPLERESOURCE),
4568+ webclient.WebClientError)
4569+
4570 if WEBCLIENT_MODULE_NAME.endswith(".txweb"):
4571 reason = "txweb does not support proxies."
4572 test_anonymous_proxy_is_used.skip = reason
4573- test_authenticated_proxy_is_used.skip = reason
4574+ test_authenticated_proxy_is_used.kip = reason
4575+ test_auth_proxy_is_used_creds_requested.skip = reason
4576+ test_auth_proxy_is_requested_creds_bad_details.skip = reason
4577+ test_auth_proxy_is_requested_creds_bad_details_user.skip = reason
4578+ test_auth_proxy_is_requested_creds_bad_details_everywhere.skip = reason
4579+ test_auth_proxy_is_requested_user_cancels.skip = reason
4580
4581
4582 class HeaderDictTestCase(TestCase):
4583@@ -562,3 +750,315 @@
4584 """Test for the oauth signing code using HMAC-SHA1."""
4585
4586 oauth_sign = "HMAC-SHA1"
4587+
4588+
4589+class FakeKeyring(object):
4590+ """A fake keyring."""
4591+
4592+ def __init__(self, creds):
4593+ """A fake keyring."""
4594+ self.creds = creds
4595+
4596+ def __call__(self):
4597+ """Fake instance callable."""
4598+ return self
4599+
4600+ def get_credentials(self, domain):
4601+ """A fake get_credentials."""
4602+ if isinstance(self.creds, Exception):
4603+ return defer.fail(self.creds)
4604+ return defer.succeed(self.creds)
4605+
4606+
4607+class RequestProxyAuthTestCase(TestCase):
4608+ """Test the spawn of the creds dialog."""
4609+
4610+ @defer.inlineCallbacks
4611+ def setUp(self):
4612+ """Set the different tests."""
4613+ yield super(RequestProxyAuthTestCase, self).setUp()
4614+ self.wc = webclient.webclient_factory()
4615+ self.addCleanup(self.wc.shutdown)
4616+ self.domain = 'domain'
4617+ self.retry = False
4618+ self.creds = dict(username='username', password='password')
4619+
4620+ self.keyring = FakeKeyring(self.creds)
4621+ self.patch(keyring, 'Keyring', self.keyring)
4622+
4623+ self.spawn_return_code = USER_SUCCESS
4624+
4625+ def fake_spawn_process(args):
4626+ """Fake spawning a process."""
4627+ if isinstance(self.spawn_return_code, Exception):
4628+ return defer.fail(self.spawn_return_code)
4629+ return defer.succeed(self.spawn_return_code)
4630+
4631+ self.patch(webclient.common, 'spawn_program', fake_spawn_process)
4632+
4633+ def test_spawn_error(self):
4634+ """Test the case when we cannot spawn the process."""
4635+ self.spawn_return_code = Exception()
4636+ self.failUnlessFailure(self.wc.request_proxy_auth_credentials(
4637+ self.domain, True),
4638+ webclient.WebClientError)
4639+
4640+ @defer.inlineCallbacks
4641+ def test_creds_acquired(self):
4642+ """Test the case in which we do get the creds."""
4643+ got_creds = yield self.wc.request_proxy_auth_credentials(self.domain,
4644+ self.retry)
4645+ self.assertTrue(got_creds, 'Return true when creds are present.')
4646+ self.assertEqual(self.wc.proxy_username, self.creds['username'])
4647+ self.assertEqual(self.wc.proxy_password, self.creds['password'])
4648+
4649+ def test_creds_acquired_keyring_error(self):
4650+ """Test the case in which we cannot access the keyring."""
4651+ self.keyring.creds = Exception()
4652+ self.failUnlessFailure(self.wc.request_proxy_auth_credentials(
4653+ self.domain, self.retry),
4654+ webclient.WebClientError)
4655+
4656+ @defer.inlineCallbacks
4657+ def test_creds_none(self):
4658+ """Test the case in which we got None from the keyring."""
4659+ self.keyring.creds = None
4660+ got_creds = yield self.wc.request_proxy_auth_credentials(self.domain,
4661+ self.retry)
4662+ self.assertFalse(got_creds, 'Return false when creds are not present.')
4663+
4664+ def test_user_cancelation(self):
4665+ """Test the case in which the user cancels."""
4666+ self.spawn_return_code = USER_CANCELLATION
4667+ got_creds = yield self.wc.request_proxy_auth_credentials(self.domain,
4668+ self.retry)
4669+ self.assertFalse(got_creds, 'Return true when user cancels.')
4670+
4671+ def test_exception_error(self):
4672+ """Test the case in which something bad happened."""
4673+ self.spawn_return_code = EXCEPTION_RAISED
4674+ got_creds = yield self.wc.request_proxy_auth_credentials(self.domain,
4675+ self.retry)
4676+ self.assertFalse(got_creds, 'Return true when user cancels.')
4677+
4678+
4679+class BaseSSLTestCase(SquidTestCase):
4680+ """Base test that allows to use ssl connections."""
4681+
4682+ @defer.inlineCallbacks
4683+ def setUp(self):
4684+ """Set the diff tests."""
4685+ yield super(BaseSSLTestCase, self).setUp()
4686+ self.cert_dir = os.path.join(self.tmpdir, 'cert')
4687+ self.cert_details = dict(organization='Canonical',
4688+ common_name=gethostname(),
4689+ locality_name='London',
4690+ unit='Ubuntu One',
4691+ country_name='UK',
4692+ state_name='London',)
4693+ self.ssl_settings = self._generate_self_signed_certificate(
4694+ self.cert_dir,
4695+ self.cert_details)
4696+ self.addCleanup(self._clean_ssl_certificate_files)
4697+
4698+ self.ws = MockWebServer(self.ssl_settings)
4699+ self.addCleanup(self.ws.stop)
4700+ self.base_iri = self.ws.get_iri()
4701+ self.base_ssl_iri = self.ws.get_ssl_iri()
4702+
4703+ def _clean_ssl_certificate_files(self):
4704+ """Remove the certificate files."""
4705+ if os.path.exists(self.cert_dir):
4706+ shutil.rmtree(self.cert_dir)
4707+
4708+ def _generate_self_signed_certificate(self, cert_dir, cert_details):
4709+ """Generate the required SSL certificates."""
4710+ if not os.path.exists(cert_dir):
4711+ os.makedirs(cert_dir)
4712+ cert_path = os.path.join(cert_dir, 'cert.crt')
4713+ key_path = os.path.join(cert_dir, 'cert.key')
4714+
4715+ if os.path.exists(cert_path):
4716+ os.unlink(cert_path)
4717+ if os.path.exists(key_path):
4718+ os.unlink(key_path)
4719+
4720+ # create a key pair
4721+ key = crypto.PKey()
4722+ key.generate_key(crypto.TYPE_RSA, 1024)
4723+
4724+ # create a self-signed cert
4725+ cert = crypto.X509()
4726+ cert.get_subject().C = cert_details['country_name']
4727+ cert.get_subject().ST = cert_details['state_name']
4728+ cert.get_subject().L = cert_details['locality_name']
4729+ cert.get_subject().O = cert_details['organization']
4730+ cert.get_subject().OU = cert_details['unit']
4731+ cert.get_subject().CN = cert_details['common_name']
4732+ cert.set_serial_number(1000)
4733+ cert.gmtime_adj_notBefore(0)
4734+ cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60)
4735+ cert.set_issuer(cert.get_subject())
4736+ cert.set_pubkey(key)
4737+ cert.sign(key, 'sha1')
4738+
4739+ with open(cert_path, 'wt') as fd:
4740+ fd.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
4741+
4742+ with open(key_path, 'wt') as fd:
4743+ fd.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key))
4744+
4745+ return dict(key=key_path, cert=cert_path)
4746+
4747+
4748+class CorrectProxyTestCase(BaseSSLTestCase):
4749+ """Test the interaction with a SSL enabled proxy."""
4750+
4751+ @defer.inlineCallbacks
4752+ def setUp(self):
4753+ """Set the tests."""
4754+ yield super(CorrectProxyTestCase, self).setUp()
4755+
4756+ # fake the gsettings to have diff settings for https and http
4757+ http_settings = self.get_auth_proxy_settings()
4758+
4759+ #remember so that we can use them in the creds request
4760+ proxy_username = http_settings['username']
4761+ proxy_password = http_settings['password']
4762+
4763+ # delete the username and password so that we get a 407 for testing
4764+ del http_settings['username']
4765+ del http_settings['password']
4766+
4767+ https_settings = self.get_nonauth_proxy_settings()
4768+
4769+ proxy_settings = dict(http=http_settings, https=https_settings)
4770+ self.patch(gsettings, "get_proxy_settings", lambda: proxy_settings)
4771+
4772+ self.wc = webclient.webclient_factory()
4773+ self.addCleanup(self.wc.shutdown)
4774+
4775+ self.called = []
4776+
4777+ def fake_creds_request(domain, retry):
4778+ """Fake user interaction."""
4779+ self.called.append('request_proxy_auth_credentials')
4780+ self.wc.proxy_username = proxy_username
4781+ self.wc.proxy_password = proxy_password
4782+ return defer.succeed(True)
4783+
4784+ self.patch(self.wc, 'request_proxy_auth_credentials',
4785+ fake_creds_request)
4786+
4787+ def assert_header_contains(self, headers, expected):
4788+ """One of the headers matching key must contain a given value."""
4789+ self.assertTrue(any(expected in value for value in headers))
4790+
4791+ @defer.inlineCallbacks
4792+ def test_https_request(self):
4793+ """Test using the correct proxy for the ssl request.
4794+
4795+ In order to assert that the correct proxy is used we expect not to call
4796+ the auth dialog since we set the https proxy not to use the auth proxy
4797+ and to fail because we are reaching a https page with bad self-signed
4798+ certs.
4799+ """
4800+ # we fail due to the fake ssl cert
4801+ yield self.failUnlessFailure(self.wc.request(
4802+ self.base_ssl_iri + SIMPLERESOURCE),
4803+ webclient.WebClientError)
4804+ # https requests do not use the auth proxy therefore called should be
4805+ # empty. This asserts that we are using the correct settings for the
4806+ # request.
4807+ self.assertEqual([], self.called)
4808+
4809+ @defer.inlineCallbacks
4810+ def test_http_request(self):
4811+ """Test using the correct proxy for the plain request.
4812+
4813+ This tests does the opposite to the https tests. We did set the auth
4814+ proxy for the http request therefore we expect the proxy dialog to be
4815+ used and not to get an error since we are not visiting a https with bad
4816+ self-signed certs.
4817+ """
4818+ # we do not fail since we are not going to the https page
4819+ result = yield self.wc.request(self.base_iri + SIMPLERESOURCE)
4820+ self.assert_header_contains(result.headers["Via"], "squid")
4821+ # assert that we did go through the auth proxy
4822+ self.assertIn('request_proxy_auth_credentials', self.called)
4823+
4824+ if WEBCLIENT_MODULE_NAME.endswith(".txweb"):
4825+ reason = 'Multiple proxy settings is not supported.'
4826+ test_https_request.skip = reason
4827+ test_http_request.skip = reason
4828+
4829+ if WEBCLIENT_MODULE_NAME.endswith(".libsoup"):
4830+ reason = 'Hard to test since we need to fully mock gsettings.'
4831+ test_https_request.skip = reason
4832+ test_http_request.skip = reason
4833+
4834+ if WEBCLIENT_MODULE_NAME.endswith(".qtnetwork"):
4835+ reason = ('Updating proxy settings is not well support due to bug'
4836+ ' QTBUG-14850: https://bugreports.qt-project.org/'
4837+ 'browse/QTBUG-14850')
4838+ test_https_request.skip = reason
4839+ test_http_request.skip = reason
4840+
4841+
4842+class SSLTestCase(BaseSSLTestCase):
4843+ """Test error handling when dealing with ssl."""
4844+
4845+ @defer.inlineCallbacks
4846+ def setUp(self):
4847+ """Set the diff tests."""
4848+ yield super(SSLTestCase, self).setUp()
4849+
4850+ self.wc = webclient.webclient_factory()
4851+ self.addCleanup(self.wc.shutdown)
4852+
4853+ self.return_code = USER_CANCELLATION
4854+ self.called = []
4855+
4856+ def fake_launch_ssl_dialog(client, domain, details):
4857+ """Fake the ssl dialog."""
4858+ self.called.append(('_launch_ssl_dialog', domain, details))
4859+ return defer.succeed(self.return_code)
4860+
4861+ self.patch(BaseWebClient, '_launch_ssl_dialog', fake_launch_ssl_dialog)
4862+
4863+ @defer.inlineCallbacks
4864+ def _assert_ssl_fail_user_accepts(self, proxy_settings=None):
4865+ """Assert the dialog is shown in an ssl fail."""
4866+ self.return_code = USER_SUCCESS
4867+ if proxy_settings:
4868+ self.wc.force_use_proxy(proxy_settings)
4869+ yield self.wc.request(self.base_ssl_iri + SIMPLERESOURCE)
4870+ details = SSL_DETAILS_TEMPLATE % self.cert_details
4871+ self.assertIn(('_launch_ssl_dialog', gethostname(), details),
4872+ self.called)
4873+
4874+ def test_ssl_fail_dialog_user_accepts(self):
4875+ """Test showing the dialog and accepting."""
4876+ self._assert_ssl_fail_user_accepts()
4877+
4878+ def test_ssl_fail_dialog_user_accepts_via_proxy(self):
4879+ """Test showing the dialog and accepting when using a proxy."""
4880+ self._assert_ssl_fail_user_accepts(self.get_nonauth_proxy_settings())
4881+
4882+ def test_ssl_fail_dialog_user_rejects(self):
4883+ """Test showing the dialog and rejecting."""
4884+ self.failUnlessFailure(self.wc.request(self.base_iri + SIMPLERESOURCE),
4885+ webclient.WebClientError)
4886+
4887+ def test_format_ssl_details(self):
4888+ """Assert that details are correctly formatted"""
4889+ details = SSL_DETAILS_TEMPLATE % self.cert_details
4890+ self.assertEqual(details,
4891+ self.wc.format_ssl_details(self.cert_details))
4892+
4893+ if (WEBCLIENT_MODULE_NAME.endswith(".txweb") or
4894+ WEBCLIENT_MODULE_NAME.endswith(".libsoup")):
4895+ reason = 'SSL support has not yet been implemented.'
4896+ test_ssl_fail_dialog_user_accepts.skip = reason
4897+ test_ssl_fail_dialog_user_accepts_via_proxy.skip = reason
4898+ test_ssl_fail_dialog_user_rejects.skip = reason
4899
4900=== modified file 'ubuntu_sso/utils/webclient/txweb.py'
4901--- ubuntu_sso/utils/webclient/txweb.py 2012-02-07 19:36:50 +0000
4902+++ ubuntu_sso/utils/webclient/txweb.py 2012-03-20 16:10:09 +0000
4903@@ -16,11 +16,9 @@
4904 """A webclient backend that uses twisted.web.client."""
4905
4906 import base64
4907-
4908-from StringIO import StringIO
4909-
4910-from twisted.internet import defer, protocol
4911-from zope.interface import implements
4912+import urlparse
4913+
4914+from twisted.internet import defer
4915
4916 from ubuntu_sso.utils.webclient.common import (
4917 BaseWebClient,
4918@@ -31,64 +29,80 @@
4919 )
4920
4921
4922-class StringProtocol(protocol.Protocol):
4923- """Hold the stuff received in a StringIO."""
4924-
4925- # pylint: disable=C0103
4926- def __init__(self):
4927- """Initialize this instance."""
4928- self.deferred = defer.Deferred()
4929- self.content = StringIO()
4930-
4931- def dataReceived(self, data):
4932- """Some more blocks received."""
4933- self.content.write(data)
4934-
4935- def connectionLost(self, reason=protocol.connectionDone):
4936- """No more bytes available."""
4937- self.deferred.callback(self.content.getvalue())
4938-
4939-
4940-class StringProducer(object):
4941- """Simple implementation of IBodyProducer."""
4942-
4943- # delay import, otherwise a default reactor gets installed
4944- from twisted.web import iweb
4945-
4946- implements(iweb.IBodyProducer)
4947-
4948- def __init__(self, body):
4949- """Initialize this instance with some bytes."""
4950- self.body = body
4951- self.length = len(body)
4952-
4953- # pylint: disable=C0103
4954- def startProducing(self, consumer):
4955- """Start producing to the given IConsumer provider."""
4956- consumer.write(self.body)
4957- return defer.succeed(None)
4958-
4959- def pauseProducing(self):
4960- """In our case, do nothing."""
4961-
4962- def stopProducing(self):
4963- """In our case, do nothing."""
4964+class RawResponse(object):
4965+ """A raw response from the webcall."""
4966+
4967+ def __init__(self, headers, content, code=200, phrase="OK"):
4968+ """Initialize this response."""
4969+ self.headers = headers
4970+ self.content = content
4971+ self.code = code
4972+ self.phrase = phrase
4973
4974
4975 class WebClient(BaseWebClient):
4976 """A simple web client that does not support proxies, yet."""
4977
4978- # delay import, otherwise a default reactor gets installed
4979- from twisted.internet import reactor
4980- from twisted.web import client, http, http_headers
4981-
4982- # Undefined variable 'http_headers', 'client', 'reactor', 'http'
4983- # pylint: disable=E0602
4984+ def __init__(self, connector=None, context_factory=None, **kwargs):
4985+ """Initialize this webclient."""
4986+ super(WebClient, self).__init__(**kwargs)
4987+
4988+ if connector is None:
4989+ from twisted.internet import reactor
4990+ self.connector = reactor
4991+ else:
4992+ self.connector = connector
4993+
4994+ if context_factory is None:
4995+ from twisted.internet import ssl
4996+ self.context_factory = ssl.ClientContextFactory()
4997+ else:
4998+ self.context_factory = context_factory
4999+
5000+ @defer.inlineCallbacks
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches