Merge lp:~nataliabidart/ubuntu-sso-client/stable-3-0-update-2.99.91 into lp:ubuntu-sso-client/stable-3-0
- stable-3-0-update-2.99.91
- Merge into stable-3-0
Status: | Merged |
---|---|
Approved by: | Natalia Bidart |
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 |
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Roberto Alsina (community) | Approve | ||
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_
- 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_
that the signal emits (LP: #945066).
[ Jeremy Bicha <email address hidden> ]
- Improve the grammar for the CLOSE_AND_
(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 QNetworkAccessM
- Stopped listening to the proxyAuthentica
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_
- Improved code for the 'sign in' button validation.
[ Roberto Alsina <email address hidden> ]
- Made the ubuntu-
- 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).
Description of the change
Roberto Alsina (ralsina) : | # |
Preview Diff
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 |