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

Proposed by Natalia Bidart
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
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_general_error_message is not a dict (LP: #865176).
  - Adding some checks to setup_page (LP: #951461).
  - Adding a padding to the right margin of the reset layout, and align
    one of its layout to the left (LP: #945065).
  - Executing hide_error when the user click the refresh captcha link,
    not inside of the _refresh_captcha method, because this is executed
    automatically when a captcha error is generated, so we will always
    miss the error message (LP: #955010).
  - Removing the align property from the label that wasn't necessary
    and was breakint the work wrap. Also adjust the height of the widget
    depending if it has more than one line (LP: #940392).
  - Improve logging operations (LP: #934500).
  - Making LINK_STYLE to be unicode (LP: #950953).
  - Setting the window title equal to the app_name (LP: #949744).
  - The _move_to_email_verification_page wasn't receiving the params
    that the signal emits (LP: #945066).

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

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

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

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

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

To post a comment you must log in.
Revision history for this message
Roberto Alsina (ralsina) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bin/ubuntu-sso-login-qt'
--- bin/ubuntu-sso-login-qt 2012-02-13 15:43:59 +0000
+++ bin/ubuntu-sso-login-qt 2012-03-20 16:10:09 +0000
@@ -15,16 +15,19 @@
15# You should have received a copy of the GNU General Public License along15# You should have received a copy of the GNU General Public License along
16# with this program. If not, see <http://www.gnu.org/licenses/>.16# with this program. If not, see <http://www.gnu.org/licenses/>.
1717
18"""Start the sso GTK UI."""18"""Start the sso Qt UI."""
1919
20# Invalid name "ubuntu-sso-login-qt", pylint: disable=C010320# Invalid name "ubuntu-sso-login-qt", pylint: disable=C0103
21# Access to a protected member, pylint: disable=W021221# Access to a protected member, pylint: disable=W0212
2222
23import sys
24
23from ubuntu_sso.qt.main import main25from ubuntu_sso.qt.main import main
24from ubuntu_sso.utils.ui import parse_args26from ubuntu_sso.utils.ui import parse_args
2527
26from dbus.mainloop.qt import DBusQtMainLoop28if sys.platform.startswith('linux'):
27DBusQtMainLoop(set_as_default=True)29 from dbus.mainloop.qt import DBusQtMainLoop
30 DBusQtMainLoop(set_as_default=True)
2831
2932
30if __name__ == "__main__":33if __name__ == "__main__":
3134
=== modified file 'bin/ubuntu-sso-proxy-creds-qt'
--- bin/ubuntu-sso-proxy-creds-qt 2012-02-24 13:07:06 +0000
+++ bin/ubuntu-sso-proxy-creds-qt 2012-03-20 16:10:09 +0000
@@ -19,9 +19,12 @@
1919
20# Invalid name, pylint: disable=C010320# Invalid name, pylint: disable=C0103
2121
22# set the dbus main loop to be used22import sys
23from dbus.mainloop.qt import DBusQtMainLoop23
24DBusQtMainLoop(set_as_default=True)24if sys.platform.startswith('linux'):
25 # set the dbus main loop to be used
26 from dbus.mainloop.qt import DBusQtMainLoop
27 DBusQtMainLoop(set_as_default=True)
2528
26from ubuntu_sso.qt.proxy_dialog import main29from ubuntu_sso.qt.proxy_dialog import main
2730
2831
=== added file 'data/qt/linux.qss'
--- data/qt/linux.qss 1970-01-01 00:00:00 +0000
+++ data/qt/linux.qss 2012-03-20 16:10:09 +0000
@@ -0,0 +1,2 @@
1/* Styles specific to the linux platform */
2
03
=== modified file 'data/qt/loadingoverlay.ui'
--- data/qt/loadingoverlay.ui 2012-02-22 16:58:08 +0000
+++ data/qt/loadingoverlay.ui 2012-03-20 16:10:09 +0000
@@ -52,11 +52,6 @@
52 <verstretch>0</verstretch>52 <verstretch>0</verstretch>
53 </sizepolicy>53 </sizepolicy>
54 </property>54 </property>
55 <property name="font">
56 <font>
57 <pointsize>14</pointsize>
58 </font>
59 </property>
60 <property name="text">55 <property name="text">
61 <string notr="true">Getting information, please wait...</string>56 <string notr="true">Getting information, please wait...</string>
62 </property>57 </property>
6358
=== modified file 'data/qt/proxy_credentials_dialog.ui'
--- data/qt/proxy_credentials_dialog.ui 2012-02-23 11:47:00 +0000
+++ data/qt/proxy_credentials_dialog.ui 2012-03-20 16:10:09 +0000
@@ -113,8 +113,6 @@
113 </property>113 </property>
114 <property name="font">114 <property name="font">
115 <font>115 <font>
116 <pointsize>14</pointsize>
117 <weight>75</weight>
118 <bold>true</bold>116 <bold>true</bold>
119 </font>117 </font>
120 </property>118 </property>
121119
=== modified file 'data/qt/reset_password.ui'
--- data/qt/reset_password.ui 2012-03-05 20:30:57 +0000
+++ data/qt/reset_password.ui 2012-03-20 16:10:09 +0000
@@ -6,14 +6,14 @@
6 <rect>6 <rect>
7 <x>0</x>7 <x>0</x>
8 <y>0</y>8 <y>0</y>
9 <width>544</width>9 <width>505</width>
10 <height>280</height>10 <height>260</height>
11 </rect>11 </rect>
12 </property>12 </property>
13 <property name="layoutDirection">13 <property name="windowTitle">
14 <enum>Qt::LeftToRight</enum>14 <string/>
15 </property>15 </property>
16 <layout class="QVBoxLayout" name="verticalLayout_6">16 <layout class="QVBoxLayout" name="verticalLayout_4">
17 <property name="spacing">17 <property name="spacing">
18 <number>15</number>18 <number>15</number>
19 </property>19 </property>
@@ -21,261 +21,215 @@
21 <number>0</number>21 <number>0</number>
22 </property>22 </property>
23 <item>23 <item>
24 <layout class="QHBoxLayout" name="horizontalLayout">24 <layout class="QGridLayout" name="gridLayout">
25 <property name="spacing">25 <item row="0" column="0">
26 <number>0</number>26 <layout class="QVBoxLayout" name="verticalLayout">
27 </property>27 <property name="spacing">
28 <item>28 <number>3</number>
29 <layout class="QVBoxLayout" name="verticalLayout_5">29 </property>
30 <property name="spacing">30 <item>
31 <number>15</number>31 <widget class="QLabel" name="reset_code">
32 </property>32 <property name="sizePolicy">
33 <item>33 <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
34 <layout class="QVBoxLayout" name="verticalLayout_4">34 <horstretch>0</horstretch>
35 <property name="spacing">35 <verstretch>0</verstretch>
36 <number>3</number>36 </sizepolicy>
37 </property>37 </property>
38 <item>38 <property name="minimumSize">
39 <widget class="QLabel" name="reset_code">39 <size>
40 <property name="sizePolicy">40 <width>310</width>
41 <sizepolicy hsizetype="Minimum" vsizetype="Preferred">41 <height>0</height>
42 <horstretch>0</horstretch>42 </size>
43 <verstretch>0</verstretch>43 </property>
44 </sizepolicy>44 <property name="maximumSize">
45 </property>45 <size>
46 <property name="minimumSize">46 <width>16777215</width>
47 <size>47 <height>16777215</height>
48 <width>310</width>48 </size>
49 <height>0</height>49 </property>
50 </size>50 <property name="font">
51 </property>51 <font>
52 <property name="maximumSize">52 <bold>true</bold>
53 <size>53 </font>
54 <width>16777215</width>54 </property>
55 <height>16777215</height>55 <property name="text">
56 </size>56 <string notr="true">reset_code</string>
57 </property>57 </property>
58 <property name="font">58 </widget>
59 <font>59 </item>
60 <weight>75</weight>60 <item>
61 <bold>true</bold>61 <widget class="QLineEdit" name="reset_code_line_edit">
62 </font>62 <property name="sizePolicy">
63 </property>63 <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
64 <property name="text">64 <horstretch>0</horstretch>
65 <string notr="true">reset_code</string>65 <verstretch>0</verstretch>
66 </property>66 </sizepolicy>
67 </widget>67 </property>
68 </item>68 <property name="minimumSize">
69 <item>69 <size>
70 <widget class="QLineEdit" name="reset_code_line_edit">70 <width>300</width>
71 <property name="sizePolicy">71 <height>0</height>
72 <sizepolicy hsizetype="Minimum" vsizetype="Fixed">72 </size>
73 <horstretch>0</horstretch>73 </property>
74 <verstretch>0</verstretch>74 <property name="maximumSize">
75 </sizepolicy>75 <size>
76 </property>76 <width>300</width>
77 <property name="minimumSize">77 <height>16777215</height>
78 <size>78 </size>
79 <width>300</width>79 </property>
80 <height>0</height>80 </widget>
81 </size>81 </item>
82 </property>82 </layout>
83 <property name="maximumSize">83 </item>
84 <size>84 <item row="1" column="0">
85 <width>300</width>85 <layout class="QVBoxLayout" name="verticalLayout_2">
86 <height>16777215</height>86 <property name="spacing">
87 </size>87 <number>3</number>
88 </property>88 </property>
89 </widget>89 <item>
90 </item>90 <widget class="QLabel" name="password_label">
91 </layout>91 <property name="sizePolicy">
92 </item>92 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
93 <item>93 <horstretch>0</horstretch>
94 <layout class="QVBoxLayout" name="verticalLayout">94 <verstretch>0</verstretch>
95 <property name="spacing">95 </sizepolicy>
96 <number>3</number>96 </property>
97 </property>97 <property name="minimumSize">
98 <item>98 <size>
99 <widget class="QLabel" name="password_label">99 <width>310</width>
100 <property name="sizePolicy">100 <height>0</height>
101 <sizepolicy hsizetype="Minimum" vsizetype="Preferred">101 </size>
102 <horstretch>0</horstretch>102 </property>
103 <verstretch>0</verstretch>103 <property name="maximumSize">
104 </sizepolicy>104 <size>
105 </property>105 <width>16777215</width>
106 <property name="minimumSize">106 <height>16777215</height>
107 <size>107 </size>
108 <width>310</width>108 </property>
109 <height>0</height>109 <property name="font">
110 </size>110 <font>
111 </property>111 <bold>true</bold>
112 <property name="maximumSize">112 </font>
113 <size>113 </property>
114 <width>16777215</width>114 <property name="text">
115 <height>16777215</height>115 <string notr="true">password_label</string>
116 </size>116 </property>
117 </property>117 </widget>
118 <property name="font">118 </item>
119 <font>119 <item>
120 <weight>75</weight>120 <widget class="QLineEdit" name="password_line_edit">
121 <bold>true</bold>121 <property name="sizePolicy">
122 </font>122 <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
123 </property>123 <horstretch>0</horstretch>
124 <property name="text">124 <verstretch>0</verstretch>
125 <string notr="true">password_label</string>125 </sizepolicy>
126 </property>126 </property>
127 </widget>127 <property name="minimumSize">
128 </item>128 <size>
129 <item>129 <width>300</width>
130 <widget class="QLineEdit" name="password_line_edit">130 <height>0</height>
131 <property name="sizePolicy">131 </size>
132 <sizepolicy hsizetype="Minimum" vsizetype="Fixed">132 </property>
133 <horstretch>0</horstretch>133 <property name="maximumSize">
134 <verstretch>0</verstretch>134 <size>
135 </sizepolicy>135 <width>300</width>
136 </property>136 <height>16777215</height>
137 <property name="minimumSize">137 </size>
138 <size>138 </property>
139 <width>300</width>139 <property name="echoMode">
140 <height>0</height>140 <enum>QLineEdit::Password</enum>
141 </size>141 </property>
142 </property>142 </widget>
143 <property name="maximumSize">143 </item>
144 <size>144 </layout>
145 <width>300</width>145 </item>
146 <height>16777215</height>146 <item row="2" column="0">
147 </size>
148 </property>
149 <property name="echoMode">
150 <enum>QLineEdit::Password</enum>
151 </property>
152 </widget>
153 </item>
154 </layout>
155 </item>
156 <item>
157 <layout class="QVBoxLayout" name="verticalLayout_2">
158 <property name="spacing">
159 <number>3</number>
160 </property>
161 <item>
162 <widget class="QLabel" name="confirm_password_label">
163 <property name="sizePolicy">
164 <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
165 <horstretch>0</horstretch>
166 <verstretch>0</verstretch>
167 </sizepolicy>
168 </property>
169 <property name="minimumSize">
170 <size>
171 <width>310</width>
172 <height>0</height>
173 </size>
174 </property>
175 <property name="font">
176 <font>
177 <weight>75</weight>
178 <bold>true</bold>
179 </font>
180 </property>
181 <property name="text">
182 <string notr="true">confirm_password_label</string>
183 </property>
184 </widget>
185 </item>
186 <item>
187 <widget class="QLineEdit" name="confirm_password_line_edit">
188 <property name="sizePolicy">
189 <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
190 <horstretch>0</horstretch>
191 <verstretch>0</verstretch>
192 </sizepolicy>
193 </property>
194 <property name="minimumSize">
195 <size>
196 <width>300</width>
197 <height>0</height>
198 </size>
199 </property>
200 <property name="maximumSize">
201 <size>
202 <width>300</width>
203 <height>16777215</height>
204 </size>
205 </property>
206 <property name="echoMode">
207 <enum>QLineEdit::Password</enum>
208 </property>
209 </widget>
210 </item>
211 </layout>
212 </item>
213 </layout>
214 </item>
215 <item>
216 <layout class="QVBoxLayout" name="verticalLayout_3">147 <layout class="QVBoxLayout" name="verticalLayout_3">
217 <property name="spacing">148 <property name="spacing">
218 <number>0</number>149 <number>3</number>
219 </property>150 </property>
220 <item>151 <item>
221 <widget class="QLabel" name="password_assistance">152 <widget class="QLabel" name="confirm_password_label">
222 <property name="sizePolicy">153 <property name="sizePolicy">
223 <sizepolicy hsizetype="Maximum" vsizetype="Preferred">154 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
224 <horstretch>0</horstretch>155 <horstretch>0</horstretch>
225 <verstretch>0</verstretch>156 <verstretch>0</verstretch>
226 </sizepolicy>157 </sizepolicy>
227 </property>158 </property>
228 <property name="minimumSize">159 <property name="minimumSize">
229 <size>160 <size>
230 <width>220</width>161 <width>310</width>
231 <height>100</height>162 <height>0</height>
163 </size>
164 </property>
165 <property name="font">
166 <font>
167 <bold>true</bold>
168 </font>
169 </property>
170 <property name="text">
171 <string notr="true">confirm_password_label</string>
172 </property>
173 </widget>
174 </item>
175 <item>
176 <widget class="QLineEdit" name="confirm_password_line_edit">
177 <property name="sizePolicy">
178 <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
179 <horstretch>0</horstretch>
180 <verstretch>0</verstretch>
181 </sizepolicy>
182 </property>
183 <property name="minimumSize">
184 <size>
185 <width>300</width>
186 <height>0</height>
232 </size>187 </size>
233 </property>188 </property>
234 <property name="maximumSize">189 <property name="maximumSize">
235 <size>190 <size>
236 <width>220</width>191 <width>300</width>
237 <height>16777215</height>192 <height>16777215</height>
238 </size>193 </size>
239 </property>194 </property>
240 <property name="text">195 <property name="echoMode">
241 <string notr="true">password_assistance</string>196 <enum>QLineEdit::Password</enum>
242 </property>
243 <property name="indent">
244 <number>20</number>
245 </property>197 </property>
246 </widget>198 </widget>
247 </item>199 </item>
248 <item>
249 <spacer name="verticalSpacer_2">
250 <property name="orientation">
251 <enum>Qt::Vertical</enum>
252 </property>
253 <property name="sizeHint" stdset="0">
254 <size>
255 <width>20</width>
256 <height>40</height>
257 </size>
258 </property>
259 </spacer>
260 </item>
261 <item>
262 <spacer name="horizontalSpacer">
263 <property name="orientation">
264 <enum>Qt::Horizontal</enum>
265 </property>
266 <property name="sizeType">
267 <enum>QSizePolicy::Ignored</enum>
268 </property>
269 <property name="sizeHint" stdset="0">
270 <size>
271 <width>220</width>
272 <height>0</height>
273 </size>
274 </property>
275 </spacer>
276 </item>
277 </layout>200 </layout>
278 </item>201 </item>
202 <item row="1" column="1" rowspan="2">
203 <widget class="QLabel" name="password_assistance">
204 <property name="sizePolicy">
205 <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
206 <horstretch>0</horstretch>
207 <verstretch>0</verstretch>
208 </sizepolicy>
209 </property>
210 <property name="minimumSize">
211 <size>
212 <width>185</width>
213 <height>95</height>
214 </size>
215 </property>
216 <property name="maximumSize">
217 <size>
218 <width>185</width>
219 <height>95</height>
220 </size>
221 </property>
222 <property name="text">
223 <string notr="true">password_assistance</string>
224 </property>
225 <property name="wordWrap">
226 <bool>true</bool>
227 </property>
228 <property name="indent">
229 <number>20</number>
230 </property>
231 </widget>
232 </item>
279 </layout>233 </layout>
280 </item>234 </item>
281 <item>235 <item>
@@ -327,22 +281,5 @@
327 </layout>281 </layout>
328 </widget>282 </widget>
329 <resources/>283 <resources/>
330 <connections>284 <connections/>
331 <connection>
332 <sender>confirm_password_line_edit</sender>
333 <signal>returnPressed()</signal>
334 <receiver>reset_password_button</receiver>
335 <slot>click()</slot>
336 <hints>
337 <hint type="sourcelabel">
338 <x>160</x>
339 <y>81</y>
340 </hint>
341 <hint type="destinationlabel">
342 <x>541</x>
343 <y>237</y>
344 </hint>
345 </hints>
346 </connection>
347 </connections>
348</ui>285</ui>
349286
=== modified file 'data/qt/resources.qrc'
--- data/qt/resources.qrc 2012-02-16 14:13:36 +0000
+++ data/qt/resources.qrc 2012-03-20 16:10:09 +0000
@@ -6,5 +6,7 @@
6 <file>../Ubuntu-B.ttf</file>6 <file>../Ubuntu-B.ttf</file>
7 <file>../balloon_shape.png</file>7 <file>../balloon_shape.png</file>
8 <file>stylesheet.qss</file>8 <file>stylesheet.qss</file>
9 <file>windows.qss</file>
10 <file>linux.qss</file>
9 </qresource>11 </qresource>
10</RCC>12</RCC>
1113
=== modified file 'data/qt/setup_account.ui'
--- data/qt/setup_account.ui 2012-03-05 21:52:45 +0000
+++ data/qt/setup_account.ui 2012-03-20 16:10:09 +0000
@@ -85,7 +85,6 @@
85 <widget class="QLabel" name="name_label">85 <widget class="QLabel" name="name_label">
86 <property name="font">86 <property name="font">
87 <font>87 <font>
88 <weight>75</weight>
89 <bold>true</bold>88 <bold>true</bold>
90 </font>89 </font>
91 </property>90 </property>
@@ -114,11 +113,6 @@
114 <height>16777215</height>113 <height>16777215</height>
115 </size>114 </size>
116 </property>115 </property>
117 <property name="font">
118 <font>
119 <pointsize>11</pointsize>
120 </font>
121 </property>
122 <property name="formError" stdset="0">116 <property name="formError" stdset="0">
123 <bool>false</bool>117 <bool>false</bool>
124 </property>118 </property>
@@ -135,7 +129,6 @@
135 <widget class="QLabel" name="email_label">129 <widget class="QLabel" name="email_label">
136 <property name="font">130 <property name="font">
137 <font>131 <font>
138 <weight>75</weight>
139 <bold>true</bold>132 <bold>true</bold>
140 </font>133 </font>
141 </property>134 </property>
@@ -164,11 +157,6 @@
164 <height>16777215</height>157 <height>16777215</height>
165 </size>158 </size>
166 </property>159 </property>
167 <property name="font">
168 <font>
169 <pointsize>11</pointsize>
170 </font>
171 </property>
172 <property name="placeholderText">160 <property name="placeholderText">
173 <string/>161 <string/>
174 </property>162 </property>
@@ -229,7 +217,6 @@
229 <widget class="QLabel" name="confirm_email_label">217 <widget class="QLabel" name="confirm_email_label">
230 <property name="font">218 <property name="font">
231 <font>219 <font>
232 <weight>75</weight>
233 <bold>true</bold>220 <bold>true</bold>
234 </font>221 </font>
235 </property>222 </property>
@@ -258,11 +245,6 @@
258 <height>16777215</height>245 <height>16777215</height>
259 </size>246 </size>
260 </property>247 </property>
261 <property name="font">
262 <font>
263 <pointsize>11</pointsize>
264 </font>
265 </property>
266 <property name="placeholderText">248 <property name="placeholderText">
267 <string/>249 <string/>
268 </property>250 </property>
@@ -377,7 +359,6 @@
377 <widget class="QLabel" name="password_label">359 <widget class="QLabel" name="password_label">
378 <property name="font">360 <property name="font">
379 <font>361 <font>
380 <weight>75</weight>
381 <bold>true</bold>362 <bold>true</bold>
382 </font>363 </font>
383 </property>364 </property>
@@ -406,13 +387,8 @@
406 <height>16777215</height>387 <height>16777215</height>
407 </size>388 </size>
408 </property>389 </property>
409 <property name="font">
410 <font>
411 <pointsize>11</pointsize>
412 </font>
413 </property>
414 <property name="toolTip">390 <property name="toolTip">
415 <string notr="true">Your password must be at least 8 characters long and at least contain one number and one upper later.</string>391 <string notr="true">Your password must be at least 8 characters long and contain at least one number and one uppercase letter.</string>
416 </property>392 </property>
417 <property name="statusTip">393 <property name="statusTip">
418 <string/>394 <string/>
@@ -477,7 +453,6 @@
477 <widget class="QLabel" name="confirm_password_label">453 <widget class="QLabel" name="confirm_password_label">
478 <property name="font">454 <property name="font">
479 <font>455 <font>
480 <weight>75</weight>
481 <bold>true</bold>456 <bold>true</bold>
482 </font>457 </font>
483 </property>458 </property>
@@ -506,11 +481,6 @@
506 <height>16777215</height>481 <height>16777215</height>
507 </size>482 </size>
508 </property>483 </property>
509 <property name="font">
510 <font>
511 <pointsize>11</pointsize>
512 </font>
513 </property>
514 <property name="echoMode">484 <property name="echoMode">
515 <enum>QLineEdit::Password</enum>485 <enum>QLineEdit::Password</enum>
516 </property>486 </property>
@@ -580,11 +550,6 @@
580 <height>16777215</height>550 <height>16777215</height>
581 </size>551 </size>
582 </property>552 </property>
583 <property name="font">
584 <font>
585 <pointsize>11</pointsize>
586 </font>
587 </property>
588 <property name="locale">553 <property name="locale">
589 <locale language="English" country="UnitedStates"/>554 <locale language="English" country="UnitedStates"/>
590 </property>555 </property>
591556
=== modified file 'data/qt/ssl_dialog.ui'
--- data/qt/ssl_dialog.ui 2012-02-24 15:22:26 +0000
+++ data/qt/ssl_dialog.ui 2012-03-20 16:10:09 +0000
@@ -95,11 +95,6 @@
95 </property>95 </property>
96 <item>96 <item>
97 <widget class="QLabel" name="title_label">97 <widget class="QLabel" name="title_label">
98 <property name="font">
99 <font>
100 <pointsize>14</pointsize>
101 </font>
102 </property>
103 <property name="text">98 <property name="text">
104 <string notr="true">Do you want to connect to this server</string>99 <string notr="true">Do you want to connect to this server</string>
105 </property>100 </property>
106101
=== modified file 'data/qt/stylesheet.qss'
--- data/qt/stylesheet.qss 2012-03-05 20:30:57 +0000
+++ data/qt/stylesheet.qss 2012-03-20 16:10:09 +0000
@@ -1,5 +1,4 @@
1QWidget {1QWidget {
2 font-family: "Ubuntu";
3 color: #333333;2 color: #333333;
4}3}
54
@@ -16,7 +15,6 @@
1615
17QLabel#password_assistance {16QLabel#password_assistance {
18 border-image: url(":/balloon_shape.png");17 border-image: url(":/balloon_shape.png");
19 font-size: 12px;
20}18}
2119
22QLineEdit {20QLineEdit {
@@ -91,21 +89,11 @@
91 min-height: 100px;89 min-height: 100px;
92}90}
9391
94QFrame#frm_box > QLabel {92WizardHeader {
95 font-size: 20px;
96}
97
98QLabel#title_label {
99 font-size: 20px;
100}
101
102QFrame#header {
103 padding-top: 1px;93 padding-top: 1px;
104 padding-bottom: 1px;94 padding-bottom: 1px;
105}95}
10696
107QLabel#form_errors {97QLabel#form_errors {
108 font: bold 14px;
109 color: #df2d1f;
110 padding-bottom: 1px;98 padding-bottom: 1px;
111}99}
112100
=== added file 'data/qt/windows.qss'
--- data/qt/windows.qss 1970-01-01 00:00:00 +0000
+++ data/qt/windows.qss 2012-03-20 16:10:09 +0000
@@ -0,0 +1,5 @@
1/* Styles specific to the windows platform */
2
3QWidget {
4 font-family: "Ubuntu";
5}
06
=== modified file 'po/POTFILES.in'
--- po/POTFILES.in 2010-11-19 21:35:11 +0000
+++ po/POTFILES.in 2012-03-20 16:10:09 +0000
@@ -1,1 +1,2 @@
1ubuntu_sso/gtk/gui.py1ubuntu_sso/gtk/gui.py
2ubuntu_sso/utils/ui.py
23
=== modified file 'run-tests'
--- run-tests 2012-02-17 16:57:34 +0000
+++ run-tests 2012-03-20 16:10:09 +0000
@@ -57,7 +57,7 @@
5757
58echo "*** Running QT test suite for ""$MODULE"" ***"58echo "*** Running QT test suite for ""$MODULE"" ***"
59./setup.py build59./setup.py build
60USE_QT_MAINLOOP=True $XVFB_CMDLINE u1trial --reactor=qt4 --gui -p "$GTK_TESTS_PATH" -i "test_windows.py" "$MODULE"60$XVFB_CMDLINE u1trial --reactor=qt4 --gui -p "$GTK_TESTS_PATH" -i "test_windows.py" "$MODULE"
61rm -rf _trial_temp61rm -rf _trial_temp
62rm -rf build62rm -rf build
6363
6464
=== modified file 'ubuntu_sso/__init__.py'
--- ubuntu_sso/__init__.py 2012-02-11 19:25:01 +0000
+++ ubuntu_sso/__init__.py 2012-03-20 16:10:09 +0000
@@ -15,6 +15,8 @@
15# with this program. If not, see <http://www.gnu.org/licenses/>.15# with this program. If not, see <http://www.gnu.org/licenses/>.
16"""Ubuntu Single Sign On client code."""16"""Ubuntu Single Sign On client code."""
1717
18import sys
19
18# DBus constants20# DBus constants
19DBUS_BUS_NAME = "com.ubuntu.sso"21DBUS_BUS_NAME = "com.ubuntu.sso"
2022
@@ -29,7 +31,13 @@
29# return codes for UIs31# return codes for UIs
30USER_SUCCESS = 032USER_SUCCESS = 0
31USER_CANCELLATION = 1033USER_CANCELLATION = 10
34EXCEPTION_RAISED = 11
3235
33# available UIs36# available UIs
34UI_EXECUTABLE_GTK = 'ubuntu-sso-login-gtk'37UI_EXECUTABLE_GTK = 'ubuntu-sso-login-gtk'
35UI_EXECUTABLE_QT = 'ubuntu-sso-login-qt'38UI_EXECUTABLE_QT = 'ubuntu-sso-login-qt'
39UI_PROXY_CREDS_DIALOG = 'ubuntu-sso-proxy-creds-qt'
40
41if getattr(sys, "frozen", None) is not None and sys.platform == "win32":
42 UI_EXECUTABLE_QT += ".exe"
43 UI_PROXY_CREDS_DIALOG += ".exe"
3644
=== modified file 'ubuntu_sso/gtk/gui.py'
--- ubuntu_sso/gtk/gui.py 2012-02-17 18:43:17 +0000
+++ ubuntu_sso/gtk/gui.py 2012-03-20 16:10:09 +0000
@@ -90,23 +90,6 @@
90 return c90 return c
91# pylint: enable=C010391# pylint: enable=C0103
9292
93
94# To be removed when Python bindings provide these constants
95# as per http://code.google.com/p/pywebkitgtk/issues/detail?id=44
96# WebKitLoadStatus
97WEBKIT_LOAD_PROVISIONAL = 0
98WEBKIT_LOAD_COMMITTED = 1
99WEBKIT_LOAD_FINISHED = 2
100WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT = 3
101WEBKIT_LOAD_FAILED = 4
102# WebKitWebNavigationReason
103WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED = 0
104WEBKIT_WEB_NAVIGATION_REASON_FORM_SUBMITTED = 1
105WEBKIT_WEB_NAVIGATION_REASON_BACK_FORWARD = 2
106WEBKIT_WEB_NAVIGATION_REASON_RELOAD = 3
107WEBKIT_WEB_NAVIGATION_REASON_FORM_RESUBMITTED = 4
108WEBKIT_WEB_NAVIGATION_REASON_OTHER = 5
109
110DEFAULT_WIDTH = 3093DEFAULT_WIDTH = 30
111# To be replaced by values from the theme (LP: #616526)94# To be replaced by values from the theme (LP: #616526)
112HELP_TEXT_COLOR = parse_color("#bfbfbf")95HELP_TEXT_COLOR = parse_color("#bfbfbf")
@@ -947,23 +930,14 @@
947930
948 self._set_current_page(self.processing_vbox)931 self._set_current_page(self.processing_vbox)
949932
950 def on_tc_button_clicked(self, *args, **kwargs):933 def _add_webkit_browser(self):
951 """The T&C button was clicked, create the browser and load terms."""934 """Add the webkit browser for the t&c."""
952 # delay the import of webkit to be able to build without it935 # delay the import of webkit to be able to build without it
953 from gi.repository import WebKit # pylint: disable=E0611936 from gi.repository import WebKit # pylint: disable=E0611
937
954 browser = WebKit.WebView()938 browser = WebKit.WebView()
955939
956 # The signal WebKitWebView::load-finished is deprecated and should not940 browser.connect('notify::load-status',
957 # be used in newly-written code. Use the "load-status" property
958 # instead. Connect to "notify::load-status" to monitor loading.
959
960 # nataliabidart (2010-10-04): connecting this signal makes the loading
961 # of the Ubuntu One terms URL to fail. So we're using the deprecated
962 # 'load-finished' for now.
963
964 #browser.connect('notify::load-status',
965 # self.on_tc_browser_notify_load_status)
966 browser.connect('load-finished',
967 self.on_tc_browser_notify_load_status)941 self.on_tc_browser_notify_load_status)
968 browser.connect('navigation-policy-decision-requested',942 browser.connect('navigation-policy-decision-requested',
969 self.on_tc_browser_navigation_requested)943 self.on_tc_browser_navigation_requested)
@@ -978,7 +952,14 @@
978 browser.load_uri(self.tc_url)952 browser.load_uri(self.tc_url)
979 browser.show()953 browser.show()
980 self.tc_browser_window.add(browser)954 self.tc_browser_window.add(browser)
981 self._set_current_page(self.processing_vbox)955
956 def on_tc_button_clicked(self, *args, **kwargs):
957 """The T&C button was clicked, create the browser and load terms."""
958 if self.tc_browser_window.get_child() is None:
959 self._add_webkit_browser()
960 self._set_current_page(self.processing_vbox)
961 else:
962 self._set_current_page(self.tc_browser_vbox)
982963
983 def on_tc_back_button_clicked(self, *args, **kwargs):964 def on_tc_back_button_clicked(self, *args, **kwargs):
984 """T & C 'back' button was clicked, return to the previous page."""965 """T & C 'back' button was clicked, return to the previous page."""
@@ -986,14 +967,18 @@
986967
987 def on_tc_browser_notify_load_status(self, browser, *args, **kwargs):968 def on_tc_browser_notify_load_status(self, browser, *args, **kwargs):
988 """The T&C page is being loaded."""969 """The T&C page is being loaded."""
989 if browser.get_load_status() == WEBKIT_LOAD_FINISHED:970 from gi.repository import WebKit # pylint: disable=E0611
971
972 if browser.get_load_status().real == WebKit.LoadStatus.FINISHED:
990 self._set_current_page(self.tc_browser_vbox)973 self._set_current_page(self.tc_browser_vbox)
991974
992 def on_tc_browser_navigation_requested(self, browser, frame, request,975 def on_tc_browser_navigation_requested(self, browser, frame, request,
993 action, decision, *args, **kwargs):976 action, decision, *args, **kwargs):
994 """The user wants to navigate within the T&C browser."""977 """The user wants to navigate within the T&C browser."""
978 from gi.repository import WebKit # pylint: disable=E0611
979
995 if action is not None and \980 if action is not None and \
996 action.get_reason() == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED:981 action.get_reason() == WebKit.WebNavigationReason.LINK_CLICKED:
997 if decision is not None:982 if decision is not None:
998 decision.ignore()983 decision.ignore()
999 url = action.get_original_uri()984 url = action.get_original_uri()
1000985
=== modified file 'ubuntu_sso/gtk/tests/test_gui.py'
--- ubuntu_sso/gtk/tests/test_gui.py 2012-02-17 18:43:17 +0000
+++ ubuntu_sso/gtk/tests/test_gui.py 2012-03-20 16:10:09 +0000
@@ -134,7 +134,7 @@
134134
135 def get_load_status(self):135 def get_load_status(self):
136 """Return the current load status."""136 """Return the current load status."""
137 return gui.WEBKIT_LOAD_FINISHED137 return WebKit.LoadStatus.FINISHED
138138
139 def show(self):139 def show(self):
140 """Show this instance."""140 """Show this instance."""
@@ -981,7 +981,7 @@
981 def test_notify_load_finished_connected(self):981 def test_notify_load_finished_connected(self):
982 """The 'load-finished' signal is connected."""982 """The 'load-finished' signal is connected."""
983 expected = [self.ui.on_tc_browser_notify_load_status]983 expected = [self.ui.on_tc_browser_notify_load_status]
984 self.assertEqual(self.browser._signals['load-finished'],984 self.assertEqual(self.browser._signals['notify::load-status'],
985 expected)985 expected)
986986
987 def test_tc_loaded_morphs_into_tc_browser_vbox(self):987 def test_tc_loaded_morphs_into_tc_browser_vbox(self):
@@ -998,7 +998,7 @@
998 def test_navigation_requested_succeeds_for_no_clicking(self):998 def test_navigation_requested_succeeds_for_no_clicking(self):
999 """The navigation request succeeds when user hasn't clicked a link."""999 """The navigation request succeeds when user hasn't clicked a link."""
1000 action = WebKit.WebNavigationAction()1000 action = WebKit.WebNavigationAction()
1001 action.set_reason(gui.WEBKIT_WEB_NAVIGATION_REASON_OTHER)1001 action.set_reason(WebKit.WebNavigationReason.OTHER)
10021002
1003 decision = WebKit.WebPolicyDecision()1003 decision = WebKit.WebPolicyDecision()
1004 decision.use = self._set_called1004 decision.use = self._set_called
@@ -1011,7 +1011,7 @@
1011 def test_navigation_requested_ignores_clicked_links(self):1011 def test_navigation_requested_ignores_clicked_links(self):
1012 """The navigation request is ignored if a link was clicked."""1012 """The navigation request is ignored if a link was clicked."""
1013 action = WebKit.WebNavigationAction()1013 action = WebKit.WebNavigationAction()
1014 action.set_reason(gui.WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED)1014 action.set_reason(WebKit.WebNavigationReason.LINK_CLICKED)
10151015
1016 decision = WebKit.WebPolicyDecision()1016 decision = WebKit.WebPolicyDecision()
1017 decision.ignore = self._set_called1017 decision.ignore = self._set_called
@@ -1037,7 +1037,7 @@
1037 """1037 """
1038 url = 'http://something.com/yadda'1038 url = 'http://something.com/yadda'
1039 action = WebKit.WebNavigationAction()1039 action = WebKit.WebNavigationAction()
1040 action.set_reason(gui.WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED)1040 action.set_reason(WebKit.WebNavigationReason.LINK_CLICKED)
1041 action.set_original_uri(url)1041 action.set_original_uri(url)
10421042
1043 decision = WebKit.WebPolicyDecision()1043 decision = WebKit.WebPolicyDecision()
@@ -1050,6 +1050,35 @@
1050 self.ui.on_tc_browser_navigation_requested(**kwargs)1050 self.ui.on_tc_browser_navigation_requested(**kwargs)
1051 self.assertEqual(self._called, ((url,), {}))1051 self.assertEqual(self._called, ((url,), {}))
10521052
1053 def test_on_tc_button_clicked_no_child(self):
1054 """Test the tc loading with no child."""
1055 called = []
1056
1057 def fake_add_browser():
1058 """Fake add browser."""
1059 called.append('fake_add_browser')
1060
1061 self.patch(self.ui, '_add_webkit_browser', fake_add_browser)
1062 self.patch(self.ui.tc_browser_window, 'get_child', lambda: None)
1063
1064 self.ui.on_tc_button_clicked()
1065 self.assertIn('fake_add_browser', called)
1066
1067 def test_on_tc_button_clicked_child(self):
1068 """Test the tc loading with child."""
1069 called = []
1070
1071 def fake_add_browser(i_self):
1072 """Fake add browser."""
1073 called.append('fake_add_browser')
1074
1075 self.patch(self.ui, '_add_webkit_browser', fake_add_browser)
1076
1077 browser = WebKit.WebView()
1078 self.ui.tc_browser_window.add(browser)
1079 self.ui.on_tc_button_clicked()
1080 self.assertNotIn('fake_add_browser', called)
1081
10531082
1054class RegistrationErrorTestCase(UbuntuSSOClientTestCase):1083class RegistrationErrorTestCase(UbuntuSSOClientTestCase):
1055 """Test suite for the user registration error handling."""1084 """Test suite for the user registration error handling."""
10561085
=== modified file 'ubuntu_sso/qt/__init__.py'
--- ubuntu_sso/qt/__init__.py 2012-03-05 18:56:50 +0000
+++ ubuntu_sso/qt/__init__.py 2012-03-20 16:10:09 +0000
@@ -18,19 +18,27 @@
1818
19import collections19import collections
2020
2121from PyQt4 import QtGui, QtCore
22LINK_STYLE = ('<a href="{link_url}">'22
23from ubuntu_sso.logger import setup_gui_logging
24from ubuntu_sso.utils.ui import GENERIC_BACKEND_ERROR
25
26logger = setup_gui_logging('ubuntu_sso.qt')
27
28LINK_STYLE = (u'<a href="{link_url}">'
23 '<span style="color:#df2d1f;">{link_text}</span></a>')29 '<span style="color:#df2d1f;">{link_text}</span></a>')
24ERROR_ALL = '__all__'30ERROR_ALL = '__all__'
25ERROR_STYLE = u'<font color="#df2d1f"><b>%s</b></font>'31ERROR_STYLE = u'<font color="#df2d1f" style="font-size:small"><b>%s</b></font>'
26ERROR_MESSAGE = 'message'32ERROR_MESSAGE = 'message'
27PREFERED_UI_SIZE = {'width': 550, 'height': 525}33PREFERED_UI_SIZE = {'width': 550, 'height': 525}
28TITLE_STYLE = u'<span style="font-size:24px">%s</span>'34TITLE_STYLE = u'<span style="font-size:xx-large;font-weight:bold;">%s</span>'
35WINDOW_TITLE = 'Ubuntu Single Sign On'
2936
3037
31# Based on the gtk implementation38# Based on the gtk implementation
32def build_general_error_message(errordict):39def build_general_error_message(errordict):
33 """Build a user-friendly error message from the errordict."""40 """Build a user-friendly error message from the errordict."""
41 logger.debug('build_general_error_message: errordict is: %r.', errordict)
34 result = ''42 result = ''
35 if isinstance(errordict, collections.Mapping):43 if isinstance(errordict, collections.Mapping):
36 msg1 = errordict.get(ERROR_ALL)44 msg1 = errordict.get(ERROR_ALL)
@@ -50,5 +58,25 @@
50 result = '\n'.join(58 result = '\n'.join(
51 [('%s: %s' % (k, v)) for k, v in errordict.iteritems()])59 [('%s: %s' % (k, v)) for k, v in errordict.iteritems()])
52 else:60 else:
53 result = repr(errordict)61 result = GENERIC_BACKEND_ERROR
62 logger.error('build_general_error_message with unknown error: %r',
63 errordict)
64
65 logger.info('build_general_error_message: returning %r.', result)
54 return result66 return result
67
68
69def maybe_elide_text(label, text, width, markup=None):
70 """Set 'text' to be the 'label's text.
71
72 If 'text' is longer than 'width', set the label's tooltip to be the full
73 text, and the text itself to be the elided version of 'text'.
74
75 """
76 fm = QtGui.QFontMetrics(label.font())
77 elided_text = fm.elidedText(text, QtCore.Qt.ElideRight, width)
78 if elided_text != text:
79 label.setToolTip(text)
80 if markup is not None:
81 elided_text = markup % elided_text
82 label.setText(elided_text)
5583
=== modified file 'ubuntu_sso/qt/common.py'
--- ubuntu_sso/qt/common.py 2012-02-16 14:13:36 +0000
+++ ubuntu_sso/qt/common.py 2012-03-20 16:10:09 +0000
@@ -28,9 +28,9 @@
28)28)
2929
30# all the text + styles that are used in the gui30# all the text + styles that are used in the gui
31BAD = u'<img src=":/password_hint_warning.png" /><font> %s </font>'31BAD = u'<img src=":/password_hint_warning.png" /><small> %s </small>'
32GOOD = u'<img src=":/password_hint_ok.png" /><font> %s </font>'32GOOD = u'<img src=":/password_hint_ok.png" /><small> %s </small>'
33NORMAL = u'<font> %s </font>'33NORMAL = u'<small> %s </small>'
3434
3535
36def password_assistance(line_edit, assistance, icon_type=BAD):36def password_assistance(line_edit, assistance, icon_type=BAD):
3737
=== modified file 'ubuntu_sso/qt/current_user_sign_in_page.py'
--- ubuntu_sso/qt/current_user_sign_in_page.py 2012-03-05 20:30:57 +0000
+++ ubuntu_sso/qt/current_user_sign_in_page.py 2012-03-20 16:10:09 +0000
@@ -61,6 +61,11 @@
61 }61 }
62 return result62 return result
6363
64 @property
65 def password(self):
66 """Return the content of the password edit."""
67 return unicode(self.ui.password_edit.text())
68
64 def on_user_not_validated(self, app_name, email):69 def on_user_not_validated(self, app_name, email):
65 """Show the validate email page."""70 """Show the validate email page."""
66 self.hide_overlay()71 self.hide_overlay()
@@ -76,6 +81,7 @@
7681
77 def initializePage(self):82 def initializePage(self):
78 """Setup UI details."""83 """Setup UI details."""
84 logger.debug('initializePage - About to show CurrentUserSignInPage')
79 self.setButtonText(QtGui.QWizard.CancelButton, CANCEL_BUTTON)85 self.setButtonText(QtGui.QWizard.CancelButton, CANCEL_BUTTON)
80 # Layout without custom button 1,86 # Layout without custom button 1,
81 # without finish button87 # without finish button
@@ -94,10 +100,8 @@
94100
95 def _set_translated_strings(self):101 def _set_translated_strings(self):
96 """Set the translated strings."""102 """Set the translated strings."""
97 logger.debug('CurrentUserSignInPage._set_translated_strings')
98 self.setTitle(LOGIN_TITLE.format(app_name=self.app_name))103 self.setTitle(LOGIN_TITLE.format(app_name=self.app_name))
99 self.setSubTitle(LOGIN_SUBTITLE % {'app_name': self.app_name})104 self.setSubTitle(LOGIN_SUBTITLE % {'app_name': self.app_name})
100
101 self.ui.email_label.setText(EMAIL_LABEL)105 self.ui.email_label.setText(EMAIL_LABEL)
102 self.ui.password_label.setText(LOGIN_PASSWORD_LABEL)106 self.ui.password_label.setText(LOGIN_PASSWORD_LABEL)
103 forgotten_text = LINK_STYLE.format(link_url='#',107 forgotten_text = LINK_STYLE.format(link_url='#',
@@ -107,7 +111,6 @@
107111
108 def _connect_ui(self):112 def _connect_ui(self):
109 """Connect the buttons to perform actions."""113 """Connect the buttons to perform actions."""
110 logger.debug('CurrentUserSignInPage._connect_buttons')
111 self.ui.forgot_password_label.linkActivated.connect(114 self.ui.forgot_password_label.linkActivated.connect(
112 self.on_forgotten_password)115 self.on_forgotten_password)
113 self.ui.email_edit.textChanged.connect(self._validate)116 self.ui.email_edit.textChanged.connect(self._validate)
@@ -116,18 +119,16 @@
116119
117 def _validate(self):120 def _validate(self):
118 """Perform input validation."""121 """Perform input validation."""
119 valid = True
120 correct_mail = is_correct_email(unicode(self.ui.email_edit.text()))122 correct_mail = is_correct_email(unicode(self.ui.email_edit.text()))
121 password = unicode(self.ui.password_edit.text())123 correct_password = len(unicode(self.ui.password_edit.text())) > 0
122 if not correct_mail or not password:124 enabled = correct_mail and correct_password
123 valid = False125 self.ui.sign_in_button.setEnabled(enabled)
124 self.ui.sign_in_button.setEnabled(valid)
125126
126 def login(self):127 def login(self):
127 """Perform the login using the self.backend."""128 """Perform the login using the self.backend."""
128 logger.debug('CurrentUserSignInPage.login')
129 # grab the data from the view and call the backend129 # grab the data from the view and call the backend
130 email = unicode(self.ui.email_edit.text())130 email = unicode(self.ui.email_edit.text())
131 logger.info('CurrentUserSignInPage.login for: %s', email)
131 password = unicode(self.ui.password_edit.text())132 password = unicode(self.ui.password_edit.text())
132 args = (self.app_name, email, password)133 args = (self.app_name, email, password)
133 if self.ping_url:134 if self.ping_url:
@@ -146,18 +147,18 @@
146 # let the user know147 # let the user know
147 logger.error('Got error when login %s, error: %s',148 logger.error('Got error when login %s, error: %s',
148 self.app_name, error)149 self.app_name, error)
149 self.show_error(self.app_name, build_general_error_message(error))150 self.show_error(build_general_error_message(error))
150151
151 def on_logged_in(self, app_name, result):152 def on_logged_in(self, app_name, result):
152 """We managed to log in."""153 """We managed to log in."""
153 logger.info('Logged in for %s', app_name)154 logger.info('Logged in for %s', app_name)
154 self.hide_overlay()155 self.hide_overlay()
155 email = unicode(self.ui.email_edit.text())156 email = unicode(self.ui.email_edit.text())
157 logger.debug('About to emit userLoggedIn signal with: (%s).', email)
156 self.userLoggedIn.emit(email)158 self.userLoggedIn.emit(email)
157 logger.debug('Wizard.loginSuccess emitted.')
158159
159 def on_forgotten_password(self, link=None):160 def on_forgotten_password(self, link=None):
160 """Show the user the forgotten password page."""161 """Show the user the forgotten password page."""
161 logger.info('Forgotten password')
162 self.hide_overlay()162 self.hide_overlay()
163 logger.debug('About to emit passwordForgotten signal')
163 self.passwordForgotten.emit()164 self.passwordForgotten.emit()
164165
=== modified file 'ubuntu_sso/qt/email_verification_page.py'
--- ubuntu_sso/qt/email_verification_page.py 2012-03-05 20:30:57 +0000
+++ ubuntu_sso/qt/email_verification_page.py 2012-03-20 16:10:09 +0000
@@ -71,7 +71,6 @@
7171
72 def _connect_ui(self):72 def _connect_ui(self):
73 """Set the connection of signals."""73 """Set the connection of signals."""
74 logger.debug('EmailVerificationController._connect_ui')
75 self.ui.verification_code_edit.textChanged.connect(74 self.ui.verification_code_edit.textChanged.connect(
76 self.validate_form)75 self.validate_form)
77 self.next_button.clicked.connect(self.validate_email)76 self.next_button.clicked.connect(self.validate_email)
@@ -84,7 +83,6 @@
8483
85 def _set_translated_strings(self):84 def _set_translated_strings(self):
86 """Set the different titles."""85 """Set the different titles."""
87 logger.debug('EmailVerificationController._set_titles')
88 self.header.set_title(VERIFY_EMAIL_TITLE)86 self.header.set_title(VERIFY_EMAIL_TITLE)
89 self.header.set_subtitle(VERIFY_EMAIL_CONTENT % {87 self.header.set_subtitle(VERIFY_EMAIL_CONTENT % {
90 "app_name": self.app_name,88 "app_name": self.app_name,
@@ -103,7 +101,8 @@
103101
104 def validate_email(self):102 def validate_email(self):
105 """Call the next action."""103 """Call the next action."""
106 logger.debug('EmailVerificationController.validate_email')104 logger.debug('EmailVerificationController.validate_email for: %s',
105 self.email)
107 code = unicode(self.ui.verification_code_edit.text())106 code = unicode(self.ui.verification_code_edit.text())
108 args = (self.app_name, self.email, self.password, code)107 args = (self.app_name, self.email, self.password, code)
109 self.hide_error()108 self.hide_error()
@@ -123,21 +122,25 @@
123122
124 def on_email_validated(self, app_name, email):123 def on_email_validated(self, app_name, email):
125 """Signal thrown after the email is validated."""124 """Signal thrown after the email is validated."""
126 logger.info('EmailVerificationController.on_email_validated')125 logger.info('EmailVerificationController.on_email_validated for %s, '
126 'email: %s', app_name, email)
127 self.hide_overlay()127 self.hide_overlay()
128 self.registrationSuccess.emit(self.email)128 self.registrationSuccess.emit(self.email)
129129
130 def on_email_validation_error(self, app_name, error):130 def on_email_validation_error(self, app_name, error):
131 """Signal thrown when there's a problem validating the email."""131 """Signal thrown when there's a problem validating the email."""
132 logger.error('Got error on email validation %s, error: %s',
133 app_name, error)
132 self.hide_overlay()134 self.hide_overlay()
133 msg = error.pop(ERROR_EMAIL_TOKEN, '')135 msg = error.pop(ERROR_EMAIL_TOKEN, '')
134 msg += build_general_error_message(error)136 msg += build_general_error_message(error)
135 self.show_error(self.app_name, msg)137 self.show_error(msg)
136138
137 # pylint: disable=C0103139 # pylint: disable=C0103
138140
139 def initializePage(self):141 def initializePage(self):
140 """Called to prepare the page just before it is shown."""142 """Called to prepare the page just before it is shown."""
143 logger.debug('initializePage - About to show EmailVerificationPage')
141 self.next_button.setDefault(True)144 self.next_button.setDefault(True)
142 self.next_button.setEnabled(False)145 self.next_button.setEnabled(False)
143 self.wizard().setButtonLayout([QtGui.QWizard.Stretch])146 self.wizard().setButtonLayout([QtGui.QWizard.Stretch])
144147
=== modified file 'ubuntu_sso/qt/enhanced_check_box.py'
--- ubuntu_sso/qt/enhanced_check_box.py 2012-03-01 16:53:29 +0000
+++ ubuntu_sso/qt/enhanced_check_box.py 2012-03-20 16:10:09 +0000
@@ -24,11 +24,12 @@
24class EnhancedCheckBox(QtGui.QCheckBox):24class EnhancedCheckBox(QtGui.QCheckBox):
25 """Enhanced QCheckBox to support links in the message displayed."""25 """Enhanced QCheckBox to support links in the message displayed."""
2626
27 def __init__(self, text=""):27 def __init__(self, text="", parent=None):
28 QtGui.QCheckBox.__init__(self)28 QtGui.QCheckBox.__init__(self, parent)
29 hbox = QtGui.QHBoxLayout()29 hbox = QtGui.QHBoxLayout()
30 hbox.setAlignment(QtCore.Qt.AlignLeft)
30 self.text_label = QtGui.QLabel(text)31 self.text_label = QtGui.QLabel(text)
31 self.text_label.setAlignment(QtCore.Qt.AlignTop)32 self.text_label.setWordWrap(True)
32 self.text_label.setOpenExternalLinks(True)33 self.text_label.setOpenExternalLinks(True)
33 padding = self.iconSize().width()34 padding = self.iconSize().width()
34 self.text_label.setStyleSheet("margin-top: -3px;"35 self.text_label.setStyleSheet("margin-top: -3px;"
@@ -37,6 +38,11 @@
37 hbox.addWidget(self.text_label)38 hbox.addWidget(self.text_label)
38 self.setLayout(hbox)39 self.setLayout(hbox)
3940
41 if parent is not None:
42 lines = self.text_label.width() / float(parent.width())
43 self.text_label.setMinimumWidth(parent.width())
44 self.setMinimumHeight(self.height() * lines)
45
40 self.stateChanged.connect(self.text_label.setFocus)46 self.stateChanged.connect(self.text_label.setFocus)
4147
42 def text(self):48 def text(self):
4349
=== modified file 'ubuntu_sso/qt/forgotten_password_page.py'
--- ubuntu_sso/qt/forgotten_password_page.py 2012-03-05 20:31:22 +0000
+++ ubuntu_sso/qt/forgotten_password_page.py 2012-03-20 16:10:09 +0000
@@ -63,6 +63,7 @@
6363
64 def initializePage(self):64 def initializePage(self):
65 """Set the initial state of ForgottenPassword page."""65 """Set the initial state of ForgottenPassword page."""
66 logger.debug('initializePage - About to show ForgottenPasswordPage')
66 self.ui.send_button.setDefault(True)67 self.ui.send_button.setDefault(True)
67 enabled = not self.ui.email_line_edit.text().isEmpty()68 enabled = not self.ui.email_line_edit.text().isEmpty()
68 self.ui.send_button.setEnabled(enabled)69 self.ui.send_button.setEnabled(enabled)
@@ -98,6 +99,7 @@
98 """Send the request password operation."""99 """Send the request password operation."""
99 self.hide_error()100 self.hide_error()
100 args = (self.app_name, self.email_address)101 args = (self.app_name, self.email_address)
102 logger.debug('Sending request new password for %s, email: %s', *args)
101 f = self.backend.request_password_reset_token103 f = self.backend.request_password_reset_token
102104
103 error_handler = partial(self._handle_error, f,105 error_handler = partial(self._handle_error, f,
@@ -113,6 +115,8 @@
113115
114 def on_password_reset_token_sent(self, app_name, email):116 def on_password_reset_token_sent(self, app_name, email):
115 """Action taken when we managed to get the password reset done."""117 """Action taken when we managed to get the password reset done."""
118 logger.info('ForgottenPasswordPage.on_password_reset_token_sent for '
119 '%s, email: %s', app_name, email)
116 # ignore the result and move to the reset page120 # ignore the result and move to the reset page
117 self.hide_overlay()121 self.hide_overlay()
118 self.passwordResetTokenSent.emit(email)122 self.passwordResetTokenSent.emit(email)
@@ -123,4 +127,4 @@
123 # set the error message127 # set the error message
124 self.hide_overlay()128 self.hide_overlay()
125 msg = REQUEST_PASSWORD_TOKEN_WRONG_EMAIL129 msg = REQUEST_PASSWORD_TOKEN_WRONG_EMAIL
126 self.show_error(self.app_name, msg)130 self.show_error(msg)
127131
=== modified file 'ubuntu_sso/qt/loadingoverlay.py'
--- ubuntu_sso/qt/loadingoverlay.py 2012-02-27 19:00:59 +0000
+++ ubuntu_sso/qt/loadingoverlay.py 2012-03-20 16:10:09 +0000
@@ -21,6 +21,8 @@
21from ubuntu_sso.qt.ui import loadingoverlay_ui21from ubuntu_sso.qt.ui import loadingoverlay_ui
22from ubuntu_sso.utils.ui import LOADING_OVERLAY22from ubuntu_sso.utils.ui import LOADING_OVERLAY
2323
24LOADING_STYLE = u'<span style="font-size:x-large;">{0}</span>'
25
2426
25class LoadingOverlay(QtGui.QFrame):27class LoadingOverlay(QtGui.QFrame):
26 """The widget that shows a loading animation and disable the widget below.28 """The widget that shows a loading animation and disable the widget below.
@@ -43,7 +45,7 @@
43 self.counter = 045 self.counter = 0
44 self.orientation = False46 self.orientation = False
4547
46 self.ui.label.setText(LOADING_OVERLAY)48 self.ui.label.setText(LOADING_STYLE.format(LOADING_OVERLAY))
4749
48 # Invalid name "paintEvent", "eventFilter", "showEvent", "timerEvent"50 # Invalid name "paintEvent", "eventFilter", "showEvent", "timerEvent"
49 # pylint: disable=C010351 # pylint: disable=C0103
5052
=== modified file 'ubuntu_sso/qt/main.py'
--- ubuntu_sso/qt/main.py 2012-02-24 19:54:48 +0000
+++ ubuntu_sso/qt/main.py 2012-03-20 16:10:09 +0000
@@ -25,6 +25,7 @@
25from ubuntu_sso.qt.ui import resources_rc25from ubuntu_sso.qt.ui import resources_rc
26# pylint: enable=W061126# pylint: enable=W0611
27from ubuntu_sso.qt.ubuntu_sso_wizard import UbuntuSSOClientGUI27from ubuntu_sso.qt.ubuntu_sso_wizard import UbuntuSSOClientGUI
28from ubuntu_sso.utils import PLATFORM_QSS
2829
2930
30def main(**kwargs):31def main(**kwargs):
@@ -34,9 +35,17 @@
34 QtGui.QFontDatabase.addApplicationFont(':/Ubuntu-R.ttf')35 QtGui.QFontDatabase.addApplicationFont(':/Ubuntu-R.ttf')
35 QtGui.QFontDatabase.addApplicationFont(':/Ubuntu-B.ttf')36 QtGui.QFontDatabase.addApplicationFont(':/Ubuntu-B.ttf')
3637
37 # Apply Style Sheet -- The windows version may be different38 data = []
38 qss = QtCore.QResource(":/stylesheet.qss")39 for qss_name in (PLATFORM_QSS, ":/stylesheet.qss"):
39 app.setStyleSheet(qss.data())40 qss = QtCore.QResource(qss_name)
41 data.append(unicode(qss.data()))
42 app.setStyleSheet('\n'.join(data))
43
44 # Fix the string that contains unicode chars.
45 for key in kwargs:
46 value = kwargs[key]
47 if isinstance(value, str):
48 kwargs[key] = value.decode('utf-8')
4049
41 # Unused variable 'ui', pylint: disable=W061250 # Unused variable 'ui', pylint: disable=W0612
42 ui = UbuntuSSOClientGUI(close_callback=app.exit, **kwargs)51 ui = UbuntuSSOClientGUI(close_callback=app.exit, **kwargs)
4352
=== modified file 'ubuntu_sso/qt/network_detection_page.py'
--- ubuntu_sso/qt/network_detection_page.py 2012-02-29 14:00:12 +0000
+++ ubuntu_sso/qt/network_detection_page.py 2012-03-20 16:10:09 +0000
@@ -20,7 +20,7 @@
20from PyQt4 import QtGui20from PyQt4 import QtGui
2121
22from ubuntu_sso import networkstate22from ubuntu_sso import networkstate
2323from ubuntu_sso.logger import setup_logging
24from ubuntu_sso.qt.sso_wizard_page import SSOWizardPage24from ubuntu_sso.qt.sso_wizard_page import SSOWizardPage
25from ubuntu_sso.qt.ui import network_detection_ui25from ubuntu_sso.qt.ui import network_detection_ui
26from ubuntu_sso.utils.ui import (26from ubuntu_sso.utils.ui import (
@@ -31,6 +31,9 @@
31)31)
3232
3333
34logger = setup_logging('ubuntu_sso.network_detection_page')
35
36
34class NetworkDetectionPage(SSOWizardPage):37class NetworkDetectionPage(SSOWizardPage):
3538
36 """Widget to show if we don't detect a network connection."""39 """Widget to show if we don't detect a network connection."""
@@ -48,6 +51,7 @@
4851
49 def initializePage(self):52 def initializePage(self):
50 """Set UI details."""53 """Set UI details."""
54 logger.debug('initializePage - About to show NetworkDetectionPage')
51 self.wizard()._next_id = None55 self.wizard()._next_id = None
5256
53 self.setButtonText(QtGui.QWizard.CustomButton1, TRY_AGAIN_BUTTON)57 self.setButtonText(QtGui.QWizard.CustomButton1, TRY_AGAIN_BUTTON)
5458
=== modified file 'ubuntu_sso/qt/proxy_dialog.py'
--- ubuntu_sso/qt/proxy_dialog.py 2012-03-05 20:30:57 +0000
+++ ubuntu_sso/qt/proxy_dialog.py 2012-03-20 16:10:09 +0000
@@ -21,6 +21,7 @@
21from PyQt4.QtGui import QApplication, QDialog, QIcon21from PyQt4.QtGui import QApplication, QDialog, QIcon
22from twisted.internet import defer22from twisted.internet import defer
2323
24from ubuntu_sso import EXCEPTION_RAISED, USER_SUCCESS, USER_CANCELLATION
24from ubuntu_sso.logger import setup_gui_logging25from ubuntu_sso.logger import setup_gui_logging
25from ubuntu_sso.keyring import Keyring26from ubuntu_sso.keyring import Keyring
26from ubuntu_sso.qt.ui.proxy_credentials_dialog_ui import Ui_ProxyCredsDialog27from ubuntu_sso.qt.ui.proxy_credentials_dialog_ui import Ui_ProxyCredsDialog
@@ -37,10 +38,6 @@
37 PROXY_CREDS_SAVE_BUTTON,38 PROXY_CREDS_SAVE_BUTTON,
38)39)
3940
40CREDS_ACQUIRED = 0
41USER_CANCELATION = -1
42EXCEPTION_RAISED = -2
43
44logger = setup_gui_logging("ubuntu_sso.qt.proxy_dialog")41logger = setup_gui_logging("ubuntu_sso.qt.proxy_dialog")
4542
4643
@@ -107,15 +104,15 @@
107 logger.debug('Save credentials as for domain %s.', self.domain)104 logger.debug('Save credentials as for domain %s.', self.domain)
108 yield self.keyring.set_credentials(self.domain, creds)105 yield self.keyring.set_credentials(self.domain, creds)
109 except Exception, e:106 except Exception, e:
110 logger.error('Could not retrieve credentials.')107 logger.exception('Could not set credentials:')
111 self.done(EXCEPTION_RAISED)108 self.done(EXCEPTION_RAISED)
112 # pylint: disable=W0703, W0612109 # pylint: disable=W0703, W0612
113 self.done(CREDS_ACQUIRED)110 self.done(USER_SUCCESS)
114111
115 def _on_cancel_clicked(self, *args):112 def _on_cancel_clicked(self, *args):
116 """End the dialog."""113 """End the dialog."""
117 logger.debug('User canceled credentials dialog.')114 logger.debug('User canceled credentials dialog.')
118 self.done(USER_CANCELATION)115 self.done(USER_CANCELLATION)
119116
120 def _set_buttons(self):117 def _set_buttons(self):
121 """Set the labels of the buttons."""118 """Set the labels of the buttons."""
122119
=== modified file 'ubuntu_sso/qt/reset_password_page.py'
--- ubuntu_sso/qt/reset_password_page.py 2012-03-05 20:30:57 +0000
+++ ubuntu_sso/qt/reset_password_page.py 2012-03-20 16:10:09 +0000
@@ -18,7 +18,7 @@
1818
19from functools import partial19from functools import partial
2020
21from PyQt4.QtCore import SIGNAL, pyqtSignal21from PyQt4.QtCore import Qt, SIGNAL, pyqtSignal
22from PyQt4.QtGui import QApplication22from PyQt4.QtGui import QApplication
2323
24from ubuntu_sso import NO_OP24from ubuntu_sso import NO_OP
@@ -73,7 +73,9 @@
7373
74 def initializePage(self):74 def initializePage(self):
75 """Extends QWizardPage initializePage method."""75 """Extends QWizardPage initializePage method."""
76 logger.debug('initializePage - About to show ResetPasswordPage')
76 super(ResetPasswordPage, self).initializePage()77 super(ResetPasswordPage, self).initializePage()
78 self.ui.gridLayout.setAlignment(Qt.AlignLeft)
77 common.password_default_assistance(self.ui.password_assistance)79 common.password_default_assistance(self.ui.password_assistance)
78 self.ui.password_assistance.setVisible(False)80 self.ui.password_assistance.setVisible(False)
79 self.setTitle(RESET_TITLE)81 self.setTitle(RESET_TITLE)
@@ -158,6 +160,8 @@
158160
159 def on_password_changed(self, app_name, email):161 def on_password_changed(self, app_name, email):
160 """Let user know that the password was changed."""162 """Let user know that the password was changed."""
163 logger.info('ResetPasswordPage.on_password_changed for %s, email: %s',
164 app_name, email)
161 self.hide_overlay()165 self.hide_overlay()
162 email = unicode(self.wizard().forgotten.ui.email_line_edit.text())166 email = unicode(self.wizard().forgotten.ui.email_line_edit.text())
163 self.passwordChanged.emit(email)167 self.passwordChanged.emit(email)
@@ -166,7 +170,7 @@
166 """Let the user know that there was an error."""170 """Let the user know that there was an error."""
167 logger.error('Got error changing password for %s, error: %s',171 logger.error('Got error changing password for %s, error: %s',
168 self.app_name, error)172 self.app_name, error)
169 self.show_error(self.app_name, build_general_error_message(error))173 self.show_error(build_general_error_message(error))
170174
171 def set_new_password(self):175 def set_new_password(self):
172 """Request a new password to be set."""176 """Request a new password to be set."""
173177
=== modified file 'ubuntu_sso/qt/setup_account_page.py'
--- ubuntu_sso/qt/setup_account_page.py 2012-03-06 14:13:10 +0000
+++ ubuntu_sso/qt/setup_account_page.py 2012-03-20 16:10:09 +0000
@@ -31,7 +31,7 @@
31from PyQt4 import QtGui, QtCore31from PyQt4 import QtGui, QtCore
3232
33from ubuntu_sso import NO_OP33from ubuntu_sso import NO_OP
34from ubuntu_sso.logger import setup_gui_logging34from ubuntu_sso.logger import setup_gui_logging, log_call
35from ubuntu_sso.qt import (35from ubuntu_sso.qt import (
36 LINK_STYLE,36 LINK_STYLE,
37 build_general_error_message,37 build_general_error_message,
@@ -112,11 +112,17 @@
112 }112 }
113 return result113 return result
114114
115 @property
116 def password(self):
117 """Return the content of the password edit."""
118 return unicode(self.ui.password_edit.text())
119
115 # Invalid name "initializePage"120 # Invalid name "initializePage"
116 # pylint: disable=C0103121 # pylint: disable=C0103
117122
118 def initializePage(self):123 def initializePage(self):
119 """Setup UI details."""124 """Setup UI details."""
125 logger.debug('initializePage - About to show SetupAccountPage')
120 # Set Setup Account button126 # Set Setup Account button
121 self.wizard().setOption(QtGui.QWizard.HaveCustomButton3, True)127 self.wizard().setOption(QtGui.QWizard.HaveCustomButton3, True)
122 try:128 try:
@@ -152,7 +158,6 @@
152158
153 def _set_translated_strings(self):159 def _set_translated_strings(self):
154 """Set the strings."""160 """Set the strings."""
155 logger.debug('SetUpAccountPage._set_translated_strings')
156 # set the translated string161 # set the translated string
157 title_page = REGISTER_TITLE.format(app_name=self.app_name)162 title_page = REGISTER_TITLE.format(app_name=self.app_name)
158 self.setTitle(title_page)163 self.setTitle(title_page)
@@ -190,7 +195,7 @@
190 terms = AGREE_TO_PRIVACY_POLICY.format(app_name=self.app_name,195 terms = AGREE_TO_PRIVACY_POLICY.format(app_name=self.app_name,
191 privacy_policy=privacy_policy_link)196 privacy_policy=privacy_policy_link)
192197
193 self.terms_checkbox = enhanced_check_box.EnhancedCheckBox(terms)198 self.terms_checkbox = enhanced_check_box.EnhancedCheckBox(terms, self)
194 self.ui.hlayout_check.addWidget(self.terms_checkbox)199 self.ui.hlayout_check.addWidget(self.terms_checkbox)
195 self.terms_checkbox.setVisible(bool(self.tc_url or self.policy_url))200 self.terms_checkbox.setVisible(bool(self.tc_url or self.policy_url))
196201
@@ -220,7 +225,6 @@
220225
221 def _connect_ui(self):226 def _connect_ui(self):
222 """Set the connection of signals."""227 """Set the connection of signals."""
223 logger.debug('SetUpAccountPage._connect_ui')
224 self._set_line_edits_validations()228 self._set_line_edits_validations()
225229
226 self.ui.captcha_view.setPixmap(QtGui.QPixmap())230 self.ui.captcha_view.setPixmap(QtGui.QPixmap())
@@ -229,6 +233,7 @@
229 self.ui.password_assistance,233 self.ui.password_assistance,
230 common.NORMAL))234 common.NORMAL))
231235
236 self.ui.refresh_label.linkActivated.connect(self.hide_error)
232 self.ui.refresh_label.linkActivated.connect(lambda url: \237 self.ui.refresh_label.linkActivated.connect(lambda url: \
233 self._refresh_captcha())238 self._refresh_captcha())
234 # We need to check if we enable the button on many signals239 # We need to check if we enable the button on many signals
@@ -271,7 +276,6 @@
271 def _refresh_captcha(self):276 def _refresh_captcha(self):
272 """Refresh the captcha image shown in the ui."""277 """Refresh the captcha image shown in the ui."""
273 logger.debug('SetUpAccountPage._refresh_captcha')278 logger.debug('SetUpAccountPage._refresh_captcha')
274 self.hide_error()
275 # lets clean behind us, do we have the old file arround?279 # lets clean behind us, do we have the old file arround?
276 if self.captcha_file and os.path.exists(self.captcha_file):280 if self.captcha_file and os.path.exists(self.captcha_file):
277 os.unlink(self.captcha_file)281 os.unlink(self.captcha_file)
@@ -297,11 +301,9 @@
297 self.registerField('email_address', self.ui.email_edit)301 self.registerField('email_address', self.ui.email_edit)
298 self.registerField('password', self.ui.password_edit)302 self.registerField('password', self.ui.password_edit)
299303
304 @log_call(logger.debug)
300 def on_captcha_generated(self, app_name, result):305 def on_captcha_generated(self, app_name, result):
301 """A new image was generated."""306 """A new image was generated."""
302 logger.debug('SetUpAccountPage.on_captcha_generated for %r '
303 '(captcha id %r, filename %r).',
304 app_name, result, self.captcha_file)
305 self.captcha_id = result307 self.captcha_id = result
306 # HACK: First, let me apologize before hand, you can mention my mother308 # HACK: First, let me apologize before hand, you can mention my mother
307 # if needed I would do the same (mandel)309 # if needed I would do the same (mandel)
@@ -320,28 +322,28 @@
320 self.captcha_image = pixmap_image322 self.captcha_image = pixmap_image
321 self.on_captcha_refresh_complete()323 self.on_captcha_refresh_complete()
322324
323 def on_captcha_generation_error(self, error, *args, **kwargs):325 @log_call(logger.error)
326 def on_captcha_generation_error(self, app_name, error):
324 """An error ocurred."""327 """An error ocurred."""
325 logger.debug('SetUpAccountPage.on_captcha_generation_error')328 self.show_error(CAPTCHA_LOAD_ERROR)
326 self.show_error(self.app_name, CAPTCHA_LOAD_ERROR)
327 self.on_captcha_refresh_complete()329 self.on_captcha_refresh_complete()
328330
331 @log_call(logger.error)
329 def on_user_registration_error(self, app_name, error):332 def on_user_registration_error(self, app_name, error):
330 """Let the user know we could not register."""333 """Let the user know we could not register."""
331 logger.debug('SetUpAccountPage.on_user_registration_error')
332 # errors are returned as a dict with the data we want to show.334 # errors are returned as a dict with the data we want to show.
333 msg = error.pop(ERROR_EMAIL, '')335 msg = error.pop(ERROR_EMAIL, '')
334 if msg:336 if msg:
335 self.set_error_message(self.ui.email_assistance, msg)337 self.set_error_message(self.ui.email_assistance, msg)
336 error_msg = build_general_error_message(error)338 error_msg = build_general_error_message(error)
337 if error_msg:339 if error_msg:
338 self.show_error(self.app_name, error_msg)340 self.show_error(error_msg)
339 self._refresh_captcha()341 self._refresh_captcha()
340342
343 @log_call(logger.info)
341 def on_user_registered(self, app_name, email):344 def on_user_registered(self, app_name, email):
342 """Execute when the user did register."""345 """Execute when the user did register."""
343 self.hide_overlay()346 self.hide_overlay()
344 logger.debug('SetUpAccountPage.on_user_registered')
345 email = unicode(self.ui.email_edit.text())347 email = unicode(self.ui.email_edit.text())
346 self.userRegistered.emit(email)348 self.userRegistered.emit(email)
347349
@@ -376,7 +378,7 @@
376 messages.append(CAPTCHA_REQUIRED_ERROR)378 messages.append(CAPTCHA_REQUIRED_ERROR)
377 if len(messages) > 0:379 if len(messages) > 0:
378 condition = False380 condition = False
379 self.show_error(self.app_name, '\n'.join(messages))381 self.show_error('\n'.join(messages))
380 return condition382 return condition
381383
382 def set_next_validation(self):384 def set_next_validation(self):
@@ -401,17 +403,14 @@
401403
402 def is_correct_email(self, email_address):404 def is_correct_email(self, email_address):
403 """Return if the email is correct."""405 """Return if the email is correct."""
404 logger.debug('SetUpAccountPage.is_correct_email')
405 return '@' in email_address406 return '@' in email_address
406407
407 def is_correct_email_confirmation(self, email_address):408 def is_correct_email_confirmation(self, email_address):
408 """Return that the email is the same."""409 """Return that the email is the same."""
409 logger.debug('SetUpAccountPage.is_correct_email_confirmation')
410 return unicode(self.ui.email_edit.text()) == email_address410 return unicode(self.ui.email_edit.text()) == email_address
411411
412 def is_correct_password_confirmation(self, password):412 def is_correct_password_confirmation(self, password):
413 """Return that the passwords are correct."""413 """Return that the passwords are correct."""
414 logger.debug('SetUpAccountPage.is_correct_password_confirmation')
415 return unicode(self.ui.password_edit.text()) == password414 return unicode(self.ui.password_edit.text()) == password
416415
417 def focus_changed(self, old, now):416 def focus_changed(self, old, now):
@@ -500,12 +499,14 @@
500499
501 def on_captcha_refreshing(self):500 def on_captcha_refreshing(self):
502 """Show overlay when captcha is refreshing."""501 """Show overlay when captcha is refreshing."""
502 logger.info('SetUpAccountPage.on_captcha_refreshing')
503 if self.isVisible():503 if self.isVisible():
504 self.show_overlay()504 self.show_overlay()
505 self.captcha_received = False505 self.captcha_received = False
506506
507 def on_captcha_refresh_complete(self):507 def on_captcha_refresh_complete(self):
508 """Hide overlay when captcha finished refreshing."""508 """Hide overlay when captcha finished refreshing."""
509 logger.info('SetUpAccountPage.on_captcha_refresh_complete')
509 self.hide_overlay()510 self.hide_overlay()
510 self.captcha_received = True511 self.captcha_received = True
511512
512513
=== modified file 'ubuntu_sso/qt/sso_wizard_page.py'
--- ubuntu_sso/qt/sso_wizard_page.py 2012-03-05 20:30:57 +0000
+++ ubuntu_sso/qt/sso_wizard_page.py 2012-03-20 16:10:09 +0000
@@ -23,31 +23,38 @@
23 QApplication,23 QApplication,
24 QCursor,24 QCursor,
25 QFrame,25 QFrame,
26 QFontMetrics,
27 QHBoxLayout,26 QHBoxLayout,
27 QLabel,
28 QStyle,
28 QVBoxLayout,29 QVBoxLayout,
29 QStyle,
30 QWizardPage,30 QWizardPage,
31 QLabel,
32)31)
33from twisted.internet import defer32from twisted.internet import defer
3433
35from ubuntu_sso import main34from ubuntu_sso import main
36from ubuntu_sso.logger import setup_gui_logging35from ubuntu_sso.logger import setup_gui_logging, log_call
37from ubuntu_sso.qt import PREFERED_UI_SIZE, TITLE_STYLE36from ubuntu_sso.qt import (
37 ERROR_STYLE,
38 maybe_elide_text,
39 PREFERED_UI_SIZE,
40 TITLE_STYLE,
41)
38from ubuntu_sso.utils.ui import GENERIC_BACKEND_ERROR42from ubuntu_sso.utils.ui import GENERIC_BACKEND_ERROR
3943
4044
41logger = setup_gui_logging('ubuntu_sso.sso_wizard_page')45logger = setup_gui_logging('ubuntu_sso.sso_wizard_page')
4246
4347
44class Header(QFrame):48class WizardHeader(QFrame):
45 """Header Class for Title and Subtitle in all wizard pages."""49 """WizardHeader Class for Title and Subtitle in all wizard pages."""
4650
47 def __init__(self):51 def __init__(self, max_width, parent=None):
48 """Create a new instance."""52 """Create a new instance."""
49 super(Header, self).__init__()53 super(WizardHeader, self).__init__(parent=parent)
50 self.setObjectName('header')54 self.max_width = max_width
55 self.max_title_width = self.max_width * 0.95
56 self.max_subtitle_width = self.max_width * 1.8
57
51 vbox = QVBoxLayout(self)58 vbox = QVBoxLayout(self)
52 vbox.setContentsMargins(0, 0, 0, 0)59 vbox.setContentsMargins(0, 0, 0, 0)
53 self.title_label = QLabel()60 self.title_label = QLabel()
@@ -64,11 +71,8 @@
64 def set_title(self, title):71 def set_title(self, title):
65 """Set the Title of the page or hide it otherwise"""72 """Set the Title of the page or hide it otherwise"""
66 if title:73 if title:
67 fm = QFontMetrics(self.subtitle_label.font())74 maybe_elide_text(self.title_label, title, self.max_title_width,
68 width = PREFERED_UI_SIZE['width'] * 0.9575 markup=TITLE_STYLE)
69 elided_text = fm.elidedText(title, Qt.ElideRight, width)
70 self.title_label.setToolTip(title)
71 self.title_label.setText(elided_text)
72 self.title_label.setVisible(True)76 self.title_label.setVisible(True)
73 else:77 else:
74 self.title_label.setVisible(False)78 self.title_label.setVisible(False)
@@ -76,28 +80,23 @@
76 def set_subtitle(self, subtitle):80 def set_subtitle(self, subtitle):
77 """Set the Subtitle of the page or hide it otherwise"""81 """Set the Subtitle of the page or hide it otherwise"""
78 if subtitle:82 if subtitle:
79 fm = QFontMetrics(self.subtitle_label.font())83 maybe_elide_text(self.subtitle_label, subtitle,
80 width = PREFERED_UI_SIZE['width'] * 1.884 self.max_subtitle_width)
81 elided_text = fm.elidedText(subtitle, Qt.ElideRight, width)
82 self.subtitle_label.setText(elided_text)
83 self.subtitle_label.setToolTip(subtitle)
84 self.subtitle_label.setVisible(True)85 self.subtitle_label.setVisible(True)
85 else:86 else:
86 self.subtitle_label.setVisible(False)87 self.subtitle_label.setVisible(False)
8788
8889
89class SSOWizardPage(QWizardPage):90class BaseWizardPage(QWizardPage):
90 """Root class for all wizard pages."""91 """Base class for all wizard pages."""
9192
92 ui_class = None93 ui_class = None
93 _signals = {} # override in children94 max_width = 0
94 processingStarted = pyqtSignal()95 processingStarted = pyqtSignal()
95 processingFinished = pyqtSignal()96 processingFinished = pyqtSignal()
9697
97 def __init__(self, app_name, **kwargs):98 def __init__(self, parent=None):
98 """Create a new instance."""99 super(BaseWizardPage, self).__init__(parent=parent)
99 parent = kwargs.pop('parent', None)
100 super(SSOWizardPage, self).__init__(parent=parent)
101100
102 self.ui = None101 self.ui = None
103 if self.ui_class is not None:102 if self.ui_class is not None:
@@ -105,64 +104,39 @@
105 self.ui = self.ui_class()104 self.ui = self.ui_class()
106 self.ui.setupUi(self)105 self.ui.setupUi(self)
107106
108 # store common useful data provided by the app107 if self.layout() is None:
109 self.app_name = app_name108 self.setLayout(QVBoxLayout(self))
110 self.ping_url = kwargs.get('ping_url', '')
111 self.tc_url = kwargs.get('tc_url', '')
112 self.policy_url = kwargs.get('policy_url', '')
113 self.help_text = kwargs.get('help_text', '')
114109
115 # Set the error area110 # Set the error area
116 self.form_errors_label = QLabel(' ')111 self.form_errors_label = QLabel()
117 self.form_errors_label.setObjectName('form_errors')112 self.form_errors_label.setObjectName('form_errors')
118 self.form_errors_label.setAlignment(Qt.AlignBottom)113 self.form_errors_label.setAlignment(Qt.AlignBottom)
119 self.layout().insertWidget(0, self.form_errors_label)114 self.layout().insertWidget(0, self.form_errors_label)
115
120 # Set the header116 # Set the header
121 self.header = Header()117 self.header = WizardHeader(max_width=self.max_width)
122 self.header.set_title(title='')118 self.header.set_title(title='')
123 self.header.set_subtitle(subtitle='')119 self.header.set_subtitle(subtitle='')
124 self.layout().insertWidget(0, self.header)120 self.layout().insertWidget(0, self.header)
125 self._signals_receivers = {}
126 self.backend = None
127121
128 self.layout().setAlignment(Qt.AlignLeft)122 self.layout().setAlignment(Qt.AlignLeft)
129123
130 self.setup_page()124 self._is_processing = False
131125
132 def show_error(self, app_name, message):126 def _get_is_processing(self):
133 """Show an error message inside the page."""127 """Is this widget processing any request?"""
134 self.hide_overlay()128 return self._is_processing
135 fm = QFontMetrics(self.form_errors_label.font())129
136 width = PREFERED_UI_SIZE['width'] * 0.95130 def _set_is_processing(self, new_value):
137 elided_text = fm.elidedText(message, Qt.ElideRight, width)131 """Set this widget to be processing a request."""
138 self.form_errors_label.setText(elided_text)132 self._is_processing = new_value
139 self.form_errors_label.setToolTip(message)133 self.setEnabled(not new_value)
140134 if not self._is_processing:
141 def hide_error(self):135 self.processingFinished.emit()
142 """Hide the label errors in the current page."""136 else:
143 # We actually want the label with one chat, because if it is an137 self.processingStarted.emit()
144 # empty string, the height of the label is 0138
145 self.form_errors_label.setText(' ')139 is_processing = property(fget=_get_is_processing, fset=_set_is_processing)
146
147 def hide_overlay(self):
148 """Emit the signal to notify the upper container that ends loading."""
149 self.setEnabled(True)
150 self.processingFinished.emit()
151
152 def show_overlay(self):
153 """Emit the signal to notify the upper container that is loading."""
154 self.setEnabled(False)
155 self.processingStarted.emit()
156
157 @defer.inlineCallbacks
158 def setup_page(self):
159 """Setup the widget components."""
160 client = yield main.get_sso_client()
161 self.backend = client.sso_login
162
163 self._setup_signals()
164 self._set_translated_strings()
165 self._connect_ui()
166140
167 # pylint: disable=C0103141 # pylint: disable=C0103
168142
@@ -172,7 +146,7 @@
172146
173 def setTitle(self, title=''):147 def setTitle(self, title=''):
174 """Set the Wizard Page Title."""148 """Set the Wizard Page Title."""
175 self.header.set_title(TITLE_STYLE % title)149 self.header.set_title(title)
176150
177 def setSubTitle(self, subtitle=''):151 def setSubTitle(self, subtitle=''):
178 """Set the Wizard Page Subtitle."""152 """Set the Wizard Page Subtitle."""
@@ -188,6 +162,74 @@
188162
189 # pylint: enable=C0103163 # pylint: enable=C0103
190164
165 @log_call(logger.error)
166 def show_error(self, message):
167 """Show an error message inside the page."""
168 self.is_processing = False
169 maybe_elide_text(self.form_errors_label, message,
170 self.max_width * 0.95, markup=ERROR_STYLE)
171
172 def hide_error(self):
173 """Hide the label errors in the current page."""
174 # We actually want the label with one empty char, because if it is an
175 # empty string, the height of the label is 0
176 self.form_errors_label.setText(' ')
177
178
179class SSOWizardPage(BaseWizardPage):
180 """Root class for all SSO specific wizard pages."""
181
182 _signals = {} # override in children
183 max_width = PREFERED_UI_SIZE['width']
184
185 def __init__(self, app_name, **kwargs):
186 """Create a new instance."""
187 parent = kwargs.pop('parent', None)
188 super(SSOWizardPage, self).__init__(parent=parent)
189
190 # store common useful data provided by the app
191 self.app_name = app_name
192 self.ping_url = kwargs.get('ping_url', '')
193 self.tc_url = kwargs.get('tc_url', '')
194 self.policy_url = kwargs.get('policy_url', '')
195 self.help_text = kwargs.get('help_text', '')
196
197 self._signals_receivers = {}
198 self.backend = None
199
200 self.setup_page()
201
202 def hide_overlay(self):
203 """Emit the signal to notify the upper container that ends loading."""
204 self.is_processing = False
205
206 def show_overlay(self):
207 """Emit the signal to notify the upper container that is loading."""
208 self.is_processing = True
209
210 @defer.inlineCallbacks
211 def setup_page(self):
212 """Setup the widget components."""
213 logger.info('Starting setup_page for: %r', self)
214 # pylint: disable=W0702,W0703
215 try:
216 # Get Backend
217 client = yield main.get_sso_client()
218 self.backend = client.sso_login
219 self._set_translated_strings()
220 self._connect_ui()
221 # Call _setup_signals at the end, so we ensure that the UI
222 # is at least styled as expected if the operations with the
223 # backend fails.
224 self._setup_signals()
225 except:
226 message = 'There was a problem trying to setup the page %r' % self
227 self.show_error(message)
228 logger.exception(message)
229 self.setEnabled(False)
230 # pylint: enable=W0702,W0703
231 logger.info('%r - setup_page ends, backend is %r.', self, self.backend)
232
191 def _filter_by_app_name(self, f):233 def _filter_by_app_name(self, f):
192 """Excecute the decorated function only for 'self.app_name'."""234 """Excecute the decorated function only for 'self.app_name'."""
193235
@@ -218,11 +260,9 @@
218260
219 def _set_translated_strings(self):261 def _set_translated_strings(self):
220 """Implement in each child."""262 """Implement in each child."""
221 raise NotImplementedError()
222263
223 def _connect_ui(self):264 def _connect_ui(self):
224 """Implement in each child."""265 """Implement in each child."""
225 raise NotImplementedError()
226266
227 def _handle_error(self, remote_call, handler, error):267 def _handle_error(self, remote_call, handler, error):
228 """Handle any error when calling the remote backend."""268 """Handle any error when calling the remote backend."""
229269
=== modified file 'ubuntu_sso/qt/tests/__init__.py'
--- ubuntu_sso/qt/tests/__init__.py 2012-03-05 20:30:57 +0000
+++ ubuntu_sso/qt/tests/__init__.py 2012-03-20 16:10:09 +0000
@@ -21,7 +21,7 @@
21from twisted.trial.unittest import TestCase21from twisted.trial.unittest import TestCase
2222
23from ubuntu_sso import main, NO_OP23from ubuntu_sso import main, NO_OP
24from ubuntu_sso.qt import TITLE_STYLE24from ubuntu_sso.qt import ERROR_STYLE, maybe_elide_text, TITLE_STYLE
25from ubuntu_sso.tests import (25from ubuntu_sso.tests import (
26 APP_NAME,26 APP_NAME,
27 HELP_TEXT,27 HELP_TEXT,
@@ -38,6 +38,15 @@
38# pylint: disable=W021238# pylint: disable=W0212
3939
4040
41def build_string_for_pixels(label, width):
42 """Return a random string that will be as big as with in pixels."""
43 char = 'a'
44 fm = QtGui.QFontMetrics(label.font())
45 pixel_width = fm.width(char)
46 chars = int(width / pixel_width)
47 return char * chars
48
49
41class FakedObject(object):50class FakedObject(object):
42 """Fake an object, record every call."""51 """Fake an object, record every call."""
4352
@@ -439,6 +448,9 @@
439 self.wizard = self.ui_wizard_class()448 self.wizard = self.ui_wizard_class()
440 self.patch(self.ui, 'wizard', lambda: self.wizard)449 self.patch(self.ui, 'wizard', lambda: self.wizard)
441450
451 self.ui.show()
452 self.addCleanup(self.ui.hide)
453
442 def _set_called(self, *args, **kwargs):454 def _set_called(self, *args, **kwargs):
443 """Store 'args' and 'kwargs' for test assertions."""455 """Store 'args' and 'kwargs' for test assertions."""
444 self._called = (args, kwargs)456 self._called = (args, kwargs)
@@ -467,6 +479,38 @@
467479
468 self.assertEqual(self.signal_results, [signal_args])480 self.assertEqual(self.signal_results, [signal_args])
469481
482 def assert_title_correct(self, title_label, expected, max_width):
483 """Check that the label's text is equal to 'expected'."""
484 label = QtGui.QLabel()
485 maybe_elide_text(label, expected, max_width)
486
487 self.assertEqual(TITLE_STYLE % unicode(label.text()),
488 unicode(title_label.text()))
489 self.assertEqual(unicode(label.toolTip()),
490 unicode(title_label.toolTip()))
491 self.assertTrue(title_label.isVisible())
492
493 def assert_subtitle_correct(self, subtitle_label, expected, max_width):
494 """Check that the subtitle is equal to 'expected'."""
495 label = QtGui.QLabel()
496 maybe_elide_text(label, expected, max_width)
497
498 self.assertEqual(unicode(label.text()), unicode(subtitle_label.text()))
499 self.assertEqual(unicode(label.toolTip()),
500 unicode(subtitle_label.toolTip()))
501 self.assertTrue(subtitle_label.isVisible())
502
503 def assert_error_correct(self, error_label, expected, max_width):
504 """Check that the error 'error_label' displays 'expected' as text."""
505 label = QtGui.QLabel()
506 maybe_elide_text(label, expected, max_width)
507
508 self.assertEqual(ERROR_STYLE % unicode(label.text()),
509 unicode(error_label.text()))
510 self.assertEqual(unicode(label.toolTip()),
511 unicode(error_label.toolTip()))
512 self.assertTrue(error_label.isVisible())
513
470 def get_pixmap_data(self, pixmap):514 def get_pixmap_data(self, pixmap):
471 """Get the raw data of a QPixmap."""515 """Get the raw data of a QPixmap."""
472 byte_array = QtCore.QByteArray()516 byte_array = QtCore.QByteArray()
@@ -507,7 +551,7 @@
507 self.assertIn(signal, self.ui._signals)551 self.assertIn(signal, self.ui._signals)
508 self.assertTrue(callable(self.ui._signals[signal]))552 self.assertTrue(callable(self.ui._signals[signal]))
509553
510 expected = ['_setup_signals', '_set_translated_strings', '_connect_ui']554 expected = ['_set_translated_strings', '_connect_ui', '_setup_signals']
511 self.assertEqual(expected, called)555 self.assertEqual(expected, called)
512556
513557
@@ -550,14 +594,18 @@
550 self.assertEqual(self._overlay_hide_counter, 1)594 self.assertEqual(self._overlay_hide_counter, 1)
551 self.assertTrue(self.ui.isEnabled())595 self.assertTrue(self.ui.isEnabled())
552596
597 # pylint: disable=W0221
598
553 def assert_title_correct(self, expected):599 def assert_title_correct(self, expected):
554 """Check that the title is equal to 'expected'."""600 """Check that the title is equal to 'expected'."""
555 self.assertEqual(TITLE_STYLE % expected, unicode(self.ui.title()))601 check = super(PageBaseTestCase, self).assert_title_correct
602 check(self.ui.header.title_label, expected,
603 self.ui.header.max_title_width)
556604
557 def assert_subtitle_correct(self, expected):605 def assert_subtitle_correct(self, expected):
558 """Check that the subtitle is equal to 'expected'."""606 """Check that the subtitle is equal to 'expected'."""
559 elided_text = unicode(self.ui.subTitle())607 check = super(PageBaseTestCase, self).assert_subtitle_correct
560 elided_text = elided_text[:len(elided_text) - 1]608 check(self.ui.header.subtitle_label, expected,
609 self.ui.header.max_subtitle_width)
561610
562 self.assertTrue(expected.startswith(elided_text))611 # pylint: enable=W0221
563 self.assertEqual(self.ui.header.subtitle_label.toolTip(), expected)
564612
=== modified file 'ubuntu_sso/qt/tests/login_u_p.py'
--- ubuntu_sso/qt/tests/login_u_p.py 2012-01-26 15:34:16 +0000
+++ ubuntu_sso/qt/tests/login_u_p.py 2012-03-20 16:10:09 +0000
@@ -39,7 +39,6 @@
3939
40 def found(*args):40 def found(*args):
41 """The result was received."""41 """The result was received."""
42 print "result received", args
43 d.callback(args)42 d.callback(args)
4443
45 client.cred_manager.connect_to_signal('CredentialsFound', found)44 client.cred_manager.connect_to_signal('CredentialsFound', found)
@@ -61,7 +60,6 @@
61 yield cleared60 yield cleared
6261
63 yield client.cred_manager.login_email_password('SUPER', args)62 yield client.cred_manager.login_email_password('SUPER', args)
64 print "called ok"
65 yield d63 yield d
6664
67 yield client.disconnect()65 yield client.disconnect()
6866
=== modified file 'ubuntu_sso/qt/tests/show_gui.py'
--- ubuntu_sso/qt/tests/show_gui.py 2012-02-10 17:18:22 +0000
+++ ubuntu_sso/qt/tests/show_gui.py 2012-03-20 16:10:09 +0000
@@ -46,7 +46,6 @@
4646
47 def found(*args):47 def found(*args):
48 """The result was received."""48 """The result was received."""
49 print "result received", args
50 d.callback(args)49 d.callback(args)
5150
52 client.cred_manager.connect_to_signal('CredentialsFound', found)51 client.cred_manager.connect_to_signal('CredentialsFound', found)
@@ -62,7 +61,6 @@
62 TC_URL_KEY: u'http://www.google.com/',61 TC_URL_KEY: u'http://www.google.com/',
63 UI_EXECUTABLE_KEY: 'ubuntu-sso-login-gtk',62 UI_EXECUTABLE_KEY: 'ubuntu-sso-login-gtk',
64 })63 })
65 print "called ok"
66 yield d64 yield d
6765
68 yield client.disconnect()66 yield client.disconnect()
6967
=== modified file 'ubuntu_sso/qt/tests/test_common.py'
--- ubuntu_sso/qt/tests/test_common.py 2011-10-28 10:41:18 +0000
+++ ubuntu_sso/qt/tests/test_common.py 2012-03-20 16:10:09 +0000
@@ -20,7 +20,13 @@
20from twisted.internet import defer20from twisted.internet import defer
21from twisted.trial.unittest import TestCase21from twisted.trial.unittest import TestCase
2222
23from ubuntu_sso.qt.common import (check_as_invalid,23from ubuntu_sso.qt import (
24 build_general_error_message,
25 maybe_elide_text,
26 GENERIC_BACKEND_ERROR,
27)
28from ubuntu_sso.qt.common import (
29 check_as_invalid,
24 check_as_valid,30 check_as_valid,
25 password_assistance,31 password_assistance,
26 password_check_match,32 password_check_match,
@@ -30,7 +36,9 @@
30 PASSWORD_DIGIT,36 PASSWORD_DIGIT,
31 PASSWORD_LENGTH,37 PASSWORD_LENGTH,
32 PASSWORD_MATCH,38 PASSWORD_MATCH,
33 PASSWORD_UPPER)39 PASSWORD_UPPER,
40)
41from ubuntu_sso.qt.tests import build_string_for_pixels
3442
3543
36class PasswordTestCase(TestCase):44class PasswordTestCase(TestCase):
@@ -234,3 +242,123 @@
234 line_edit = QtGui.QLineEdit()242 line_edit = QtGui.QLineEdit()
235 check_as_invalid(line_edit)243 check_as_invalid(line_edit)
236 self.assertTrue(line_edit.property("formError").toBool())244 self.assertTrue(line_edit.property("formError").toBool())
245
246
247class ElidedTextTestCase(TestCase):
248 """The test case for the maybe_elide_text function."""
249
250 max_width = 100
251
252 @defer.inlineCallbacks
253 def setUp(self):
254 """Setup tests."""
255 yield super(ElidedTextTestCase, self).setUp()
256 self.ui = QtGui.QLabel()
257
258 def test_text_not_elided_if_too_short(self):
259 """If text is shorter than max_width, do not elide."""
260 text = build_string_for_pixels(self.ui, self.max_width - 1)
261
262 maybe_elide_text(self.ui, text, self.max_width)
263
264 self.assertEqual(self.ui.toolTip(), '')
265 self.assertEqual(self.ui.text(), text)
266 self.assertNotIn(u'\u2026', self.ui.text())
267
268 def test_text_not_elided_if_equals_max_width(self):
269 """If text is equal than max_width, do not elide."""
270 text = build_string_for_pixels(self.ui, self.max_width)
271
272 maybe_elide_text(self.ui, text, self.max_width)
273
274 self.assertEqual(self.ui.toolTip(), '')
275 self.assertEqual(self.ui.text(), text)
276 self.assertNotIn(u'\u2026', self.ui.text())
277
278 def test_text_elided_if_bigger_than_max_width(self):
279 """If text is equal than max_width, do not elide."""
280 text = build_string_for_pixels(self.ui, self.max_width + 10)
281
282 maybe_elide_text(self.ui, text, self.max_width)
283
284 self.assertEqual(self.ui.toolTip(), text)
285 expected = unicode(self.ui.text())
286 self.assertTrue(expected.endswith(u'\u2026'))
287 self.assertTrue(text.startswith(expected[:-1]))
288
289
290class BuildGeneralErrorMessageTestCase(TestCase):
291 """Test passwords conditions."""
292
293 def test_with_message(self):
294 """Test build_general_error_message with 'message' key."""
295 error = "error message"
296 err_dict = {'message': error}
297
298 result = build_general_error_message(err_dict)
299
300 self.assertEqual(result, error)
301
302 def test_with_all(self):
303 """Test build_general_error_message with 'all' key."""
304 error = "error message"
305 err_dict = {'__all__': error}
306
307 result = build_general_error_message(err_dict)
308
309 self.assertEqual(result, error)
310
311 def test_with_message_and_all(self):
312 """Test build_general_error_message with 'all' and 'message' key."""
313 error = "error message"
314 error2 = "error message2"
315 err_dict = {'__all__': error, 'message': error2}
316
317 result = build_general_error_message(err_dict)
318
319 expected = '\n'.join((error, error2))
320 self.assertEqual(result, expected)
321
322 def test_with_all_and_error_message(self):
323 """Test for 'all' and 'error_message' key."""
324 error = "error message"
325 error2 = "error message2"
326 err_dict = {'__all__': error, 'error_message': error2}
327 result = build_general_error_message(err_dict)
328 expected = '\n'.join((error, error2))
329 self.assertEqual(result, expected)
330
331 def test_with_random_keys(self):
332 """Test build_general_error_message with random keys."""
333 error = "error message"
334 error2 = "error message2"
335 err_dict = {'my_bad': error, 'odd_error': error2}
336
337 result = build_general_error_message(err_dict)
338
339 expected = '\n'.join(
340 [('%s: %s' % (k, v)) for k, v in err_dict.iteritems()])
341 self.assertEqual(result, expected)
342
343 def test_with_random_keys_with_errtype(self):
344 """Test build_general_error_message with random keys and errtype."""
345 error = "error message"
346 error2 = "error message2"
347 err_dict = {'my_bad': error, 'odd_error': error2, 'errtype': 'Danger'}
348
349 result = build_general_error_message(err_dict)
350
351 expected = '\n'.join(
352 [('%s: %s' % (k, v)) \
353 for k, v in {'my_bad': error, 'odd_error': error2}.iteritems()])
354 self.assertEqual(result, expected)
355
356 def test_with_not_dict(self):
357 """Test build_general_error_message with argument not dict."""
358 error = "error message"
359 err_dict = Exception(error)
360
361 result = build_general_error_message(err_dict)
362
363 expected = GENERIC_BACKEND_ERROR
364 self.assertEqual(result, expected)
237365
=== modified file 'ubuntu_sso/qt/tests/test_current_user_sign_in_page.py'
--- ubuntu_sso/qt/tests/test_current_user_sign_in_page.py 2012-03-05 20:30:57 +0000
+++ ubuntu_sso/qt/tests/test_current_user_sign_in_page.py 2012-03-20 16:10:09 +0000
@@ -24,6 +24,7 @@
24 FakePageUiStyle,24 FakePageUiStyle,
25 FakeWizardButtonStyle,25 FakeWizardButtonStyle,
26)26)
27from ubuntu_sso.tests import EMAIL
2728
2829
29# pylint: disable=W021230# pylint: disable=W0212
@@ -51,19 +52,32 @@
51 self.assertTrue(button.properties['default'])52 self.assertTrue(button.properties['default'])
52 self.assertFalse(button.isEnabled())53 self.assertFalse(button.isEnabled())
5354
55 def test_unicode_in_forgotten_password_link(self):
56 """Ensure that this label supports unicode."""
57 forgot_fr = u"J'ai oublié mon mot de passe"
58 self.patch(gui, "FORGOTTEN_PASSWORD_BUTTON", forgot_fr)
59 forgotten_text = gui.LINK_STYLE.format(link_url='#',
60 link_text=forgot_fr)
61 self.ui._set_translated_strings()
62 self.assertEqual(unicode(self.ui.ui.forgot_password_label.text()),
63 forgotten_text)
64
54 def test_set_translated_strings(self):65 def test_set_translated_strings(self):
55 """Test the translated string method."""66 """Test the translated string method."""
56 expected = gui.LOGIN_TITLE.format(app_name=self.app_name)67 expected = gui.LOGIN_TITLE.format(app_name=self.app_name)
57 self.assert_title_correct(expected)68 self.assert_title_correct(expected)
58 expected = gui.LOGIN_SUBTITLE % dict(app_name=self.app_name)69 expected = gui.LOGIN_SUBTITLE % dict(app_name=self.app_name)
59 self.assert_subtitle_correct(expected)70 self.assert_subtitle_correct(expected)
60 self.assertEqual(self.ui.ui.email_label.text(), gui.EMAIL_LABEL)71 self.assertEqual(unicode(self.ui.ui.email_label.text()),
61 self.assertEqual(self.ui.ui.password_label.text(),72 gui.EMAIL_LABEL)
73 self.assertEqual(unicode(self.ui.ui.password_label.text()),
62 gui.LOGIN_PASSWORD_LABEL)74 gui.LOGIN_PASSWORD_LABEL)
63 text = gui.LINK_STYLE.format(link_url='#',75 text = gui.LINK_STYLE.format(link_url='#',
64 link_text=gui.FORGOTTEN_PASSWORD_BUTTON)76 link_text=gui.FORGOTTEN_PASSWORD_BUTTON)
65 self.assertEqual(self.ui.ui.forgot_password_label.text(), text)77 self.assertEqual(unicode(self.ui.ui.forgot_password_label.text()),
66 self.assertEqual(self.ui.ui.sign_in_button.text(), gui.SIGN_IN_BUTTON)78 text)
79 self.assertEqual(unicode(self.ui.ui.sign_in_button.text()),
80 gui.SIGN_IN_BUTTON)
6781
68 def test_connect_ui(self):82 def test_connect_ui(self):
69 """Test the connect ui method."""83 """Test the connect ui method."""
@@ -128,30 +142,27 @@
128 def test_on_login_error(self):142 def test_on_login_error(self):
129 """Test the on_login_error method."""143 """Test the on_login_error method."""
130 self.patch(self.ui, "show_error", self._set_called)144 self.patch(self.ui, "show_error", self._set_called)
131 app_name = 'my_app'
132 self.ui.app_name = app_name
133 error = {'errtype': 'UserNotValidated'}145 error = {'errtype': 'UserNotValidated'}
134 self.ui.on_login_error(app_name, error)146
147 self.ui.on_login_error(self.app_name, error)
148
135 self.assertEqual(self._overlay_hide_counter, 0)149 self.assertEqual(self._overlay_hide_counter, 0)
136 self.assertTrue(self.ui.isEnabled())150 self.assertTrue(self.ui.isEnabled())
137 expected = ((self.ui, 'my_app', ''), {})151 expected = ((self.ui, self.app_name, ''), {})
138 self.assertTrue(expected, self._called)152 self.assertTrue(expected, self._called)
139153
140 def test_on_logged_in(self):154 def test_on_logged_in(self):
141 """Test the on_login_in method."""155 """Test the on_login_in method."""
142 email = 'email@example'156 self.ui.ui.email_edit.setText(EMAIL)
143 self.ui.ui.email_edit.setText(email)157 self.assert_signal_emitted(self.ui.userLoggedIn, (EMAIL,),
144158 self.ui.on_logged_in, self.app_name, EMAIL)
145 self.assert_signal_emitted(self.ui.userLoggedIn, (email,),
146 self.ui.on_logged_in, self.app_name, email)
147 self.assertTrue(self.ui.isEnabled())159 self.assertTrue(self.ui.isEnabled())
148160
149 def test_on_user_not_validated(self):161 def test_on_user_not_validated(self):
150 """Test the navigation flow on user not validated."""162 """Test the navigation flow on user not validated."""
151 email = 'email@example'163 self.ui.ui.email_edit.setText(EMAIL)
152 self.ui.ui.email_edit.setText(email)164 self.assert_signal_emitted(self.ui.userNotValidated, (EMAIL,),
153 self.assert_signal_emitted(self.ui.userNotValidated, (email,),165 self.ui.on_user_not_validated, self.app_name, EMAIL)
154 self.ui.on_user_not_validated, self.app_name, email)
155166
156 def test_on_forgotten_password(self):167 def test_on_forgotten_password(self):
157 """Test the on_forgotten_password method."""168 """Test the on_forgotten_password method."""
158169
=== modified file 'ubuntu_sso/qt/tests/test_email_verification.py'
--- ubuntu_sso/qt/tests/test_email_verification.py 2012-03-05 18:56:50 +0000
+++ ubuntu_sso/qt/tests/test_email_verification.py 2012-03-20 16:10:09 +0000
@@ -78,14 +78,12 @@
78 email = 'mail@example'78 email = 'mail@example'
79 self.ui.email = email79 self.ui.email = email
80 self.ui.set_titles(email)80 self.ui.set_titles(email)
81 self.assertEqual(self.ui.header.title_label.text(),81 self.assert_title_correct(email_verification_page.VERIFY_EMAIL_TITLE)
82 email_verification_page.VERIFY_EMAIL_TITLE)
83 expected = email_verification_page.VERIFY_EMAIL_CONTENT % {82 expected = email_verification_page.VERIFY_EMAIL_CONTENT % {
84 "app_name": self.app_name,83 "app_name": self.app_name,
85 "email": email,84 "email": email,
86 }85 }
87 self.assertEqual(unicode(self.ui.header.subtitle_label.toolTip()),86 self.assert_subtitle_correct(expected)
88 expected)
8987
90 def test_initialize_page(self):88 def test_initialize_page(self):
91 """Test the initialization method."""89 """Test the initialization method."""
@@ -100,10 +98,11 @@
100 def test_on_email_validation_error(self):98 def test_on_email_validation_error(self):
101 """Test the validate_email method."""99 """Test the validate_email method."""
102 self.patch(self.ui, "show_error", self._set_called)100 self.patch(self.ui, "show_error", self._set_called)
103 app_name = 'my_app'
104 error = {email_verification_page: 'error in email_verification_page'}101 error = {email_verification_page: 'error in email_verification_page'}
105 self.ui.on_email_validation_error(app_name, error)102
106 expected = ((self.ui, app_name, ''), {})103 self.ui.on_email_validation_error(self.app_name, error)
104
105 expected = ((self.ui, ''), {})
107 self.assertTrue(expected, self._called)106 self.assertTrue(expected, self._called)
108 self.assertEqual(self._overlay_hide_counter, 1)107 self.assertEqual(self._overlay_hide_counter, 1)
109108
110109
=== modified file 'ubuntu_sso/qt/tests/test_enhanced_check_box.py'
--- ubuntu_sso/qt/tests/test_enhanced_check_box.py 2012-02-16 18:40:41 +0000
+++ ubuntu_sso/qt/tests/test_enhanced_check_box.py 2012-03-20 16:10:09 +0000
@@ -16,7 +16,7 @@
1616
17"""Tests for the EnhancedCheckBox widget."""17"""Tests for the EnhancedCheckBox widget."""
1818
19from PyQt4 import QtCore19from PyQt4 import QtGui, QtCore
2020
21from ubuntu_sso.qt import enhanced_check_box21from ubuntu_sso.qt import enhanced_check_box
22from ubuntu_sso.qt.tests import BaseTestCase22from ubuntu_sso.qt.tests import BaseTestCase
@@ -40,3 +40,21 @@
40 check.setText("text")40 check.setText("text")
41 self.assertEqual(check.text(), "text")41 self.assertEqual(check.text(), "text")
42 self.assertEqual(check.text(), check.text_label.text())42 self.assertEqual(check.text(), check.text_label.text())
43
44 def test_enhanced_check_size_adjust_with_small_height(self):
45 """Check if the size of the EnhancedCheckBox is adjusted correctly."""
46 text = 't' * 200
47 height = 10
48 widget = QtGui.QWidget()
49 widget.setFixedSize(200, height)
50 check = enhanced_check_box.EnhancedCheckBox(text, widget)
51 self.assertTrue(check.height() > height)
52
53 def test_enhanced_check_size_adjust_with_big_height(self):
54 """Check if the size of the EnhancedCheckBox is adjusted correctly."""
55 text = 't' * 20
56 height = 200
57 widget = QtGui.QWidget()
58 widget.setFixedSize(200, height)
59 check = enhanced_check_box.EnhancedCheckBox(text, widget)
60 self.assertTrue(check.height() < height)
4361
=== modified file 'ubuntu_sso/qt/tests/test_forgotten_password.py'
--- ubuntu_sso/qt/tests/test_forgotten_password.py 2012-03-05 20:30:57 +0000
+++ ubuntu_sso/qt/tests/test_forgotten_password.py 2012-03-20 16:10:09 +0000
@@ -75,9 +75,10 @@
75 subtitle = gui.FORGOTTEN_PASSWORD_SUBTITLE75 subtitle = gui.FORGOTTEN_PASSWORD_SUBTITLE
76 self.assert_subtitle_correct(subtitle.format(app_name=self.app_name))76 self.assert_subtitle_correct(subtitle.format(app_name=self.app_name))
7777
78 self.assertEqual(self.ui.ui.email_address_label.text(),78 self.assertEqual(unicode(self.ui.ui.email_address_label.text()),
79 gui.EMAIL_LABEL)79 gui.EMAIL_LABEL)
80 self.assertEqual(self.ui.ui.send_button.text(), gui.RESET_PASSWORD)80 self.assertEqual(unicode(self.ui.ui.send_button.text()),
81 gui.RESET_PASSWORD)
8182
82 def test_connect_ui(self):83 def test_connect_ui(self):
83 """Test the connect ui method."""84 """Test the connect ui method."""
@@ -112,7 +113,8 @@
112 """Test on_password_reset_error method."""113 """Test on_password_reset_error method."""
113 self.patch(self.ui, "show_error", self._set_called)114 self.patch(self.ui, "show_error", self._set_called)
114 error = {'errtype': 'FooBarBaz'}115 error = {'errtype': 'FooBarBaz'}
116
115 self.ui.on_password_reset_error(self.app_name, error)117 self.ui.on_password_reset_error(self.app_name, error)
116 expected = ((self.ui, self.app_name,118
117 gui.REQUEST_PASSWORD_TOKEN_WRONG_EMAIL), {})119 expected = ((self.ui, gui.REQUEST_PASSWORD_TOKEN_WRONG_EMAIL), {})
118 self.assertTrue(expected, self._called)120 self.assertTrue(expected, self._called)
119121
=== modified file 'ubuntu_sso/qt/tests/test_loadingoverlay.py'
--- ubuntu_sso/qt/tests/test_loadingoverlay.py 2012-02-23 19:49:02 +0000
+++ ubuntu_sso/qt/tests/test_loadingoverlay.py 2012-03-20 16:10:09 +0000
@@ -29,8 +29,5 @@
2929
30 def test_status_correct(self):30 def test_status_correct(self):
31 """Test if the necessary variables for the animation exists"""31 """Test if the necessary variables for the animation exists"""
32 self.ui.show()
33 self.addCleanup(self.ui.hide)
34
35 self.assertTrue(self.ui.counter is not None)32 self.assertTrue(self.ui.counter is not None)
36 self.assertTrue(self.ui.orientation is not None)33 self.assertTrue(self.ui.orientation is not None)
3734
=== modified file 'ubuntu_sso/qt/tests/test_main.py'
--- ubuntu_sso/qt/tests/test_main.py 2012-02-13 15:43:59 +0000
+++ ubuntu_sso/qt/tests/test_main.py 2012-03-20 16:10:09 +0000
@@ -16,27 +16,123 @@
1616
17"""Tests for the main module."""17"""Tests for the main module."""
1818
19from PyQt4 import QtCore
20from twisted.internet import defer
19from twisted.trial.unittest import TestCase21from twisted.trial.unittest import TestCase
2022
21from ubuntu_sso.qt import main23from ubuntu_sso.qt import main
24from ubuntu_sso import tests
25
26
27# pylint: disable=C0103
28class FakeUi(object):
29
30 """A fake UI."""
31
32 def size(self):
33 """Fake size."""
34 return QtCore.QSize(100, 100)
35
36 def setGeometry(self, *args):
37 """Fake setGeometry."""
38
39 show = setGeometry
40
41
42class FakeDesktop(object):
43
44 """Fake Desktop Widget."""
45
46 def availableGeometry(self):
47 """Fake availableGeometry for desktop.-"""
48 return QtCore.QRect(100, 100, 100, 100)
49
50
51class FakeApplication(object):
52
53 """Fake QApplication."""
54
55 called = {}
56 __instance = None
57
58 def __init__(self, args):
59 self.called['args'] = args
60 FakeApplication.__instance = self
61
62 def setStyleSheet(self, style):
63 """Fake setStyleSheet."""
64 self.called["setStyleSheet"] = style
65
66 def styleSheet(self):
67 """Fake get style sheet."""
68 return self.called.get("setStyleSheet", '')
69
70 def desktop(self):
71 """Fake Desktop."""
72 return FakeDesktop()
73
74 def exec_(self):
75 """Fake exec_."""
76
77 def exit(self):
78 """Fake exit."""
79
80 @classmethod
81 def instance(cls):
82 """Fake instance."""
83 return FakeApplication.__instance
84# pylint: enable=C0103
2285
2386
24class BasicTestCase(TestCase):87class BasicTestCase(TestCase):
25 """Test case with a helper tracker."""88 """Test case with a helper tracker."""
2689
90 @defer.inlineCallbacks
91 def setUp(self):
92 yield super(BasicTestCase, self).setUp()
93 self.called = []
94
95 def called_ui(**kw):
96 """record ui call."""
97 self.called.append(('GUI', kw))
98 return FakeUi()
99
100 self.patch(main, 'UbuntuSSOClientGUI', called_ui)
101 self.patch(main.QtGui, 'QApplication', FakeApplication)
102
27 def test_main(self):103 def test_main(self):
28 """Calling main.main() a UI instance is created."""104 """Calling main.main() a UI instance is created."""
29 called = []105 kwargs = dict(app_name='APP_NAME', foo='foo', bar='bar',
30 self.patch(main, 'UbuntuSSOClientGUI',106 baz='yadda', yadda=0)
31 lambda **kw: called.append(('GUI', kw)))107 main.main(**kwargs)
32 self.patch(main.QtGui.QApplication, 'exec_',108
33 lambda *a: called.append('main'))109 kwargs['close_callback'] = main.QtGui.QApplication.instance().exit
34110 self.assertEqual(self.called, [('GUI', kwargs)])
111
112 def test_main_args(self):
113 """Calling main.main() a UI instance is created."""
114 arg_list = (tests.APP_NAME, tests.NAME, tests.PASSWORD,
115 tests.EMAIL_TOKEN)
116 kwargs = dict(app_name=arg_list[0].encode('utf-8'),
117 foo=arg_list[1].encode('utf-8'),
118 bar=arg_list[2].encode('utf-8'),
119 baz=arg_list[3].encode('utf-8'))
120 main.main(**kwargs)
121
122 kwargs['close_callback'] = main.QtGui.QApplication.instance().exit
123 expected = dict(app_name=arg_list[0], foo=arg_list[1],
124 bar=arg_list[2], baz=arg_list[3],
125 close_callback=main.QtGui.QApplication.instance().exit)
126 self.assertEqual(self.called, [('GUI', expected)])
127
128 def test_styles_load(self):
129 """Test that all stylesheets load."""
35 kwargs = dict(foo='foo', bar='bar', baz='yadda', yadda=0)130 kwargs = dict(foo='foo', bar='bar', baz='yadda', yadda=0)
36 main.main(**kwargs)131 main.main(**kwargs)
37132 data = []
38 kwargs['close_callback'] = main.QtGui.QApplication.instance().exit133 for qss_name in (main.PLATFORM_QSS, ":/stylesheet.qss"):
39 self.assertEqual(called, [('GUI', kwargs), 'main'])134 qss = QtCore.QResource(qss_name)
40135 data.append(unicode(qss.data()))
41 test_main.skip = 'Failing with QObject::startTimer: QTimer can only be ' \136 self.assertEqual(
42 'used with threads started with QThread'137 unicode(main.QtGui.QApplication.instance().styleSheet()),
138 '\n'.join(data))
43139
=== modified file 'ubuntu_sso/qt/tests/test_proxy_dialog.py'
--- ubuntu_sso/qt/tests/test_proxy_dialog.py 2012-02-13 16:16:03 +0000
+++ ubuntu_sso/qt/tests/test_proxy_dialog.py 2012-03-20 16:10:09 +0000
@@ -256,7 +256,7 @@
256 self.patch(dialog, 'done', fake_done)256 self.patch(dialog, 'done', fake_done)
257257
258 dialog._on_cancel_clicked()258 dialog._on_cancel_clicked()
259 self.assertIn(('done', proxy_dialog.USER_CANCELATION), called)259 self.assertIn(('done', proxy_dialog.USER_CANCELLATION), called)
260260
261 def assert_save_button(self, set_creds_callback, result_number):261 def assert_save_button(self, set_creds_callback, result_number):
262 """Test the save button execution."""262 """Test the save button execution."""
@@ -288,7 +288,7 @@
288 def test_on_save_clicked_correct(self):288 def test_on_save_clicked_correct(self):
289 """Test that we do save the creds."""289 """Test that we do save the creds."""
290 set_creds_cb = lambda: defer.succeed(True)290 set_creds_cb = lambda: defer.succeed(True)
291 result_number = proxy_dialog.CREDS_ACQUIRED291 result_number = proxy_dialog.USER_SUCCESS
292 self.assert_save_button(set_creds_cb, result_number)292 self.assert_save_button(set_creds_cb, result_number)
293293
294294
295295
=== modified file 'ubuntu_sso/qt/tests/test_reset_password.py'
--- ubuntu_sso/qt/tests/test_reset_password.py 2012-03-05 18:56:50 +0000
+++ ubuntu_sso/qt/tests/test_reset_password.py 2012-03-20 16:10:09 +0000
@@ -70,27 +70,23 @@
7070
71 def test_initialize(self):71 def test_initialize(self):
72 """Check the Title and Subtitle."""72 """Check the Title and Subtitle."""
73 self.ui.show()
74 self.ui.initializePage()73 self.ui.initializePage()
75 self.addCleanup(self.ui.hide)
76 self.assert_title_correct(RESET_TITLE)74 self.assert_title_correct(RESET_TITLE)
77 self.assert_subtitle_correct(RESET_SUBTITLE)75 self.assert_subtitle_correct(RESET_SUBTITLE)
78 self.assertEqual(self.ui.ui.password_label.text(), PASSWORD1_ENTRY)76 self.assertEqual(unicode(self.ui.ui.password_label.text()),
79 self.assertEqual(self.ui.ui.confirm_password_label.text(),77 PASSWORD1_ENTRY)
78 self.assertEqual(unicode(self.ui.ui.confirm_password_label.text()),
80 PASSWORD2_ENTRY)79 PASSWORD2_ENTRY)
81 self.assertEqual(self.ui.ui.reset_code.text(), RESET_CODE_ENTRY)80 self.assertEqual(unicode(self.ui.ui.reset_code.text()),
81 RESET_CODE_ENTRY)
8282
83 def test_focus_changed_password_visibility(self):83 def test_focus_changed_password_visibility(self):
84 """Check visibility changes when focus_changed() is executed."""84 """Check visibility changes when focus_changed() is executed."""
85 self.ui.show()
86 self.addCleanup(self.ui.hide)
87 self.ui.focus_changed(None, self.ui.ui.password_line_edit)85 self.ui.focus_changed(None, self.ui.ui.password_line_edit)
88 self.assertTrue(self.ui.ui.password_assistance.isVisible())86 self.assertTrue(self.ui.ui.password_assistance.isVisible())
8987
90 def test_show_hide_event(self):88 def test_show_hide_event(self):
91 """Check connections to focusChanged on show and hide event."""89 """Check connections to focusChanged on show and hide event."""
92 self.ui.show()
93 self.addCleanup(self.ui.hide)
94 self.assertEqual(QtGui.QApplication.instance().receivers(90 self.assertEqual(QtGui.QApplication.instance().receivers(
95 QtCore.SIGNAL("focusChanged(QWidget*, QWidget*)")), 1)91 QtCore.SIGNAL("focusChanged(QWidget*, QWidget*)")), 1)
96 self.ui.hide()92 self.ui.hide()
@@ -102,24 +98,20 @@
102 def test_focus_changed_1(self):98 def test_focus_changed_1(self):
103 """Check functions execution when focus_changed() is executed."""99 """Check functions execution when focus_changed() is executed."""
104 self.patch(common, 'password_default_assistance', self._set_called)100 self.patch(common, 'password_default_assistance', self._set_called)
105
106 self.ui.show()
107 self.addCleanup(self.ui.hide)
108
109 self.assertFalse(self._called)101 self.assertFalse(self._called)
102
110 self.ui.focus_changed(None, self.ui.ui.password_line_edit)103 self.ui.focus_changed(None, self.ui.ui.password_line_edit)
104
111 self.assertTrue(self.ui.ui.password_assistance.isVisible())105 self.assertTrue(self.ui.ui.password_assistance.isVisible())
112 self.assertTrue(self._called)106 self.assertTrue(self._called)
113107
114 def test_focus_changed_2(self):108 def test_focus_changed_2(self):
115 """Check functions execution when focus_changed() is executed."""109 """Check functions execution when focus_changed() is executed."""
116 self.patch(common, 'password_check_match', self._set_called)110 self.patch(common, 'password_check_match', self._set_called)
117
118 self.ui.show()
119 self.addCleanup(self.ui.hide)
120
121 self.assertFalse(self._called)111 self.assertFalse(self._called)
112
122 self.ui.focus_changed(None, self.ui.ui.confirm_password_line_edit)113 self.ui.focus_changed(None, self.ui.ui.confirm_password_line_edit)
114
123 self.assertTrue(self.ui.ui.password_assistance.isVisible())115 self.assertTrue(self.ui.ui.password_assistance.isVisible())
124 self.assertTrue(self._called)116 self.assertTrue(self._called)
125117
126118
=== modified file 'ubuntu_sso/qt/tests/test_setup_account.py'
--- ubuntu_sso/qt/tests/test_setup_account.py 2012-03-06 14:13:10 +0000
+++ ubuntu_sso/qt/tests/test_setup_account.py 2012-03-20 16:10:09 +0000
@@ -43,13 +43,20 @@
43 """43 """
44 self.ui.ui.name_edit.setText("")44 self.ui.ui.name_edit.setText("")
45 self.ui.name_assistance()45 self.ui.name_assistance()
46 self.ui.show()
47 self.addCleanup(self.ui.hide)
48 self.assertTrue(self.ui.ui.name_assistance.isVisible())46 self.assertTrue(self.ui.ui.name_assistance.isVisible())
49 self.assertEqual(47 self.assert_error_correct(self.ui.ui.name_assistance, gui.EMPTY_NAME,
50 unicode(self.ui.ui.name_assistance.text()),48 max_width=self.ui.header.max_title_width)
51 gui.ERROR_STYLE % gui.EMPTY_NAME)49
52 self.ui.hide()50 def test_hide_error_on_refresh_clicked(self):
51 """Hide form errors when the user click to refresh the captcha."""
52 self.ui.show_error('error')
53 self.assert_error_correct(self.ui.form_errors_label, 'error',
54 max_width=self.ui.header.max_title_width)
55
56 self.ui.ui.refresh_label.linkActivated.emit('error')
57
58 message = unicode(self.ui.form_errors_label.text())
59 self.assertEqual(message, ' ')
5360
54 def test_enable_setup_button_with_visible_check(self):61 def test_enable_setup_button_with_visible_check(self):
55 """Test _enable_setup_button method with terms check visible."""62 """Test _enable_setup_button method with terms check visible."""
@@ -65,8 +72,6 @@
65 self.ui.ui.captcha_solution_edit.setText('captcha solution')72 self.ui.ui.captcha_solution_edit.setText('captcha solution')
66 self.ui.terms_checkbox.setChecked(True)73 self.ui.terms_checkbox.setChecked(True)
6774
68 self.ui.show()
69 self.addCleanup(self.ui.hide)
70 self.ui.terms_checkbox.setVisible(True)75 self.ui.terms_checkbox.setVisible(True)
71 self.ui.ui.captcha_solution_edit.textEdited.emit('')76 self.ui.ui.captcha_solution_edit.textEdited.emit('')
72 self.assertTrue(self.ui.set_up_button.isEnabled())77 self.assertTrue(self.ui.set_up_button.isEnabled())
@@ -84,8 +89,6 @@
84 self.ui.ui.confirm_password_edit.setText(password)89 self.ui.ui.confirm_password_edit.setText(password)
85 self.ui.ui.captcha_solution_edit.setText('captcha solution')90 self.ui.ui.captcha_solution_edit.setText('captcha solution')
8691
87 self.ui.show()
88 self.addCleanup(self.ui.hide)
89 self.ui.terms_checkbox.setVisible(False)92 self.ui.terms_checkbox.setVisible(False)
90 self.ui.ui.captcha_solution_edit.textEdited.emit('')93 self.ui.ui.captcha_solution_edit.textEdited.emit('')
91 self.assertTrue(self.ui.set_up_button.isEnabled())94 self.assertTrue(self.ui.set_up_button.isEnabled())
@@ -114,8 +117,6 @@
114117
115 def test_password_focus_gain(self):118 def test_password_focus_gain(self):
116 """Check functions execution when focus_changed() is executed."""119 """Check functions execution when focus_changed() is executed."""
117 self.ui.show()
118 self.addCleanup(self.ui.hide)
119 self.ui.ui.password_assistance.setVisible(False)120 self.ui.ui.password_assistance.setVisible(False)
120 self.assertFalse(self.ui.ui.password_assistance.isVisible())121 self.assertFalse(self.ui.ui.password_assistance.isVisible())
121 self.patch(self.ui, 'name_assistance', self._set_called)122 self.patch(self.ui, 'name_assistance', self._set_called)
@@ -174,7 +175,6 @@
174 """Test on_user_registered method."""175 """Test on_user_registered method."""
175 email = 'email@example'176 email = 'email@example'
176 self.ui.ui.email_edit.setText(email)177 self.ui.ui.email_edit.setText(email)
177
178 self.assert_signal_emitted(self.ui.userRegistered, (email,),178 self.assert_signal_emitted(self.ui.userRegistered, (email,),
179 self.ui.on_user_registered, self.app_name, email)179 self.ui.on_user_registered, self.app_name, email)
180180
@@ -193,8 +193,6 @@
193 def test_initialize_page(self):193 def test_initialize_page(self):
194 """Widgets are properly initialized."""194 """Widgets are properly initialized."""
195 self.ui.initializePage()195 self.ui.initializePage()
196 self.ui.show()
197 self.addCleanup(self.ui.hide)
198196
199 # set up account button197 # set up account button
200 expected = [QtGui.QWizard.BackButton, QtGui.QWizard.Stretch,198 expected = [QtGui.QWizard.BackButton, QtGui.QWizard.Stretch,
@@ -210,12 +208,15 @@
210 self.assertFalse(self.ui.captcha_received)208 self.assertFalse(self.ui.captcha_received)
211209
212 # labels210 # labels
213 self.assertEqual(self.ui.ui.name_label.text(), gui.NAME_ENTRY)211 self.assertEqual(unicode(self.ui.ui.name_label.text()),
214 self.assertEqual(self.ui.ui.email_label.text(), gui.EMAIL)212 gui.NAME_ENTRY)
215 self.assertEqual(self.ui.ui.confirm_email_label.text(),213 self.assertEqual(unicode(self.ui.ui.email_label.text()),
214 gui.EMAIL)
215 self.assertEqual(unicode(self.ui.ui.confirm_email_label.text()),
216 gui.RETYPE_EMAIL)216 gui.RETYPE_EMAIL)
217 self.assertEqual(self.ui.ui.password_label.text(), gui.PASSWORD)217 self.assertEqual(unicode(self.ui.ui.password_label.text()),
218 self.assertEqual(self.ui.ui.confirm_password_label.text(),218 gui.PASSWORD)
219 self.assertEqual(unicode(self.ui.ui.confirm_password_label.text()),
219 gui.RETYPE_PASSWORD)220 gui.RETYPE_PASSWORD)
220221
221 # assistants222 # assistants
@@ -230,8 +231,6 @@
230 self.patch(self.ui, 'set_next_validation', self._set_called)231 self.patch(self.ui, 'set_next_validation', self._set_called)
231 self.ui.initializePage()232 self.ui.initializePage()
232 self.ui.captcha_received = True233 self.ui.captcha_received = True
233 self.ui.show()
234 self.addCleanup(self.ui.hide)
235234
236 self.ui.set_up_button.clicked.emit(False)235 self.ui.set_up_button.clicked.emit(False)
237 self.assertEqual(self._called, ((False,), {}))236 self.assertEqual(self._called, ((False,), {}))
@@ -239,13 +238,10 @@
239 def test_set_error_message(self):238 def test_set_error_message(self):
240 """Check the state of the label after calling: set_error_message."""239 """Check the state of the label after calling: set_error_message."""
241 self.ui.email_assistance()240 self.ui.email_assistance()
242 self.ui.show()
243 self.addCleanup(self.ui.hide)
244 self.ui.set_error_message(self.ui.ui.email_assistance, "message")241 self.ui.set_error_message(self.ui.ui.email_assistance, "message")
245 self.assertTrue(self.ui.ui.email_assistance.isVisible())242 self.assertTrue(self.ui.ui.email_assistance.isVisible())
246 self.assertEqual(243 self.assert_error_correct(self.ui.ui.email_assistance, "message",
247 unicode(self.ui.ui.email_assistance.text()),244 max_width=self.ui.header.max_title_width)
248 gui.ERROR_STYLE % "message")
249245
250 def test_blank_name(self):246 def test_blank_name(self):
251 """Status when the name field is blank (spaces).247 """Status when the name field is blank (spaces).
@@ -255,13 +251,9 @@
255 """251 """
256 self.ui.ui.name_edit.setText(" ")252 self.ui.ui.name_edit.setText(" ")
257 self.ui.name_assistance()253 self.ui.name_assistance()
258 self.ui.show()
259 self.addCleanup(self.ui.hide)
260 self.assertTrue(self.ui.ui.name_assistance.isVisible())254 self.assertTrue(self.ui.ui.name_assistance.isVisible())
261 self.assertEqual(255 self.assert_error_correct(self.ui.ui.name_assistance, gui.EMPTY_NAME,
262 unicode(self.ui.ui.name_assistance.text()),256 max_width=self.ui.header.max_title_width)
263 gui.ERROR_STYLE % gui.EMPTY_NAME)
264 self.ui.hide()
265257
266 def test_valid_name(self):258 def test_valid_name(self):
267 """Status when the name field is valid.259 """Status when the name field is valid.
@@ -270,10 +262,7 @@
270 """262 """
271 self.ui.ui.name_edit.setText("John Doe")263 self.ui.ui.name_edit.setText("John Doe")
272 self.ui.name_assistance()264 self.ui.name_assistance()
273 self.ui.show()
274 self.addCleanup(self.ui.hide)
275 self.assertFalse(self.ui.ui.name_assistance.isVisible())265 self.assertFalse(self.ui.ui.name_assistance.isVisible())
276 self.ui.hide()
277266
278 def test_invalid_email(self):267 def test_invalid_email(self):
279 """Status when the email field has no @.268 """Status when the email field has no @.
@@ -283,12 +272,10 @@
283 """272 """
284 self.ui.ui.email_edit.setText("foobar")273 self.ui.ui.email_edit.setText("foobar")
285 self.ui.email_assistance()274 self.ui.email_assistance()
286 self.ui.show()
287 self.addCleanup(self.ui.hide)
288 self.assertTrue(self.ui.ui.email_assistance.isVisible())275 self.assertTrue(self.ui.ui.email_assistance.isVisible())
289 self.assertEqual(276 self.assert_error_correct(self.ui.ui.email_assistance,
290 unicode(self.ui.ui.email_assistance.text()),277 gui.INVALID_EMAIL,
291 gui.ERROR_STYLE % gui.INVALID_EMAIL)278 max_width=self.ui.header.max_title_width)
292279
293 def test_valid_email(self):280 def test_valid_email(self):
294 """Status when the email field has a @.281 """Status when the email field has a @.
@@ -297,10 +284,7 @@
297 """284 """
298 self.ui.ui.email_edit.setText("foo@bar")285 self.ui.ui.email_edit.setText("foo@bar")
299 self.ui.email_assistance()286 self.ui.email_assistance()
300 self.ui.show()
301 self.addCleanup(self.ui.hide)
302 self.assertFalse(self.ui.ui.email_assistance.isVisible())287 self.assertFalse(self.ui.ui.email_assistance.isVisible())
303 self.ui.hide()
304288
305 def test_matching_emails(self):289 def test_matching_emails(self):
306 """Status when the email fields match.290 """Status when the email fields match.
@@ -310,10 +294,7 @@
310 self.ui.ui.email_edit.setText("foo@bar")294 self.ui.ui.email_edit.setText("foo@bar")
311 self.ui.ui.confirm_email_edit.setText("foo@bar")295 self.ui.ui.confirm_email_edit.setText("foo@bar")
312 self.ui.confirm_email_assistance()296 self.ui.confirm_email_assistance()
313 self.ui.show()
314 self.addCleanup(self.ui.hide)
315 self.assertFalse(self.ui.ui.confirm_email_assistance.isVisible())297 self.assertFalse(self.ui.ui.confirm_email_assistance.isVisible())
316 self.ui.hide()
317298
318 def test_not_matching_emails(self):299 def test_not_matching_emails(self):
319 """Status when the email fields don't match.300 """Status when the email fields don't match.
@@ -324,18 +305,13 @@
324 self.ui.ui.email_edit.setText("foo@bar")305 self.ui.ui.email_edit.setText("foo@bar")
325 self.ui.ui.confirm_email_edit.setText("foo@baz")306 self.ui.ui.confirm_email_edit.setText("foo@baz")
326 self.ui.confirm_email_assistance()307 self.ui.confirm_email_assistance()
327 self.ui.show()
328 self.addCleanup(self.ui.hide)
329 self.assertTrue(self.ui.ui.confirm_email_assistance.isVisible())308 self.assertTrue(self.ui.ui.confirm_email_assistance.isVisible())
330 self.assertEqual(309 self.assert_error_correct(self.ui.ui.confirm_email_assistance,
331 unicode(self.ui.ui.confirm_email_assistance.text()),310 gui.EMAIL_MATCH,
332 gui.ERROR_STYLE % gui.EMAIL_MATCH)311 max_width=self.ui.header.max_title_width)
333 self.ui.hide()
334312
335 def test_focus_changed_password_visibility(self):313 def test_focus_changed_password_visibility(self):
336 """Check visibility changes when focus_changed() is executed."""314 """Check visibility changes when focus_changed() is executed."""
337 self.ui.show()
338 self.addCleanup(self.ui.hide)
339 self.ui.focus_changed(None, self.ui.ui.password_edit)315 self.ui.focus_changed(None, self.ui.ui.password_edit)
340 self.assertTrue(self.ui.ui.password_assistance.isVisible())316 self.assertTrue(self.ui.ui.password_assistance.isVisible())
341317
@@ -350,33 +326,41 @@
350 self.ui.showEvent(QtGui.QShowEvent())326 self.ui.showEvent(QtGui.QShowEvent())
351 self.ui.hideEvent(QtGui.QHideEvent())327 self.ui.hideEvent(QtGui.QHideEvent())
352328
353 def test_on_captcha_refreshing(self):329 def test_on_captcha_refreshing_visible(self):
354 """Check the state of the overlay on captcha refreshing."""330 """Check the state of the overlay on captcha refreshing."""
355 self.assertEqual(self._overlay_show_counter, 0)331 self.ui.hide_overlay()
356 self.ui.on_captcha_refreshing()332
357 self.assertEqual(self._overlay_show_counter, 0)333 self.assertEqual(self._overlay_show_counter, 0)
358 self.assertTrue(self.ui.isEnabled())334 self.assertTrue(self.ui.isEnabled())
359 self.ui.captcha_received = True335
360 self.ui.show()336 self.ui.on_captcha_refreshing()
361 self.addCleanup(self.ui.hide)337
362 self.assertEqual(self._overlay_show_counter, 0)
363 self.assertTrue(self.ui.isEnabled())
364 self.ui.on_captcha_refreshing()
365 self.assertFalse(self.ui.isEnabled())338 self.assertFalse(self.ui.isEnabled())
366 self.assertEqual(self._overlay_show_counter, 1)339 self.assertEqual(self._overlay_show_counter, 1)
367340
341 def test_on_captcha_refreshing_not_visible(self):
342 """Check the state of the overlay on captcha refreshing."""
343 self.ui.hide_overlay()
344
345 self.assertEqual(self._overlay_show_counter, 0)
346 self.assertTrue(self.ui.isEnabled())
347
348 self.ui.hide()
349 self.ui.on_captcha_refreshing()
350
351 self.assertEqual(self._overlay_show_counter, 0)
352 self.assertTrue(self.ui.isEnabled())
353
368 def test_on_captcha_refresh_complete(self):354 def test_on_captcha_refresh_complete(self):
369 """Check the state of the overlay on captcha refreshing complete."""355 """Check the state of the overlay on captcha refreshing complete."""
370 self.assertEqual(self._overlay_hide_counter, 0)356 self.assertEqual(self._overlay_hide_counter, 0)
371 self.assertTrue(self.ui.isEnabled())357
372 self.ui.on_captcha_refresh_complete()358 self.ui.on_captcha_refresh_complete()
359
373 self.assertEqual(self._overlay_hide_counter, 1)360 self.assertEqual(self._overlay_hide_counter, 1)
374 self.assertTrue(self.ui.isEnabled())
375361
376 def test_hide_error_on_refresh_captcha(self):362 def test_hide_error_on_refresh_captcha(self):
377 """Test that the errors are hidden on refresh captcha."""363 """Test that the errors are hidden on refresh captcha."""
378 self.ui.show()364 self.ui.show_error('error-message')
379 self.addCleanup(self.ui.hide)
380 self.ui.show_error(self.app_name, 'error-message')
381 self.ui.ui.refresh_label.linkActivated.emit('link')365 self.ui.ui.refresh_label.linkActivated.emit('link')
382 self.assertEqual(self.ui.form_errors_label.text(), ' ')366 self.assertEqual(self.ui.form_errors_label.text(), ' ')
383367
=== modified file 'ubuntu_sso/qt/tests/test_ssl_dialog.py'
--- ubuntu_sso/qt/tests/test_ssl_dialog.py 2012-03-05 16:32:37 +0000
+++ ubuntu_sso/qt/tests/test_ssl_dialog.py 2012-03-20 16:10:09 +0000
@@ -142,7 +142,8 @@
142142
143 def test_set_expander(self):143 def test_set_expander(self):
144 """Test that the expander is correctly set."""144 """Test that the expander is correctly set."""
145 self.assertEqual(SSL_CERT_DETAILS, self.dialog.expander.text())145 self.assertEqual(SSL_CERT_DETAILS,
146 unicode(self.dialog.expander.text()))
146 self.assertNotEqual(None, self.dialog.expander.content)147 self.assertNotEqual(None, self.dialog.expander.content)
147 self.assertEqual(2, self.dialog.ui.expander_layout.indexOf(148 self.assertEqual(2, self.dialog.ui.expander_layout.indexOf(
148 self.dialog.expander))149 self.dialog.expander))
149150
=== modified file 'ubuntu_sso/qt/tests/test_sso_wizard_page.py'
--- ubuntu_sso/qt/tests/test_sso_wizard_page.py 2012-03-05 18:56:50 +0000
+++ ubuntu_sso/qt/tests/test_sso_wizard_page.py 2012-03-20 16:10:09 +0000
@@ -16,98 +16,137 @@
1616
17"""Test the SSOWizardPage and related."""17"""Test the SSOWizardPage and related."""
1818
19from twisted.internet import defer19from ubuntu_sso.qt import PREFERED_UI_SIZE, sso_wizard_page as gui
2020from ubuntu_sso.qt.tests import (
21from ubuntu_sso.qt import PREFERED_UI_SIZE21 APP_NAME,
22from ubuntu_sso.qt.setup_account_page import SetupAccountPage22 BaseTestCase,
23from ubuntu_sso.qt.sso_wizard_page import Header23 build_string_for_pixels,
24from ubuntu_sso.qt.tests import BaseTestCase, PageBaseTestCase24 PageBaseTestCase,
2525)
2626
27class HeaderTest(BaseTestCase):27
28MAX_WIDTH = 100
29
30
31class WizardHeaderTestCase(BaseTestCase):
2832
29 """Tests for injected Header in each Wizard Page."""33 """Tests for injected Header in each Wizard Page."""
3034
31 @defer.inlineCallbacks35 kwargs = dict(max_width=MAX_WIDTH)
32 def setUp(self):36 ui_class = gui.WizardHeader
33 yield super(HeaderTest, self).setUp()37 ui_wizard_class = None
34 self.header = Header()
3538
36 def test_label_state(self):39 def test_label_state(self):
37 """Check the title and subtitle properties."""40 """Check the title and subtitle properties."""
38 self.assertTrue(self.header.title_label.wordWrap())41 self.assertTrue(self.ui.title_label.wordWrap())
39 self.assertTrue(self.header.subtitle_label.wordWrap())42 self.assertTrue(self.ui.subtitle_label.wordWrap())
40 self.assertFalse(self.header.title_label.isVisible())43 self.assertFalse(self.ui.title_label.isVisible())
41 self.assertFalse(self.header.subtitle_label.isVisible())44 self.assertFalse(self.ui.subtitle_label.isVisible())
4245
43 def test_set_title(self):46 def test_set_title(self):
44 """Check if set_title works ok, showing the widget if necessary."""47 """Check if set_title works ok, showing the widget if necessary."""
45 self.header.set_title('title')48 max_width = self.ui.max_title_width
46 self.assertEqual(self.header.title_label.text(), 'title')49 text = build_string_for_pixels(self.ui.title_label, max_width)
47 self.header.show()50
48 self.assertTrue(self.header.title_label.isVisible())51 self.ui.set_title(text)
49 self.header.hide()52
53 self.assert_title_correct(self.ui.title_label, text, max_width)
5054
51 def test_set_elided_title(self):55 def test_set_elided_title(self):
52 """Check if set_title adds the ellipsis when necessary."""56 """Check if set_title adds the ellipsis when necessary."""
53 # add an extra letter so we ensure this needs to be trimmed57 # add an extra letter so we ensure this needs to be trimmed
54 title = 'a' * int(PREFERED_UI_SIZE['width'] * 0.95) + 'a'58 max_width = self.ui.max_title_width
55 self.header.set_title(title)59 text = build_string_for_pixels(self.ui.title_label, max_width + 10)
56 self.assertEqual(self.header.title_label.toolTip(), title)60
57 expected = unicode(self.header.title_label.text())61 self.ui.set_title(text)
58 self.assertTrue(expected.endswith(u'\u2026'))62
63 self.assert_title_correct(self.ui.title_label, text, max_width)
5964
60 def test_set_empty_title(self):65 def test_set_empty_title(self):
61 """Check if the widget is hidden for empty title."""66 """Check if the widget is hidden for empty title."""
62 self.header.set_title('')67 self.ui.set_title('')
63 self.assertFalse(self.header.title_label.isVisible())68
69 self.assertEqual(self.ui.title_label.toolTip(), '')
70 self.assertFalse(self.ui.title_label.isVisible())
6471
65 def test_set_subtitle(self):72 def test_set_subtitle(self):
66 """Check if set_subtitle works ok, showing the widget if necessary."""73 """Check if set_subtitle works ok, showing the widget if necessary."""
67 self.header.set_subtitle('subtitle')74 max_width = self.ui.max_subtitle_width
68 self.assertEqual(self.header.subtitle_label.text(), 'subtitle')75 text = build_string_for_pixels(self.ui.subtitle_label, max_width)
69 self.header.show()76
70 self.assertTrue(self.header.subtitle_label.isVisible())77 self.ui.set_subtitle(text)
71 self.header.hide()78
79 self.assert_subtitle_correct(self.ui.subtitle_label, text, max_width)
7280
73 def test_set_elided_subtitle(self):81 def test_set_elided_subtitle(self):
74 """Check if set_subtitle adds the ellipsis when necessary."""82 """Check if set_subtitle adds the ellipsis when necessary."""
75 subtitle = 'a' * int(PREFERED_UI_SIZE['width'] * 0.95) + 'a'83 max_width = self.ui.max_subtitle_width
76 self.header.set_subtitle(subtitle)84 text = build_string_for_pixels(self.ui.subtitle_label, max_width + 10)
77 self.assertEqual(self.header.subtitle_label.toolTip(), subtitle)85
78 expected = unicode(self.header.subtitle_label.text())86 self.ui.set_subtitle(text)
79 self.assertTrue(expected.endswith(u'\u2026'))87
88 self.assert_subtitle_correct(self.ui.subtitle_label, text, max_width)
8089
81 def test_set_empty_subtitle(self):90 def test_set_empty_subtitle(self):
82 """Check if the widget is hidden for empty subtitle."""91 """Check if the widget is hidden for empty subtitle."""
83 self.header.set_title('')92 self.ui.set_subtitle('')
84 self.assertFalse(self.header.title_label.isVisible())93
8594 self.assertEqual(self.ui.subtitle_label.toolTip(), '')
8695 self.assertFalse(self.ui.subtitle_label.isVisible())
87class SSOWizardPageTest(PageBaseTestCase):96
8897
89 """Tests for SSOWizardPage."""98class BaseWizardPageTestCase(PageBaseTestCase):
9099
91 ui_class = SetupAccountPage100 """Tests for SSOWizardPage."""
101
102 kwargs = {}
103 ui_class = gui.BaseWizardPage
104
105 def test_max_width(self):
106 """The max_width is correct."""
107 self.assertEqual(self.ui.max_width, 0)
108
109
110class SSOWizardPageTestCase(BaseWizardPageTestCase):
111
112 """Tests for SSOWizardPage."""
113
114 kwargs = dict(app_name=APP_NAME)
115 ui_class = gui.SSOWizardPage
116
117 def test_max_width(self):
118 """The max_width is correct."""
119 self.assertEqual(self.ui.max_width, PREFERED_UI_SIZE['width'])
92120
93 def test_show_error(self):121 def test_show_error(self):
94 """Test show_error with a normal length string."""122 """Test show_error with a normal length string."""
95 message = 'error-message'123 message = 'error-message'
96 self.ui.show_error(self.app_name, message)124 self.ui.show_error(message)
97 self.assertEqual(self.ui.form_errors_label.toolTip(), message)125
98 expected = unicode(self.ui.form_errors_label.text())126 self.assert_error_correct(self.ui.form_errors_label, message,
99 self.assertEqual(expected, message)127 self.ui.header.max_title_width)
100128
101 def test_show_error_long_text(self):129 def test_show_error_long_text(self):
102 """Test show_error with a long length string."""130 """Test show_error with a long length string."""
103 message = 'a' * int(PREFERED_UI_SIZE['width'] * 0.95) + 'a'131 message = build_string_for_pixels(self.ui.form_errors_label,
104 self.ui.show_error(self.app_name, message)132 self.ui.header.max_title_width + 10)
105 self.assertEqual(self.ui.form_errors_label.toolTip(), message)133
106 expected = unicode(self.ui.form_errors_label.text())134 self.ui.show_error(message)
107 self.assertTrue(expected.endswith(u'\u2026'))135 self.assert_error_correct(self.ui.form_errors_label, message,
136 self.ui.header.max_title_width)
108137
109 def test_hide_error(self):138 def test_hide_error(self):
110 """Test show_error with a long length string."""139 """Test show_error with a long length string."""
111 message = ' '
112 self.ui.hide_error()140 self.ui.hide_error()
113 self.assertEqual(self.ui.form_errors_label.text(), message)141
142 self.assertEqual(self.ui.form_errors_label.text(), ' ')
143
144 def test_setup_page_with_failing_backend(self):
145 """Test how the ui react with an invalid backend."""
146 self.patch(gui.main, "get_sso_client", lambda: None)
147 self.patch(self.ui, "show_error", self._set_called)
148 self.ui.setup_page()
149 reason = 'There was a problem trying to setup the page %r' % self.ui
150 expected = ((reason,), {})
151 self.assertEqual(expected, self._called)
152 self.assertFalse(self.ui.isEnabled())
114153
=== modified file 'ubuntu_sso/qt/tests/test_ubuntu_sso_wizard.py'
--- ubuntu_sso/qt/tests/test_ubuntu_sso_wizard.py 2012-03-05 14:37:43 +0000
+++ ubuntu_sso/qt/tests/test_ubuntu_sso_wizard.py 2012-03-20 16:10:09 +0000
@@ -19,6 +19,11 @@
19from PyQt4 import QtGui19from PyQt4 import QtGui
20from twisted.internet import defer20from twisted.internet import defer
2121
22from ubuntu_sso.tests import (
23 APP_NAME,
24 EMAIL,
25 PASSWORD,
26)
22from ubuntu_sso.qt import PREFERED_UI_SIZE, ubuntu_sso_wizard27from ubuntu_sso.qt import PREFERED_UI_SIZE, ubuntu_sso_wizard
23from ubuntu_sso.qt.tests import (28from ubuntu_sso.qt.tests import (
24 BaseTestCase,29 BaseTestCase,
@@ -86,6 +91,11 @@
86 finish_button.clicked.emit(True)91 finish_button.clicked.emit(True)
87 self.assertEqual(self._called, ((None,), {}))92 self.assertEqual(self._called, ((None,), {}))
8893
94 def test_window_title(self):
95 """Check the window title for the application."""
96 title = unicode(self.ui.windowTitle())
97 self.assertEqual(title, ubuntu_sso_wizard.WINDOW_TITLE)
98
8999
90class UbuntuSSOWizardTestCase(BaseTestCase):100class UbuntuSSOWizardTestCase(BaseTestCase):
91101
@@ -102,13 +112,9 @@
102112
103 def test_window_size(self):113 def test_window_size(self):
104 """check the window size."""114 """check the window size."""
105 self.ui.show()
106 self.addCleanup(self.ui.hide)
107 size = self.ui.size()115 size = self.ui.size()
108 # pylint: disable=E1101116 self.assertTrue(size.height() >= PREFERED_UI_SIZE['height'])
109 self.assertGreaterEqual(size.height(), PREFERED_UI_SIZE['height'])117 self.assertTrue(size.width() >= PREFERED_UI_SIZE['width'])
110 self.assertGreaterEqual(size.width(), PREFERED_UI_SIZE['width'])
111 # pylint: enable=E1101
112118
113 def test_move_to_success_page_state(self):119 def test_move_to_success_page_state(self):
114 """Test _move_to_success_page method."""120 """Test _move_to_success_page method."""
@@ -123,6 +129,9 @@
123129
124 def test_overlay_shows(self):130 def test_overlay_shows(self):
125 """Test if the signals call the overlay.show properly."""131 """Test if the signals call the overlay.show properly."""
132 # reset the counter
133 self.ui.overlay.show_counter = 0
134
126 for page in self.ui._pages:135 for page in self.ui._pages:
127 page.show_overlay()136 page.show_overlay()
128137
@@ -130,6 +139,9 @@
130139
131 def test_overlay_hides(self):140 def test_overlay_hides(self):
132 """Test if the signals call the overlay.show properly."""141 """Test if the signals call the overlay.show properly."""
142 # reset the counter
143 self.ui.overlay.show_counter = 0
144
133 for page in self.ui._pages:145 for page in self.ui._pages:
134 page.hide_overlay()146 page.hide_overlay()
135147
@@ -146,3 +158,21 @@
146 self.ui.reset_password.passwordChanged.emit('')158 self.ui.reset_password.passwordChanged.emit('')
147 expected = ((self.ui.current_user,), {})159 expected = ((self.ui.current_user,), {})
148 self.assertEqual(expected, self._called)160 self.assertEqual(expected, self._called)
161
162 def test_email_verification_page_params_from_current_user(self):
163 """Tests that email_verification_page receives the proper params."""
164 self.ui._next_id = self.ui.current_user_page_id
165 self.ui.next()
166 self.ui.current_user.ui.email_edit.setText(EMAIL)
167 self.ui.current_user.ui.password_edit.setText(PASSWORD)
168 self.ui.current_user.on_user_not_validated(APP_NAME, EMAIL)
169 self.assertEqual(EMAIL, self.ui.email_verification.email)
170 self.assertEqual(PASSWORD, self.ui.email_verification.password)
171
172 def test_email_verification_page_params_from_setup(self):
173 """Tests that email_verification_page receives the proper params."""
174 self.ui.setup_account.ui.email_edit.setText(EMAIL)
175 self.ui.setup_account.ui.password_edit.setText(PASSWORD)
176 self.ui.setup_account.on_user_registered(APP_NAME, {})
177 self.assertEqual(EMAIL, self.ui.email_verification.email)
178 self.assertEqual(PASSWORD, self.ui.email_verification.password)
149179
=== modified file 'ubuntu_sso/qt/ubuntu_sso_wizard.py'
--- ubuntu_sso/qt/ubuntu_sso_wizard.py 2012-03-05 20:30:57 +0000
+++ ubuntu_sso/qt/ubuntu_sso_wizard.py 2012-03-20 16:10:09 +0000
@@ -30,7 +30,7 @@
30 USER_SUCCESS,30 USER_SUCCESS,
31)31)
32from ubuntu_sso.logger import setup_gui_logging32from ubuntu_sso.logger import setup_gui_logging
33from ubuntu_sso.qt import PREFERED_UI_SIZE33from ubuntu_sso.qt import PREFERED_UI_SIZE, WINDOW_TITLE
34from ubuntu_sso.qt.current_user_sign_in_page import CurrentUserSignInPage34from ubuntu_sso.qt.current_user_sign_in_page import CurrentUserSignInPage
35from ubuntu_sso.qt.email_verification_page import EmailVerificationPage35from ubuntu_sso.qt.email_verification_page import EmailVerificationPage
36from ubuntu_sso.qt.error_page import ErrorPage36from ubuntu_sso.qt.error_page import ErrorPage
@@ -141,6 +141,8 @@
141141
142 def _go_back_to_page(self, page):142 def _go_back_to_page(self, page):
143 """Move back until it reaches the 'page'."""143 """Move back until it reaches the 'page'."""
144 logger.debug('Moving back from page: %s, to page: %s',
145 self.currentPage(), page)
144 page_id = self._pages[page]146 page_id = self._pages[page]
145 visited_pages = self.visitedPages()147 visited_pages = self.visitedPages()
146 for index in reversed(visited_pages):148 for index in reversed(visited_pages):
@@ -150,30 +152,42 @@
150152
151 def _move_to_reset_password_page(self):153 def _move_to_reset_password_page(self):
152 """Move to the reset password page wizard."""154 """Move to the reset password page wizard."""
155 logger.debug('Moving to ResetPasswordPage from: %s',
156 self.currentPage())
153 self._next_id = self.reset_password_page_id157 self._next_id = self.reset_password_page_id
154 self.next()158 self.next()
155 self._next_id = -1159 self._next_id = -1
156160
157 def _move_to_email_verification_page(self):161 def _move_to_email_verification_page(self, email):
158 """Move to the email verification page wizard."""162 """Move to the email verification page wizard."""
163 logger.debug('Moving to EmailVerificationPage from: %s',
164 self.currentPage())
159 self._next_id = self.email_verification_page_id165 self._next_id = self.email_verification_page_id
166 self.email_verification.email = unicode(email)
167 self.email_verification.password = self.currentPage().password
160 self.next()168 self.next()
161 self._next_id = -1169 self._next_id = -1
162170
163 def _move_to_setup_account_page(self):171 def _move_to_setup_account_page(self):
164 """Move to the setup account page wizard."""172 """Move to the setup account page wizard."""
173 logger.debug('Moving to SetupAccountPage from: %s',
174 self.currentPage())
165 self._next_id = self.setup_account_page_id175 self._next_id = self.setup_account_page_id
166 self.next()176 self.next()
167 self._next_id = -1177 self._next_id = -1
168178
169 def _move_to_login_page(self):179 def _move_to_login_page(self):
170 """Move to the login page wizard."""180 """Move to the login page wizard."""
181 logger.debug('Moving to CurrentUserSignInPage from: %s',
182 self.currentPage())
171 self._next_id = self.current_user_page_id183 self._next_id = self.current_user_page_id
172 self.next()184 self.next()
173 self._next_id = -1185 self._next_id = -1
174186
175 def _move_to_success_page(self):187 def _move_to_success_page(self):
176 """Move to the success page wizard."""188 """Move to the success page wizard."""
189 logger.debug('Moving to SuccessPage from: %s',
190 self.currentPage())
177 self._next_id = self.success_page_id191 self._next_id = self.success_page_id
178 self.next()192 self.next()
179 self.setButtonLayout([193 self.setButtonLayout([
@@ -186,6 +200,8 @@
186200
187 def _move_to_forgotten_page(self):201 def _move_to_forgotten_page(self):
188 """Move to the forgotten page wizard."""202 """Move to the forgotten page wizard."""
203 logger.debug('Moving to ForgottenPasswordPage from: %s',
204 self.currentPage())
189 self._next_id = self.forgotten_password_page_id205 self._next_id = self.forgotten_password_page_id
190 self.next()206 self.next()
191 self._next_id = -1207 self._next_id = -1
@@ -258,6 +274,7 @@
258 logger.debug('UbuntuSSOClientGUI: app_name %r, kwargs %r.',274 logger.debug('UbuntuSSOClientGUI: app_name %r, kwargs %r.',
259 app_name, kwargs)275 app_name, kwargs)
260 self.app_name = app_name276 self.app_name = app_name
277 self.setWindowTitle(WINDOW_TITLE)
261 # create the controller and the ui, then set the cb and call the show278 # create the controller and the ui, then set the cb and call the show
262 # method so that we can work279 # method so that we can work
263 self.wizard = UbuntuSSOWizard(app_name=app_name, **kwargs)280 self.wizard = UbuntuSSOWizard(app_name=app_name, **kwargs)
264281
=== modified file 'ubuntu_sso/utils/__init__.py'
--- ubuntu_sso/utils/__init__.py 2012-02-17 20:48:27 +0000
+++ ubuntu_sso/utils/__init__.py 2012-03-20 16:10:09 +0000
@@ -32,8 +32,14 @@
3232
3333
34logger = setup_logging("ubuntu_sso.utils")34logger = setup_logging("ubuntu_sso.utils")
35BIN_SUFFIX = 'bin'
35DATA_SUFFIX = 'data'36DATA_SUFFIX = 'data'
36BIN_SUFFIX = 'bin'37
38if sys.platform == "win32":
39 from ubuntu_sso.utils import windows as source
40else:
41 from ubuntu_sso.utils import linux as source
42PLATFORM_QSS = source.PLATFORM_QSS
3743
3844
39def _get_dir(dir_name, dir_constant):45def _get_dir(dir_name, dir_constant):
@@ -88,8 +94,15 @@
88 found, return the value of the BIN_DIR.94 found, return the value of the BIN_DIR.
8995
90 """96 """
91 result = _get_dir(dir_name=BIN_SUFFIX, dir_constant='BIN_DIR')97 # If sys is frozen, this is an .exe, and all binaries are in
98 # the same place
99 if getattr(sys, "frozen", None) is not None:
100 exec_path = os.path.abspath(sys.executable)
101 result = os.path.dirname(exec_path)
102 else:
103 result = _get_dir(dir_name=BIN_SUFFIX, dir_constant='BIN_DIR')
92 assert result is not None, '%r dir can not be None.' % BIN_SUFFIX104 assert result is not None, '%r dir can not be None.' % BIN_SUFFIX
105 logger.info('get_bin_dir: returning dir located at %r.', result)
93 return result106 return result
94107
95108
96109
=== added file 'ubuntu_sso/utils/linux.py'
--- ubuntu_sso/utils/linux.py 1970-01-01 00:00:00 +0000
+++ ubuntu_sso/utils/linux.py 2012-03-20 16:10:09 +0000
@@ -0,0 +1,19 @@
1# -*- coding: utf-8 -*-
2#
3# Copyright 2010-2012 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16
17"""Platform specific constants and functions (for Linux)."""
18
19PLATFORM_QSS = ":/linux.qss"
020
=== modified file 'ubuntu_sso/utils/runner/glib.py'
--- ubuntu_sso/utils/runner/glib.py 2012-02-17 18:43:17 +0000
+++ ubuntu_sso/utils/runner/glib.py 2012-03-20 16:10:09 +0000
@@ -27,7 +27,7 @@
27logger = setup_logging("ubuntu_sso.utils.runner.glib")27logger = setup_logging("ubuntu_sso.utils.runner.glib")
2828
2929
30NO_SUCH_FILE_OR_DIR = 'No such file or directory'30NO_SUCH_FILE_OR_DIR = '[Errno 2]'
3131
3232
33def spawn_program(args, reply_handler, error_handler):33def spawn_program(args, reply_handler, error_handler):
3434
=== modified file 'ubuntu_sso/utils/runner/tx.py'
--- ubuntu_sso/utils/runner/tx.py 2012-02-17 19:13:15 +0000
+++ ubuntu_sso/utils/runner/tx.py 2012-03-20 16:10:09 +0000
@@ -26,7 +26,7 @@
2626
27logger = setup_logging("ubuntu_sso.utils.runner.tx")27logger = setup_logging("ubuntu_sso.utils.runner.tx")
2828
29NO_SUCH_FILE_OR_DIR = 'OSError: [Errno 2] No such file or directory'29NO_SUCH_FILE_OR_DIR = 'OSError: [Errno 2]'
3030
3131
32EXE_EXT = ''32EXE_EXT = ''
3333
=== modified file 'ubuntu_sso/utils/tests/test_common.py'
--- ubuntu_sso/utils/tests/test_common.py 2012-02-15 20:21:52 +0000
+++ ubuntu_sso/utils/tests/test_common.py 2012-03-20 16:10:09 +0000
@@ -17,6 +17,7 @@
17"""Tests for the oauth_headers helper function."""17"""Tests for the oauth_headers helper function."""
1818
19import logging19import logging
20import os
20import sys21import sys
21import time22import time
2223
@@ -146,13 +147,21 @@
146 self.assertEqual(expected, result)147 self.assertEqual(expected, result)
147148
148149
149class GetBinDirTestCase(TestCase):150class GetBinDirTestCase(GetProjectDirTestCase):
150 """Test case for get_bin_dir when constants module is not defined."""151 """Test case for get_bin_dir when constants module is not defined."""
151152
152 DIR_NAME = utils.BIN_SUFFIX153 DIR_NAME = utils.BIN_SUFFIX
153 DIR_CONSTANT = 'BIN_DIR'154 DIR_CONSTANT = 'BIN_DIR'
154 DIR_GETTER = 'get_bin_dir'155 DIR_GETTER = 'get_bin_dir'
155156
157 def test_frozen_binary(self):
158 """Test that frozen binaries return a valid path."""
159 sys.frozen = True
160 self.addCleanup(delattr, sys, "frozen")
161 expected = os.path.dirname(os.path.abspath(sys.executable))
162 result = self.get_dir()
163 self.assertEqual(expected, result)
164
156165
157class GetBinDirWithConstantsTestCase(GetProjectDirWithConstantsTestCase):166class GetBinDirWithConstantsTestCase(GetProjectDirWithConstantsTestCase):
158 """Test case for get_bin_dir when constants module is not defined."""167 """Test case for get_bin_dir when constants module is not defined."""
159168
=== modified file 'ubuntu_sso/utils/ui.py'
--- ubuntu_sso/utils/ui.py 2012-03-06 14:13:10 +0000
+++ ubuntu_sso/utils/ui.py 2012-03-20 16:10:09 +0000
@@ -43,7 +43,7 @@
43CAPTCHA_RELOAD_TEXT = _('refresh')43CAPTCHA_RELOAD_TEXT = _('refresh')
44CAPTCHA_RELOAD_TOOLTIP = _('Reload')44CAPTCHA_RELOAD_TOOLTIP = _('Reload')
45CAPTCHA_REQUIRED_ERROR = _('The captcha is a required field')45CAPTCHA_REQUIRED_ERROR = _('The captcha is a required field')
46CLOSE_AND_SETUP_LATER = _('Close window and setup later')46CLOSE_AND_SETUP_LATER = _('Close window and set up later')
47CONGRATULATIONS = _("Congratulations, {app_name} is installed!")47CONGRATULATIONS = _("Congratulations, {app_name} is installed!")
48CONNECT_HELP_LABEL = _('To connect this computer to %(app_name)s enter your '48CONNECT_HELP_LABEL = _('To connect this computer to %(app_name)s enter your '
49 'details below.')49 'details below.')
@@ -133,6 +133,12 @@
133SSL_CERT_DETAILS = _('Certificate details')133SSL_CERT_DETAILS = _('Certificate details')
134SSL_CONNECT_BUTTON = _('Connect')134SSL_CONNECT_BUTTON = _('Connect')
135SSL_DETAILS_HELP = _('the details ssl certificate we are going to show.')135SSL_DETAILS_HELP = _('the details ssl certificate we are going to show.')
136SSL_DETAILS_TEMPLATE = ('Organization:\t%(organization)s\n'
137 'Common Name:\t%(common_name)s\n'
138 'Locality Name:\t%(locality_name)s\n'
139 'Unit:\t%(unit)s\n'
140 'Country:\t%(country_name)s\n'
141 'State or Province:\t%(state_name)s')
136SSL_DESCRIPTION = _('Open the SSL certificate UI.')142SSL_DESCRIPTION = _('Open the SSL certificate UI.')
137SSL_DIALOG_TITLE = _('SSL Certificate Not Valid')143SSL_DIALOG_TITLE = _('SSL Certificate Not Valid')
138SSL_DOMAIN_HELP = _('the domain whose ssl certificate we are going to show.')144SSL_DOMAIN_HELP = _('the domain whose ssl certificate we are going to show.')
139145
=== modified file 'ubuntu_sso/utils/webclient/__init__.py'
--- ubuntu_sso/utils/webclient/__init__.py 2012-02-09 18:11:02 +0000
+++ ubuntu_sso/utils/webclient/__init__.py 2012-03-20 16:10:09 +0000
@@ -19,6 +19,7 @@
1919
20# pylint: disable=W061120# pylint: disable=W0611
21from ubuntu_sso.utils.webclient.common import (21from ubuntu_sso.utils.webclient.common import (
22 ProxyUnauthorizedError,
22 UnauthorizedError,23 UnauthorizedError,
23 WebClientError,24 WebClientError,
24)25)
@@ -26,8 +27,23 @@
2627
27def is_qt4reactor_installed():28def is_qt4reactor_installed():
28 """Check if the qt4reactor is installed."""29 """Check if the qt4reactor is installed."""
29 reactor = sys.modules.get("twisted.internet.reactor")30 result = False
30 return reactor and getattr(reactor, "qApp", None)31
32 if not 'PyQt4' in sys.modules:
33 return result
34
35 try:
36 from PyQt4.QtCore import QCoreApplication
37 from PyQt4.QtGui import QApplication
38
39 # we could be running a process with or without ui, and those are diff
40 # apps.
41 result = (QCoreApplication.instance() is not None
42 or QApplication.instance() is not None)
43 except ImportError:
44 pass
45
46 return result
3147
3248
33def webclient_module():49def webclient_module():
3450
=== modified file 'ubuntu_sso/utils/webclient/common.py'
--- ubuntu_sso/utils/webclient/common.py 2012-02-09 18:11:02 +0000
+++ ubuntu_sso/utils/webclient/common.py 2012-03-20 16:10:09 +0000
@@ -17,14 +17,23 @@
1717
18import cgi18import cgi
19import collections19import collections
20import os
2021
21from httplib2 import iri2uri22from httplib2 import iri2uri
22from oauth import oauth23from oauth import oauth
23from twisted.internet import defer24from twisted.internet import defer
24from urlparse import urlparse25from urlparse import urlparse
2526
27from ubuntu_sso import USER_SUCCESS, UI_PROXY_CREDS_DIALOG
28from ubuntu_sso.logger import setup_logging
29from ubuntu_sso.utils.runner import spawn_program
30from ubuntu_sso.utils.ui import SSL_DETAILS_TEMPLATE
26from ubuntu_sso.utils.webclient.timestamp import TimestampChecker31from ubuntu_sso.utils.webclient.timestamp import TimestampChecker
2732
33SSL_DIALOG = 'ubuntu-sso-ssl-certificate-qt'
34
35logger = setup_logging("ubuntu_sso.utils.webclient.common")
36
2837
29class WebClientError(Exception):38class WebClientError(Exception):
30 """An http error happened while calling the webservice."""39 """An http error happened while calling the webservice."""
@@ -34,8 +43,12 @@
34 """The request ended with bad_request, unauthorized or forbidden."""43 """The request ended with bad_request, unauthorized or forbidden."""
3544
3645
46class ProxyUnauthorizedError(WebClientError):
47 """Failure raised when there is an issue with the proxy auth."""
48
49
37class Response(object):50class Response(object):
38 """A reponse object."""51 """A response object."""
3952
40 def __init__(self, content, headers=None):53 def __init__(self, content, headers=None):
41 """Initialize this instance."""54 """Initialize this instance."""
@@ -77,10 +90,14 @@
7790
78 timestamp_checker = None91 timestamp_checker = None
7992
80 def __init__(self, username=None, password=None, oauth_sign_plain=False):93 def __init__(self, appname='', username=None, password=None,
94 oauth_sign_plain=False):
81 """Initialize this instance."""95 """Initialize this instance."""
96 self.appname = appname
82 self.username = username97 self.username = username
83 self.password = password98 self.password = password
99 self.proxy_username = None
100 self.proxy_password = None
84 self.oauth_sign_plain = oauth_sign_plain101 self.oauth_sign_plain = oauth_sign_plain
85102
86 def request(self, iri, method="GET", extra_headers=None,103 def request(self, iri, method="GET", extra_headers=None,
@@ -154,3 +171,94 @@
154171
155 def shutdown(self):172 def shutdown(self):
156 """Shut down all pending requests (if possible)."""173 """Shut down all pending requests (if possible)."""
174
175 @defer.inlineCallbacks
176 def _load_proxy_creds_from_keyring(self, domain):
177 """Load the proxy creds from the keyring."""
178 from ubuntu_sso.keyring import Keyring
179 keyring = Keyring()
180 try:
181 creds = yield keyring.get_credentials(str(domain))
182 logger.debug('Got credentials from keyring.')
183 except Exception, e:
184 logger.error('Error when retrieving the creds.')
185 raise WebClientError('Error when retrieving the creds.', e)
186 if creds is not None:
187 # if we are loading the same creds it means that we got the wrong
188 # ones
189 if (self.proxy_username == creds['username'] and
190 self.proxy_password == creds['password']):
191 defer.returnValue(False)
192 else:
193 self.proxy_username = creds['username']
194 self.proxy_password = creds['password']
195 defer.returnValue(True)
196 logger.debug('Proxy creds not in keyring.')
197 defer.returnValue(False)
198
199 def _launch_proxy_creds_dialog(self, domain, retry):
200 """Launch the dialog used to get the creds."""
201 from ubuntu_sso.utils import get_bin_dir
202
203 bin_dir = get_bin_dir()
204 args = (os.path.join(bin_dir, UI_PROXY_CREDS_DIALOG), '--domain',
205 domain)
206 if retry:
207 args += ('--retry',)
208 return spawn_program(args)
209
210 @defer.inlineCallbacks
211 def request_proxy_auth_credentials(self, domain, retry):
212 """Request the auth creds to the user."""
213 if not retry:
214 if (self.proxy_username is not None
215 and self.proxy_password is not None):
216 logger.debug('Not retry and credentials are present.')
217 defer.returnValue(True)
218 else:
219 creds_loaded = yield self._load_proxy_creds_from_keyring(
220 domain)
221 if creds_loaded:
222 defer.returnValue(True)
223
224 try:
225 return_code = yield self._launch_proxy_creds_dialog(domain, retry)
226 except Exception, e:
227 logger.error('Error when running external ui process.')
228 raise WebClientError('Error when running external ui process.', e)
229
230 if return_code == USER_SUCCESS:
231 creds_loaded = yield self._load_proxy_creds_from_keyring(domain)
232 defer.returnValue(creds_loaded)
233 else:
234 logger.debug('Could not retrieve the credentials. Return code: %r',
235 return_code)
236 defer.returnValue(False)
237
238 def format_ssl_details(self, details):
239 """Return a formatted string with the details."""
240 return SSL_DETAILS_TEMPLATE % details
241
242 def _launch_ssl_dialog(self, domain, details):
243 """Launch a dialog used to approve the ssl cert."""
244 from ubuntu_sso.utils import get_bin_dir
245
246 bin_dir = get_bin_dir()
247 args = (os.path.join(bin_dir, SSL_DIALOG), '--domain', domain,
248 '--details', details,
249 '--appname', self.appname)
250 return spawn_program(args)
251
252 def _was_ssl_accepted(self, cert_details):
253 """Return if the cert was already accepted."""
254 # TODO: Ensure that we look at pinned certs in a following branch
255 return False
256
257 @defer.inlineCallbacks
258 def request_ssl_cert_approval(self, domain, details):
259 """Request the user for ssl approval."""
260 if self._was_ssl_accepted(details):
261 defer.returnValue(True)
262
263 return_code = yield self._launch_ssl_dialog(domain, details)
264 defer.returnValue(return_code == USER_SUCCESS)
157265
=== modified file 'ubuntu_sso/utils/webclient/gsettings.py'
--- ubuntu_sso/utils/webclient/gsettings.py 2012-02-10 14:46:16 +0000
+++ ubuntu_sso/utils/webclient/gsettings.py 2012-03-20 16:10:09 +0000
@@ -31,6 +31,24 @@
31 return hostname, username, password31 return hostname, username, password
3232
3333
34def parse_manual_proxy_settings(scheme, gsettings):
35 """Parse the settings for a given scheme."""
36 host, username, pwd = parse_proxy_host(gsettings[scheme + ".host"])
37 settings = {
38 "host": host,
39 "port": gsettings[scheme + ".port"],
40 }
41 if scheme == "http" and gsettings["http.use-authentication"]:
42 username = gsettings["http.authentication-user"]
43 pwd = gsettings["http.authentication-password"]
44 if username is not None and pwd is not None:
45 settings.update({
46 "username": username,
47 "password": pwd,
48 })
49 return settings
50
51
34def get_proxy_settings():52def get_proxy_settings():
35 """Parse the proxy settings as returned by the gsettings executable."""53 """Parse the proxy settings as returned by the gsettings executable."""
36 output = subprocess.check_output(GSETTINGS_CMDLINE.split())54 output = subprocess.check_output(GSETTINGS_CMDLINE.split())
@@ -56,20 +74,9 @@
56 if mode == "none":74 if mode == "none":
57 settings = {}75 settings = {}
58 elif mode == "manual":76 elif mode == "manual":
59 # attempt to parse the host77 settings = {}
60 host, username, pwd = parse_proxy_host(gsettings["http.host"])78 for scheme in ["http", "https"]:
61 settings = {79 settings[scheme] = parse_manual_proxy_settings(scheme, gsettings)
62 "host": host,
63 "port": gsettings["http.port"],
64 }
65 if gsettings["http.use-authentication"]:
66 username = gsettings["http.authentication-user"]
67 pwd = gsettings["http.authentication-password"]
68 if username is not None and pwd is not None:
69 settings.update({
70 "username": username,
71 "password": pwd,
72 })
73 else:80 else:
74 # If mode is automatic the PAC javascript should be interpreted81 # If mode is automatic the PAC javascript should be interpreted
75 # on each request. That is out of scope so it's ignored for now82 # on each request. That is out of scope so it's ignored for now
7683
=== modified file 'ubuntu_sso/utils/webclient/libsoup.py'
--- ubuntu_sso/utils/webclient/libsoup.py 2012-02-07 19:36:50 +0000
+++ ubuntu_sso/utils/webclient/libsoup.py 2012-03-20 16:10:09 +0000
@@ -19,10 +19,12 @@
1919
20from twisted.internet import defer20from twisted.internet import defer
2121
22from ubuntu_sso.logger import setup_logging
22from ubuntu_sso.utils.webclient.common import (23from ubuntu_sso.utils.webclient.common import (
23 BaseWebClient,24 BaseWebClient,
24 HeaderDict,25 HeaderDict,
25 Response,26 Response,
27 ProxyUnauthorizedError,
26 UnauthorizedError,28 UnauthorizedError,
27 WebClientError,29 WebClientError,
28)30)
@@ -30,6 +32,8 @@
30URI_ANONYMOUS_TEMPLATE = "http://{host}:{port}/"32URI_ANONYMOUS_TEMPLATE = "http://{host}:{port}/"
31URI_USERNAME_TEMPLATE = "http://{username}:{password}@{host}:{port}/"33URI_USERNAME_TEMPLATE = "http://{username}:{password}@{host}:{port}/"
3234
35logger = setup_logging("ubuntu_sso.utils.webclient.libsoup")
36
3337
34class WebClient(BaseWebClient):38class WebClient(BaseWebClient):
35 """A webclient with a libsoup backend."""39 """A webclient with a libsoup backend."""
@@ -41,11 +45,12 @@
41 from gi.repository import Soup, SoupGNOME45 from gi.repository import Soup, SoupGNOME
42 self.soup = Soup46 self.soup = Soup
43 self.session = Soup.SessionAsync()47 self.session = Soup.SessionAsync()
44 self.session.add_feature_by_type(SoupGNOME.ProxyResolverGNOME)48 self.session.add_feature(SoupGNOME.ProxyResolverGNOME())
45 self.session.connect("authenticate", self._on_authenticate)49 self.session.connect("authenticate", self._on_authenticate)
4650
47 def _on_message(self, session, message, d):51 def _on_message(self, session, message, d):
48 """Handle the result of an http message."""52 """Handle the result of an http message."""
53 logger.debug('_on_message status code is %s', message.status_code)
49 if message.status_code == httplib.OK:54 if message.status_code == httplib.OK:
50 headers = HeaderDict()55 headers = HeaderDict()
51 response_headers = message.get_property("response-headers")56 response_headers = message.get_property("response-headers")
@@ -57,14 +62,51 @@
57 elif message.status_code == httplib.UNAUTHORIZED:62 elif message.status_code == httplib.UNAUTHORIZED:
58 e = UnauthorizedError(message.reason_phrase)63 e = UnauthorizedError(message.reason_phrase)
59 d.errback(e)64 d.errback(e)
65 elif message.status_code == httplib.PROXY_AUTHENTICATION_REQUIRED:
66 e = ProxyUnauthorizedError(message.reason_phrase)
67 d.errback(e)
60 else:68 else:
61 e = WebClientError(message.reason_phrase)69 e = WebClientError(message.reason_phrase)
62 d.errback(e)70 d.errback(e)
6371
64 def _on_authenticate(self, sesion, message, auth, retrying, data=None):72 @defer.inlineCallbacks
73 def _on_authenticate(self, session, message, auth, retrying, data=None):
65 """Handle the "authenticate" signal."""74 """Handle the "authenticate" signal."""
66 if not retrying and self.username and self.password:75 self.session.pause_message(message)
67 auth.authenticate(self.username, self.password)76 try:
77 logger.debug('_on_authenticate: message status code is %s',
78 message.status_code)
79 if not retrying and self.username and self.password:
80 auth.authenticate(self.username, self.password)
81 if auth.is_for_proxy():
82 logger.debug('_on_authenticate auth is for proxy.')
83 got_creds = yield self.request_proxy_auth_credentials(
84 self.session.props.proxy_uri.host,
85 retrying)
86 if got_creds:
87 logger.debug('Got proxy credentials from user.')
88 auth.authenticate(self.proxy_username, self.proxy_password)
89 finally:
90 self.session.unpause_message(message)
91
92 @defer.inlineCallbacks
93 def _on_proxy_authenticate(self, failure, iri, method="GET",
94 extra_headers=None, oauth_credentials=None, post_content=None):
95 """Deal with wrong settings."""
96 failure.trap(ProxyUnauthorizedError)
97 logger.debug('Proxy settings are wrong.')
98 got_creds = yield self.request_proxy_auth_credentials(
99 self.session.props.proxy_uri.host,
100 True)
101 if got_creds:
102 settings = dict(host=self.session.props.proxy_uri.host,
103 port=self.session.props.proxy_uri.port,
104 username=self.proxy_username,
105 password=self.proxy_password)
106 self.force_use_proxy(settings)
107 response = yield self.request(iri, method, extra_headers,
108 oauth_credentials, post_content)
109 defer.returnValue(response)
68110
69 @defer.inlineCallbacks111 @defer.inlineCallbacks
70 def request(self, iri, method="GET", extra_headers=None,112 def request(self, iri, method="GET", extra_headers=None,
@@ -92,6 +134,8 @@
92 message.request_body.append(post_content)134 message.request_body.append(post_content)
93135
94 self.session.queue_message(message, self._on_message, d)136 self.session.queue_message(message, self._on_message, d)
137 d.addErrback(self._on_proxy_authenticate, iri, method, extra_headers,
138 oauth_credentials, post_content)
95 response = yield d139 response = yield d
96 defer.returnValue(response)140 defer.returnValue(response)
97141
98142
=== modified file 'ubuntu_sso/utils/webclient/qtnetwork.py'
--- ubuntu_sso/utils/webclient/qtnetwork.py 2012-02-07 19:36:50 +0000
+++ ubuntu_sso/utils/webclient/qtnetwork.py 2012-03-20 16:10:09 +0000
@@ -30,29 +30,61 @@
30 QNetworkProxyFactory,30 QNetworkProxyFactory,
31 QNetworkReply,31 QNetworkReply,
32 QNetworkRequest,32 QNetworkRequest,
33 QSslCertificate,
33)34)
34from twisted.internet import defer35from twisted.internet import defer
3536
37from ubuntu_sso.logger import setup_logging
36from ubuntu_sso.utils.webclient.common import (38from ubuntu_sso.utils.webclient.common import (
37 BaseWebClient,39 BaseWebClient,
38 HeaderDict,40 HeaderDict,
41 ProxyUnauthorizedError,
39 Response,42 Response,
40 UnauthorizedError,43 UnauthorizedError,
41 WebClientError,44 WebClientError,
42)45)
43from ubuntu_sso.utils.webclient import gsettings46from ubuntu_sso.utils.webclient import gsettings
4447
48logger = setup_logging("ubuntu_sso.utils.webclient.qtnetwork")
49
50
51def build_proxy(settings_groups):
52 """Create a QNetworkProxy from these settings."""
53 proxy_groups = [
54 ("socks", QNetworkProxy.Socks5Proxy),
55 ("https", QNetworkProxy.HttpProxy),
56 ("http", QNetworkProxy.HttpProxy),
57 ]
58 for group, proxy_type in proxy_groups:
59 if group not in settings_groups:
60 continue
61 settings = settings_groups[group]
62 if "host" in settings and "port" in settings:
63 return QNetworkProxy(proxy_type,
64 hostName=settings.get("host", ""),
65 port=settings.get("port", 0),
66 user=settings.get("username", ""),
67 password=settings.get("password", ""))
68 logger.error("No proxy correctly configured.")
69 return QNetworkProxy(QNetworkProxy.DefaultProxy)
70
4571
46class WebClient(BaseWebClient):72class WebClient(BaseWebClient):
47 """A webclient with a qtnetwork backend."""73 """A webclient with a qtnetwork backend."""
4874
75 proxy_instance = None
76
49 def __init__(self, *args, **kwargs):77 def __init__(self, *args, **kwargs):
50 """Initialize this instance."""78 """Initialize this instance."""
51 super(WebClient, self).__init__(*args, **kwargs)79 super(WebClient, self).__init__(*args, **kwargs)
52 self.nam = QNetworkAccessManager(QCoreApplication.instance())80 self.nam = QNetworkAccessManager(QCoreApplication.instance())
53 self.nam.finished.connect(self._handle_finished)81 self.nam.finished.connect(self._handle_finished)
54 self.nam.authenticationRequired.connect(self._handle_authentication)82 self.nam.authenticationRequired.connect(self._handle_authentication)
83 self.nam.proxyAuthenticationRequired.connect(self.handle_proxy_auth)
84 # Disabled until we make this a per-instance option
85 #self.nam.sslErrors.connect(self._handle_ssl_errors)
55 self.replies = {}86 self.replies = {}
87 self.proxy_retry = False
56 self.setup_proxy()88 self.setup_proxy()
5789
58 def setup_proxy(self):90 def setup_proxy(self):
@@ -60,11 +92,43 @@
60 # QtNetwork knows how to use the system settings on both Win and Mac92 # QtNetwork knows how to use the system settings on both Win and Mac
61 if sys.platform.startswith("linux"):93 if sys.platform.startswith("linux"):
62 settings = gsettings.get_proxy_settings()94 settings = gsettings.get_proxy_settings()
63 if settings:95 enabled = len(settings) > 0
64 self.force_use_proxy(settings)96 if enabled and WebClient.proxy_instance is None:
97 proxy = build_proxy(settings)
98 QNetworkProxy.setApplicationProxy(proxy)
99 WebClient.proxy_instance = proxy
100 elif enabled and WebClient.proxy_instance:
101 logger.info("Proxy already in use.")
102 else:
103 logger.info("Proxy is disabled.")
65 else:104 else:
66 QNetworkProxyFactory.setUseSystemConfiguration(True)105 QNetworkProxyFactory.setUseSystemConfiguration(True)
67106
107 def handle_proxy_auth(self, proxy, authenticator):
108 """Proxy authentication is required."""
109 logger.info("auth_required %r, %r", self.proxy_username,
110 proxy.hostName())
111 if (self.proxy_username is not None and
112 self.proxy_username != str(authenticator.user())):
113 authenticator.setUser(self.proxy_username)
114 WebClient.proxy_instance.setUser(self.proxy_username)
115 if (self.proxy_password is not None and
116 self.proxy_password != str(authenticator.password())):
117 authenticator.setPassword(self.proxy_password)
118 WebClient.proxy_instance.setPassword(self.proxy_password)
119
120 def _perform_request(self, request, method, post_buffer):
121 """Return a deferred that will be fired with a Response object."""
122 d = defer.Deferred()
123 if method == "GET":
124 reply = self.nam.get(request)
125 elif method == "HEAD":
126 reply = self.nam.head(request)
127 else:
128 reply = self.nam.sendCustomRequest(request, method, post_buffer)
129 self.replies[reply] = d
130 return d
131
68 @defer.inlineCallbacks132 @defer.inlineCallbacks
69 def request(self, iri, method="GET", extra_headers=None,133 def request(self, iri, method="GET", extra_headers=None,
70 oauth_credentials=None, post_content=None):134 oauth_credentials=None, post_content=None):
@@ -86,23 +150,30 @@
86 for key, value in headers.iteritems():150 for key, value in headers.iteritems():
87 request.setRawHeader(key, value)151 request.setRawHeader(key, value)
88152
89 d = defer.Deferred()153 post_buffer = QBuffer()
90 if method == "GET":154 post_buffer.setData(post_content)
91 reply = self.nam.get(request)155 try:
92 elif method == "HEAD":156 result = yield self._perform_request(request, method, post_buffer)
93 reply = self.nam.head(request)157 except ProxyUnauthorizedError, e:
94 else:158 app_proxy = QNetworkProxy.applicationProxy()
95 post_buffer = QBuffer()159 proxy_host = app_proxy.hostName() if app_proxy else "proxy server"
96 post_buffer.setData(post_content)160 got_creds = yield self.request_proxy_auth_credentials(
97 reply = self.nam.sendCustomRequest(request, method, post_buffer)161 proxy_host, self.proxy_retry)
98 self.replies[reply] = d162 if got_creds:
99 result = yield d163 self.proxy_retry = True
164 result = yield self.request(iri, method, extra_headers,
165 oauth_credentials, post_content)
166 else:
167 excp = WebClientError('Proxy creds needed.', e)
168 defer.returnValue(excp)
100 defer.returnValue(result)169 defer.returnValue(result)
101170
102 def _handle_authentication(self, reply, authenticator):171 def _handle_authentication(self, reply, authenticator):
103 """The reply needs authentication."""172 """The reply needs authentication."""
104 authenticator.setUser(self.username)173 if authenticator.user() != self.username:
105 authenticator.setPassword(self.password)174 authenticator.setUser(self.username)
175 if authenticator.password() != self.password:
176 authenticator.setPassword(self.password)
106177
107 def _handle_finished(self, reply):178 def _handle_finished(self, reply):
108 """The reply has finished processing."""179 """The reply has finished processing."""
@@ -118,20 +189,51 @@
118 d.callback(response)189 d.callback(response)
119 else:190 else:
120 error_string = reply.errorString()191 error_string = reply.errorString()
192 logger.debug('_handle_finished error (%s,%s).', error,
193 error_string)
121 if error == QNetworkReply.AuthenticationRequiredError:194 if error == QNetworkReply.AuthenticationRequiredError:
122 exception = UnauthorizedError(error_string, content)195 exception = UnauthorizedError(error_string, content)
196 elif error == QNetworkReply.ProxyAuthenticationRequiredError:
197 # we are going thru a proxy and we did not auth
198 exception = ProxyUnauthorizedError(error_string, content)
123 else:199 else:
124 exception = WebClientError(error_string, content)200 exception = WebClientError(error_string, content)
125 d.errback(exception)201 d.errback(exception)
126202
127 def force_use_proxy(self, settings):203 def _get_certificate_details(self, cert):
204 """Return an string with the details of the certificate."""
205 detail_titles = {QSslCertificate.Organization: 'organization',
206 QSslCertificate.CommonName: 'common_name',
207 QSslCertificate.LocalityName: 'locality_name',
208 QSslCertificate.OrganizationalUnitName: 'unit',
209 QSslCertificate.CountryName: 'country_name',
210 QSslCertificate.StateOrProvinceName: 'state_name'}
211 details = {}
212 for info, title in detail_titles.iteritems():
213 details[title] = str(cert.issuerInfo(info))
214 return self.format_ssl_details(details)
215
216 def _get_certificate_host(self, cert):
217 """Return the host of the cert."""
218 return str(cert.issuerInfo(QSslCertificate.CommonName))
219
220 @defer.inlineCallbacks
221 def _handle_ssl_errors(self, reply, errors):
222 """Handle the case in which we got an ssl error."""
223 # ask the user if the cer should be trusted
224 cert = errors[0].certificate()
225 trust_cert = yield self.request_ssl_cert_approval(
226 self._get_certificate_host(cert),
227 self._get_certificate_details(cert))
228 if trust_cert:
229 reply.ignoreSslErrors()
230
231 def force_use_proxy(self, https_settings):
128 """Setup this webclient to use the given proxy settings."""232 """Setup this webclient to use the given proxy settings."""
129 proxy = QNetworkProxy(QNetworkProxy.HttpProxy,233 settings = {"https": https_settings}
130 hostName=settings.get("host", ""),234 proxy = build_proxy(settings)
131 port=settings.get("port", 0),235 QNetworkProxy.setApplicationProxy(proxy)
132 user=settings.get("username", ""),236 WebClient.proxy_instance = proxy
133 password=settings.get("password", ""))
134 self.nam.setProxy(proxy)
135237
136 def shutdown(self):238 def shutdown(self):
137 """Shut down all pending requests (if possible)."""239 """Shut down all pending requests (if possible)."""
138240
=== modified file 'ubuntu_sso/utils/webclient/tests/__init__.py'
--- ubuntu_sso/utils/webclient/tests/__init__.py 2012-02-07 19:36:50 +0000
+++ ubuntu_sso/utils/webclient/tests/__init__.py 2012-03-20 16:10:09 +0000
@@ -17,9 +17,48 @@
17"""Tests for the proxy-aware webclient."""17"""Tests for the proxy-aware webclient."""
1818
19from twisted.application import internet, service19from twisted.application import internet, service
20from twisted.internet import ssl
20from twisted.web import http, server21from twisted.web import http, server
2122
2223
24# Some settings are not used as described in:
25# https://bugzilla.gnome.org/show_bug.cgi?id=648237
26
27TEMPLATE_GSETTINGS_OUTPUT = """\
28org.gnome.system.proxy autoconfig-url '{autoconfig_url}'
29org.gnome.system.proxy ignore-hosts {ignore_hosts:s}
30org.gnome.system.proxy mode '{mode}'
31org.gnome.system.proxy.ftp host '{ftp_host}'
32org.gnome.system.proxy.ftp port {ftp_port}
33org.gnome.system.proxy.http authentication-password '{auth_password}'
34org.gnome.system.proxy.http authentication-user '{auth_user}'
35org.gnome.system.proxy.http host '{http_host}'
36org.gnome.system.proxy.http port {http_port}
37org.gnome.system.proxy.http use-authentication {http_use_auth}
38org.gnome.system.proxy.https host '{https_host}'
39org.gnome.system.proxy.https port {https_port}
40org.gnome.system.proxy.socks host '{socks_host}'
41org.gnome.system.proxy.socks port {socks_port}
42"""
43
44BASE_GSETTINGS_VALUES = {
45 "autoconfig_url": "",
46 "ignore_hosts": ["localhost", "127.0.0.0/8"],
47 "mode": "none",
48 "ftp_host": "",
49 "ftp_port": 0,
50 "auth_password": "",
51 "auth_user": "",
52 "http_host": "",
53 "http_port": 0,
54 "http_use_auth": "false",
55 "https_host": "",
56 "https_port": 0,
57 "socks_host": "",
58 "socks_port": 0,
59}
60
61
23class SaveHTTPChannel(http.HTTPChannel):62class SaveHTTPChannel(http.HTTPChannel):
24 """A save protocol to be used in tests."""63 """A save protocol to be used in tests."""
2564
@@ -48,15 +87,26 @@
48class BaseMockWebServer(object):87class BaseMockWebServer(object):
49 """A mock webserver for testing"""88 """A mock webserver for testing"""
5089
51 def __init__(self):90 def __init__(self, ssl_settings=None):
52 """Start up this instance."""91 """Start up this instance."""
53 self.root = self.get_root_resource()92 self.root = self.get_root_resource()
54 self.site = SaveSite(self.root)93 self.site = SaveSite(self.root)
55 application = service.Application('web')94 application = service.Application('web')
56 self.service_collection = service.IServiceCollection(application)95 self.service_collection = service.IServiceCollection(application)
57 #pylint: disable=E110196 #pylint: disable=E1101
58 self.tcpserver = internet.TCPServer(0, self.site)97 ssl_context = None
59 self.tcpserver.setServiceParent(self.service_collection)98 if (ssl_settings is not None
99 and 'key' in ssl_settings
100 and 'cert' in ssl_settings):
101 ssl_context = ssl.DefaultOpenSSLContextFactory(ssl_settings['key'],
102 ssl_settings['cert'])
103 self.ssl_server = internet.SSLServer(0, self.site, ssl_context)
104 else:
105 self.ssl_server = None
106 self.server = internet.TCPServer(0, self.site)
107 self.server.setServiceParent(self.service_collection)
108 if self.ssl_server:
109 self.ssl_server.setServiceParent(self.service_collection)
60 self.service_collection.startService()110 self.service_collection.startService()
61111
62 def get_root_resource(self):112 def get_root_resource(self):
@@ -65,9 +115,27 @@
65115
66 def get_iri(self):116 def get_iri(self):
67 """Build the iri for this mock server."""117 """Build the iri for this mock server."""
68 #pylint: disable=W0212118 url = u"http://127.0.0.1:%d/"
69 port_num = self.tcpserver._port.getHost().port119 return url % self.get_port()
70 return u"http://127.0.0.1:%d/" % port_num120
121 def get_ssl_iri(self):
122 """Build the ssl iri for this mock server."""
123 if self.ssl_server:
124 url = u"https://127.0.0.1:%d/"
125 return url % self.get_ssl_port()
126
127 def get_port(self):
128 """Return the port where we are listening."""
129 # pylint: disable=W0212
130 return self.server._port.getHost().port
131 # pylint: enable=W0212
132
133 def get_ssl_port(self):
134 """Return the ssl port where we are listening."""
135 # pylint: disable=W0212
136 if self.ssl_server:
137 return self.ssl_server._port.getHost().port
138 # pylint: enable=W0212
71139
72 def stop(self):140 def stop(self):
73 """Shut it down."""141 """Shut it down."""
74142
=== modified file 'ubuntu_sso/utils/webclient/tests/test_gsettings.py'
--- ubuntu_sso/utils/webclient/tests/test_gsettings.py 2012-02-10 19:20:33 +0000
+++ ubuntu_sso/utils/webclient/tests/test_gsettings.py 2012-03-20 16:10:09 +0000
@@ -18,43 +18,10 @@
18from twisted.trial.unittest import TestCase18from twisted.trial.unittest import TestCase
1919
20from ubuntu_sso.utils.webclient import gsettings20from ubuntu_sso.utils.webclient import gsettings
2121from ubuntu_sso.utils.webclient.tests import (
22# Some settings are not used as described in:22 BASE_GSETTINGS_VALUES,
23# https://bugzilla.gnome.org/show_bug.cgi?id=64823723 TEMPLATE_GSETTINGS_OUTPUT,
2424)
25TEMPLATE_GSETTINGS_OUTPUT = """\
26org.gnome.system.proxy autoconfig-url '{autoconfig_url}'
27org.gnome.system.proxy ignore-hosts {ignore_hosts:s}
28org.gnome.system.proxy mode '{mode}'
29org.gnome.system.proxy.ftp host '{ftp_host}'
30org.gnome.system.proxy.ftp port {ftp_port}
31org.gnome.system.proxy.http authentication-password '{auth_password}'
32org.gnome.system.proxy.http authentication-user '{auth_user}'
33org.gnome.system.proxy.http host '{http_host}'
34org.gnome.system.proxy.http port {http_port}
35org.gnome.system.proxy.http use-authentication {http_use_auth}
36org.gnome.system.proxy.https host '{https_host}'
37org.gnome.system.proxy.https port {https_port}
38org.gnome.system.proxy.socks host '{socks_host}'
39org.gnome.system.proxy.socks port {socks_port}
40"""
41
42BASE_GSETTINGS_VALUES = {
43 "autoconfig_url": "",
44 "ignore_hosts": ["localhost", "127.0.0.0/8"],
45 "mode": "none",
46 "ftp_host": "",
47 "ftp_port": 0,
48 "auth_password": "",
49 "auth_user": "",
50 "http_host": "",
51 "http_port": 0,
52 "http_use_auth": "false",
53 "https_host": "",
54 "https_port": 0,
55 "socks_host": "",
56 "socks_port": 0,
57}
5825
5926
60class ProxySettingsTestCase(TestCase):27class ProxySettingsTestCase(TestCase):
@@ -83,25 +50,33 @@
83 ps = gsettings.get_proxy_settings()50 ps = gsettings.get_proxy_settings()
84 self.assertEqual(ps, expected)51 self.assertEqual(ps, expected)
8552
53 def _assert_parser_anonymous(self, scheme):
54 """Assert the parsing of anonymous settings."""
55 template_values = dict(BASE_GSETTINGS_VALUES)
56 expected_host = "expected_host"
57 expected_port = 54321
58 expected = {
59 "host": expected_host,
60 "port": expected_port,
61 }
62 template_values.update({
63 "mode": "manual",
64 scheme + "_host": expected_host,
65 scheme + "_port": expected_port,
66 })
67 fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
68 self.patch(gsettings.subprocess, "check_output",
69 lambda _: fake_output)
70 ps = gsettings.get_proxy_settings()
71 self.assertEqual(ps[scheme], expected)
72
86 def test_gsettings_parser_http_anonymous(self):73 def test_gsettings_parser_http_anonymous(self):
87 """Test a parser of gsettings."""74 """Test a parser of gsettings."""
88 template_values = dict(BASE_GSETTINGS_VALUES)75 self._assert_parser_anonymous('http')
89 expected_host = "expected_host"76
90 expected_port = 5432177 def test_gsettings_parser_https_anonymus(self):
91 expected = {78 """Test a parser of gsettings."""
92 "host": expected_host,79 self._assert_parser_anonymous('https')
93 "port": expected_port,
94 }
95 template_values.update({
96 "mode": "manual",
97 "http_host": expected_host,
98 "http_port": expected_port,
99 })
100 fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
101 self.patch(gsettings.subprocess, "check_output",
102 lambda _: fake_output)
103 ps = gsettings.get_proxy_settings()
104 self.assertEqual(ps, expected)
10580
106 def test_gsettings_parser_http_authenticated(self):81 def test_gsettings_parser_http_authenticated(self):
107 """Test a parser of gsettings."""82 """Test a parser of gsettings."""
@@ -128,9 +103,9 @@
128 self.patch(gsettings.subprocess, "check_output",103 self.patch(gsettings.subprocess, "check_output",
129 lambda _: fake_output)104 lambda _: fake_output)
130 ps = gsettings.get_proxy_settings()105 ps = gsettings.get_proxy_settings()
131 self.assertEqual(ps, expected)106 self.assertEqual(ps["http"], expected)
132107
133 def test_gsettings_parser_authenticated_url(self):108 def _assert_parser_authenticated_url(self, scheme):
134 """Test a parser of gsettings with creds in the url."""109 """Test a parser of gsettings with creds in the url."""
135 template_values = dict(BASE_GSETTINGS_VALUES)110 template_values = dict(BASE_GSETTINGS_VALUES)
136 expected_host = "expected_host"111 expected_host = "expected_host"
@@ -147,15 +122,23 @@
147 }122 }
148 template_values.update({123 template_values.update({
149 "mode": "manual",124 "mode": "manual",
150 "http_host": composed_url,125 scheme + "_host": composed_url,
151 "http_port": expected_port,126 scheme + "_port": expected_port,
152 "http_use_auth": "false",127 "http_use_auth": "false",
153 })128 })
154 fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)129 fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
155 self.patch(gsettings.subprocess, "check_output",130 self.patch(gsettings.subprocess, "check_output",
156 lambda _: fake_output)131 lambda _: fake_output)
157 ps = gsettings.get_proxy_settings()132 ps = gsettings.get_proxy_settings()
158 self.assertEqual(ps, expected)133 self.assertEqual(ps[scheme], expected)
134
135 def test_gsettings_parser_http_authenticated_url(self):
136 """Test a parser of gsettings with creds in the url."""
137 self._assert_parser_authenticated_url('http')
138
139 def test_gsettings_parser_https_authenticated_url(self):
140 """Test a parser of gsettings with creds in the url."""
141 self._assert_parser_authenticated_url('https')
159142
160 def test_gsettings_auth_over_url(self):143 def test_gsettings_auth_over_url(self):
161 """Test that the settings are more important that the url."""144 """Test that the settings are more important that the url."""
@@ -166,7 +149,7 @@
166 expected_password = "very secret password"149 expected_password = "very secret password"
167 composed_url = '%s:%s@%s' % ('user', 'random',150 composed_url = '%s:%s@%s' % ('user', 'random',
168 expected_host)151 expected_host)
169 expected = {152 http_expected = {
170 "host": expected_host,153 "host": expected_host,
171 "port": expected_port,154 "port": expected_port,
172 "username": expected_user,155 "username": expected_user,
@@ -184,7 +167,7 @@
184 self.patch(gsettings.subprocess, "check_output",167 self.patch(gsettings.subprocess, "check_output",
185 lambda _: fake_output)168 lambda _: fake_output)
186 ps = gsettings.get_proxy_settings()169 ps = gsettings.get_proxy_settings()
187 self.assertEqual(ps, expected)170 self.assertEqual(ps["http"], http_expected)
188171
189172
190class ParseProxyHostTestCase(TestCase):173class ParseProxyHostTestCase(TestCase):
191174
=== modified file 'ubuntu_sso/utils/webclient/tests/test_webclient.py'
--- ubuntu_sso/utils/webclient/tests/test_webclient.py 2012-02-13 13:14:18 +0000
+++ ubuntu_sso/utils/webclient/tests/test_webclient.py 2012-03-20 16:10:09 +0000
@@ -16,9 +16,12 @@
16"""Integration tests for the proxy-enabled webclient."""16"""Integration tests for the proxy-enabled webclient."""
1717
18import os18import os
19import shutil
19import sys20import sys
20import urllib21import urllib
2122
23from OpenSSL import crypto
24from socket import gethostname
22from twisted.cred import checkers, portal25from twisted.cred import checkers, portal
23from twisted.internet import defer26from twisted.internet import defer
24from twisted.web import guard, http, resource27from twisted.web import guard, http, resource
@@ -27,7 +30,15 @@
27from ubuntuone.devtools.testcases import TestCase30from ubuntuone.devtools.testcases import TestCase
28from ubuntuone.devtools.testcases.squid import SquidTestCase31from ubuntuone.devtools.testcases.squid import SquidTestCase
2932
33from ubuntu_sso import (
34 keyring,
35 EXCEPTION_RAISED,
36 USER_SUCCESS,
37 USER_CANCELLATION,
38)
30from ubuntu_sso.utils import webclient39from ubuntu_sso.utils import webclient
40from ubuntu_sso.utils.ui import SSL_DETAILS_TEMPLATE
41from ubuntu_sso.utils.webclient import gsettings, txweb
31from ubuntu_sso.utils.webclient.common import BaseWebClient, HeaderDict, oauth42from ubuntu_sso.utils.webclient.common import BaseWebClient, HeaderDict, oauth
32from ubuntu_sso.utils.webclient.tests import BaseMockWebServer43from ubuntu_sso.utils.webclient.tests import BaseMockWebServer
3344
@@ -188,9 +199,13 @@
188 return root199 return root
189200
190201
191class FakeReactor(object):202class FakeQApplication(object):
192 """A fake reactor object."""203 """A fake Qt module."""
193 qApp = "Sample qapp"204
205 @classmethod
206 def instance(cls):
207 """Return the instance."""
208 return cls
194209
195210
196class ModuleSelectionTestCase(TestCase):211class ModuleSelectionTestCase(TestCase):
@@ -201,10 +216,11 @@
201 self.patch(sys, "modules", {})216 self.patch(sys, "modules", {})
202 self.assertFalse(webclient.is_qt4reactor_installed())217 self.assertFalse(webclient.is_qt4reactor_installed())
203218
204 def test_is_qt4reactor_installed_installed(self):219 def test_is_qt4reactor_installed_installed_core(self):
205 """When the qt4reactor is installed, it returns true."""220 """When the qt4reactor is installed, it returns true."""
206 fake_sysmodules = {"twisted.internet.reactor": FakeReactor()}221 from PyQt4 import QtCore
207 self.patch(sys, "modules", fake_sysmodules)222
223 self.patch(QtCore, 'QCoreApplication', FakeQApplication)
208 self.assertTrue(webclient.is_qt4reactor_installed())224 self.assertTrue(webclient.is_qt4reactor_installed())
209225
210 def assert_module_name(self, module, expected_name):226 def assert_module_name(self, module, expected_name):
@@ -230,6 +246,7 @@
230 """Test for the webclient."""246 """Test for the webclient."""
231247
232 timeout = 1248 timeout = 1
249 webclient_factory = webclient.webclient_factory
233250
234 @defer.inlineCallbacks251 @defer.inlineCallbacks
235 def setUp(self):252 def setUp(self):
@@ -237,7 +254,7 @@
237 self.ws = MockWebServer()254 self.ws = MockWebServer()
238 self.addCleanup(self.ws.stop)255 self.addCleanup(self.ws.stop)
239 self.base_iri = self.ws.get_iri()256 self.base_iri = self.ws.get_iri()
240 self.wc = webclient.webclient_factory()257 self.wc = self.webclient_factory()
241 self.addCleanup(self.wc.shutdown)258 self.addCleanup(self.wc.shutdown)
242259
243 @defer.inlineCallbacks260 @defer.inlineCallbacks
@@ -306,13 +323,22 @@
306 @defer.inlineCallbacks323 @defer.inlineCallbacks
307 def test_send_basic_auth(self):324 def test_send_basic_auth(self):
308 """The basic authentication headers are sent."""325 """The basic authentication headers are sent."""
309 other_wc = webclient.webclient_factory(username=SAMPLE_USERNAME,326 other_wc = self.webclient_factory(username=SAMPLE_USERNAME,
310 password=SAMPLE_PASSWORD)327 password=SAMPLE_PASSWORD)
311 self.addCleanup(other_wc.shutdown)328 self.addCleanup(other_wc.shutdown)
312 result = yield other_wc.request(self.base_iri + GUARDED)329 result = yield other_wc.request(self.base_iri + GUARDED)
313 self.assertEqual(SAMPLE_RESOURCE, result.content)330 self.assertEqual(SAMPLE_RESOURCE, result.content)
314331
315 @defer.inlineCallbacks332 @defer.inlineCallbacks
333 def test_send_basic_auth_wrong_credentials(self):
334 """Wrong credentials returns a webclient error."""
335 other_wc = self.webclient_factory(username=SAMPLE_USERNAME,
336 password="wrong password!")
337 self.addCleanup(other_wc.shutdown)
338 yield self.assertFailure(other_wc.request(self.base_iri + GUARDED),
339 webclient.UnauthorizedError)
340
341 @defer.inlineCallbacks
316 def test_request_is_oauth_signed(self):342 def test_request_is_oauth_signed(self):
317 """The request is oauth signed."""343 """The request is oauth signed."""
318 tsc = self.wc.get_timestamp_checker()344 tsc = self.wc.get_timestamp_checker()
@@ -348,6 +374,57 @@
348 "The type of %r must be bytes" % result.content)374 "The type of %r must be bytes" % result.content)
349375
350376
377class FakeSavingReactor(object):
378 """A fake reactor that saves connection attempts."""
379
380 def __init__(self):
381 """Initialize this fake instance."""
382 self.connections = []
383
384 def connectTCP(self, host, port, factory, *args):
385 """Fake the connection."""
386 self.connections.append((host, port, args))
387 factory.response_headers = {}
388 factory.deferred = defer.succeed("response content")
389
390 def connectSSL(self, host, port, factory, *args):
391 """Fake the connection."""
392 self.connections.append((host, port, args))
393 factory.response_headers = {}
394 factory.deferred = defer.succeed("response content")
395
396
397class TxWebClientTestCase(WebClientTestCase):
398 """Test case for txweb."""
399
400 webclient_factory = txweb.WebClient
401
402
403class TxWebClientReactorReplaceableTestCase(TestCase):
404 """In the txweb client the reactor is replaceable."""
405
406 timeout = 3
407 FAKE_HOST = u"fake"
408 FAKE_IRI_TEMPLATE = u"%%s://%s/fake_page" % FAKE_HOST
409
410 @defer.inlineCallbacks
411 def _test_replaceable_reactor(self, iri):
412 """The reactor can be replaced with the tunnel client."""
413 fake_reactor = FakeSavingReactor()
414 wc = txweb.WebClient(fake_reactor)
415 _response = yield wc.request(iri)
416 host, _port, _args = fake_reactor.connections[0]
417 self.assertEqual(host, self.FAKE_HOST)
418
419 def test_replaceable_reactor_http(self):
420 """Test the replaceable reactor with an http iri."""
421 return self._test_replaceable_reactor(self.FAKE_IRI_TEMPLATE % "http")
422
423 def test_replaceable_reactor_https(self):
424 """Test the replaceable reactor with an https iri."""
425 return self._test_replaceable_reactor(self.FAKE_IRI_TEMPLATE % "https")
426
427
351class TimestampCheckerTestCase(TestCase):428class TimestampCheckerTestCase(TestCase):
352 """Tests for the timestampchecker classmethod."""429 """Tests for the timestampchecker classmethod."""
353430
@@ -375,6 +452,8 @@
375class BasicProxyTestCase(SquidTestCase):452class BasicProxyTestCase(SquidTestCase):
376 """Test that the proxy works at all."""453 """Test that the proxy works at all."""
377454
455 timeout = 3
456
378 @defer.inlineCallbacks457 @defer.inlineCallbacks
379 def setUp(self):458 def setUp(self):
380 yield super(BasicProxyTestCase, self).setUp()459 yield super(BasicProxyTestCase, self).setUp()
@@ -404,10 +483,119 @@
404 result = yield self.wc.request(self.base_iri + SIMPLERESOURCE)483 result = yield self.wc.request(self.base_iri + SIMPLERESOURCE)
405 self.assert_header_contains(result.headers["Via"], "squid")484 self.assert_header_contains(result.headers["Via"], "squid")
406485
486 @defer.inlineCallbacks
487 def test_auth_proxy_is_used_creds_requested(self):
488 """The authenticated proxy is used by the webclient."""
489 settings = self.get_auth_proxy_settings()
490 partial_settings = dict(host=settings['host'], port=settings['port'])
491
492 def fake_creds_request(domain, retry):
493 """Fake user interaction."""
494 self.wc.proxy_username = settings['username']
495 self.wc.proxy_password = settings['password']
496 return defer.succeed(True)
497
498 self.patch(self.wc, 'request_proxy_auth_credentials',
499 fake_creds_request)
500
501 self.wc.force_use_proxy(partial_settings)
502 result = yield self.wc.request(self.base_iri + SIMPLERESOURCE)
503 self.assert_header_contains(result.headers["Via"], "squid")
504
505 @defer.inlineCallbacks
506 def test_auth_proxy_is_requested_creds_bad_details(self):
507 """Test using wrong credentials with the proxy."""
508 settings = self.get_auth_proxy_settings()
509 wrong_settings = dict(host=settings['host'], port=settings['port'],
510 username=settings['password'],
511 password=settings['username'])
512
513 def fake_creds_request(domain, retry):
514 """Fake user interaction."""
515 self.wc.proxy_username = settings['username']
516 self.wc.proxy_password = settings['password']
517 return defer.succeed(True)
518
519 self.patch(self.wc, 'request_proxy_auth_credentials',
520 fake_creds_request)
521
522 self.wc.force_use_proxy(wrong_settings)
523 result = yield self.wc.request(self.base_iri + SIMPLERESOURCE)
524 self.assert_header_contains(result.headers["Via"], "squid")
525
526 @defer.inlineCallbacks
527 def test_auth_proxy_is_requested_creds_bad_details_user(self):
528 """Test using no creds and user providing the wrong ones."""
529 settings = self.get_auth_proxy_settings()
530 partial_settings = dict(host=settings['host'], port=settings['port'])
531
532 def fake_creds_request(domain, retry):
533 """Fake user interaction."""
534 if retry:
535 self.wc.proxy_username = settings['username']
536 self.wc.proxy_password = settings['password']
537 else:
538 self.wc.proxy_username = settings['password']
539 self.wc.proxy_password = settings['username']
540 return defer.succeed(True)
541
542 self.patch(self.wc, 'request_proxy_auth_credentials',
543 fake_creds_request)
544
545 self.wc.force_use_proxy(partial_settings)
546 result = yield self.wc.request(self.base_iri + SIMPLERESOURCE)
547 self.assert_header_contains(result.headers["Via"], "squid")
548
549 @defer.inlineCallbacks
550 def test_auth_proxy_is_requested_creds_bad_details_everywhere(self):
551 """Test when we pass the wrong settings and get the wrong settings."""
552 settings = self.get_auth_proxy_settings()
553 wrong_settings = dict(host=settings['host'], port=settings['port'],
554 username=settings['password'],
555 password=settings['username'])
556
557 def fake_creds_request(domain, retry):
558 """Fake user interaction."""
559 if retry:
560 self.wc.proxy_username = settings['username']
561 self.wc.proxy_password = settings['password']
562 else:
563 self.wc.proxy_username = settings['password']
564 self.wc.proxy_password = settings['username']
565 return defer.succeed(True)
566
567 self.patch(self.wc, 'request_proxy_auth_credentials',
568 fake_creds_request)
569
570 self.wc.force_use_proxy(wrong_settings)
571 result = yield self.wc.request(self.base_iri + SIMPLERESOURCE)
572 self.assert_header_contains(result.headers["Via"], "squid")
573
574 def test_auth_proxy_is_requested_user_cancels(self):
575 """Test when the user cancels the creds dialog."""
576 settings = self.get_auth_proxy_settings()
577 partial_settings = dict(host=settings['host'], port=settings['port'])
578
579 def fake_creds_request(domain, retry):
580 """Fake user interaction."""
581 return defer.succeed(False)
582
583 self.patch(self.wc, 'request_proxy_auth_credentials',
584 fake_creds_request)
585
586 self.wc.force_use_proxy(partial_settings)
587 self.failUnlessFailure(self.wc.request(self.base_iri + SIMPLERESOURCE),
588 webclient.WebClientError)
589
407 if WEBCLIENT_MODULE_NAME.endswith(".txweb"):590 if WEBCLIENT_MODULE_NAME.endswith(".txweb"):
408 reason = "txweb does not support proxies."591 reason = "txweb does not support proxies."
409 test_anonymous_proxy_is_used.skip = reason592 test_anonymous_proxy_is_used.skip = reason
410 test_authenticated_proxy_is_used.skip = reason593 test_authenticated_proxy_is_used.kip = reason
594 test_auth_proxy_is_used_creds_requested.skip = reason
595 test_auth_proxy_is_requested_creds_bad_details.skip = reason
596 test_auth_proxy_is_requested_creds_bad_details_user.skip = reason
597 test_auth_proxy_is_requested_creds_bad_details_everywhere.skip = reason
598 test_auth_proxy_is_requested_user_cancels.skip = reason
411599
412600
413class HeaderDictTestCase(TestCase):601class HeaderDictTestCase(TestCase):
@@ -562,3 +750,315 @@
562 """Test for the oauth signing code using HMAC-SHA1."""750 """Test for the oauth signing code using HMAC-SHA1."""
563751
564 oauth_sign = "HMAC-SHA1"752 oauth_sign = "HMAC-SHA1"
753
754
755class FakeKeyring(object):
756 """A fake keyring."""
757
758 def __init__(self, creds):
759 """A fake keyring."""
760 self.creds = creds
761
762 def __call__(self):
763 """Fake instance callable."""
764 return self
765
766 def get_credentials(self, domain):
767 """A fake get_credentials."""
768 if isinstance(self.creds, Exception):
769 return defer.fail(self.creds)
770 return defer.succeed(self.creds)
771
772
773class RequestProxyAuthTestCase(TestCase):
774 """Test the spawn of the creds dialog."""
775
776 @defer.inlineCallbacks
777 def setUp(self):
778 """Set the different tests."""
779 yield super(RequestProxyAuthTestCase, self).setUp()
780 self.wc = webclient.webclient_factory()
781 self.addCleanup(self.wc.shutdown)
782 self.domain = 'domain'
783 self.retry = False
784 self.creds = dict(username='username', password='password')
785
786 self.keyring = FakeKeyring(self.creds)
787 self.patch(keyring, 'Keyring', self.keyring)
788
789 self.spawn_return_code = USER_SUCCESS
790
791 def fake_spawn_process(args):
792 """Fake spawning a process."""
793 if isinstance(self.spawn_return_code, Exception):
794 return defer.fail(self.spawn_return_code)
795 return defer.succeed(self.spawn_return_code)
796
797 self.patch(webclient.common, 'spawn_program', fake_spawn_process)
798
799 def test_spawn_error(self):
800 """Test the case when we cannot spawn the process."""
801 self.spawn_return_code = Exception()
802 self.failUnlessFailure(self.wc.request_proxy_auth_credentials(
803 self.domain, True),
804 webclient.WebClientError)
805
806 @defer.inlineCallbacks
807 def test_creds_acquired(self):
808 """Test the case in which we do get the creds."""
809 got_creds = yield self.wc.request_proxy_auth_credentials(self.domain,
810 self.retry)
811 self.assertTrue(got_creds, 'Return true when creds are present.')
812 self.assertEqual(self.wc.proxy_username, self.creds['username'])
813 self.assertEqual(self.wc.proxy_password, self.creds['password'])
814
815 def test_creds_acquired_keyring_error(self):
816 """Test the case in which we cannot access the keyring."""
817 self.keyring.creds = Exception()
818 self.failUnlessFailure(self.wc.request_proxy_auth_credentials(
819 self.domain, self.retry),
820 webclient.WebClientError)
821
822 @defer.inlineCallbacks
823 def test_creds_none(self):
824 """Test the case in which we got None from the keyring."""
825 self.keyring.creds = None
826 got_creds = yield self.wc.request_proxy_auth_credentials(self.domain,
827 self.retry)
828 self.assertFalse(got_creds, 'Return false when creds are not present.')
829
830 def test_user_cancelation(self):
831 """Test the case in which the user cancels."""
832 self.spawn_return_code = USER_CANCELLATION
833 got_creds = yield self.wc.request_proxy_auth_credentials(self.domain,
834 self.retry)
835 self.assertFalse(got_creds, 'Return true when user cancels.')
836
837 def test_exception_error(self):
838 """Test the case in which something bad happened."""
839 self.spawn_return_code = EXCEPTION_RAISED
840 got_creds = yield self.wc.request_proxy_auth_credentials(self.domain,
841 self.retry)
842 self.assertFalse(got_creds, 'Return true when user cancels.')
843
844
845class BaseSSLTestCase(SquidTestCase):
846 """Base test that allows to use ssl connections."""
847
848 @defer.inlineCallbacks
849 def setUp(self):
850 """Set the diff tests."""
851 yield super(BaseSSLTestCase, self).setUp()
852 self.cert_dir = os.path.join(self.tmpdir, 'cert')
853 self.cert_details = dict(organization='Canonical',
854 common_name=gethostname(),
855 locality_name='London',
856 unit='Ubuntu One',
857 country_name='UK',
858 state_name='London',)
859 self.ssl_settings = self._generate_self_signed_certificate(
860 self.cert_dir,
861 self.cert_details)
862 self.addCleanup(self._clean_ssl_certificate_files)
863
864 self.ws = MockWebServer(self.ssl_settings)
865 self.addCleanup(self.ws.stop)
866 self.base_iri = self.ws.get_iri()
867 self.base_ssl_iri = self.ws.get_ssl_iri()
868
869 def _clean_ssl_certificate_files(self):
870 """Remove the certificate files."""
871 if os.path.exists(self.cert_dir):
872 shutil.rmtree(self.cert_dir)
873
874 def _generate_self_signed_certificate(self, cert_dir, cert_details):
875 """Generate the required SSL certificates."""
876 if not os.path.exists(cert_dir):
877 os.makedirs(cert_dir)
878 cert_path = os.path.join(cert_dir, 'cert.crt')
879 key_path = os.path.join(cert_dir, 'cert.key')
880
881 if os.path.exists(cert_path):
882 os.unlink(cert_path)
883 if os.path.exists(key_path):
884 os.unlink(key_path)
885
886 # create a key pair
887 key = crypto.PKey()
888 key.generate_key(crypto.TYPE_RSA, 1024)
889
890 # create a self-signed cert
891 cert = crypto.X509()
892 cert.get_subject().C = cert_details['country_name']
893 cert.get_subject().ST = cert_details['state_name']
894 cert.get_subject().L = cert_details['locality_name']
895 cert.get_subject().O = cert_details['organization']
896 cert.get_subject().OU = cert_details['unit']
897 cert.get_subject().CN = cert_details['common_name']
898 cert.set_serial_number(1000)
899 cert.gmtime_adj_notBefore(0)
900 cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60)
901 cert.set_issuer(cert.get_subject())
902 cert.set_pubkey(key)
903 cert.sign(key, 'sha1')
904
905 with open(cert_path, 'wt') as fd:
906 fd.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
907
908 with open(key_path, 'wt') as fd:
909 fd.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key))
910
911 return dict(key=key_path, cert=cert_path)
912
913
914class CorrectProxyTestCase(BaseSSLTestCase):
915 """Test the interaction with a SSL enabled proxy."""
916
917 @defer.inlineCallbacks
918 def setUp(self):
919 """Set the tests."""
920 yield super(CorrectProxyTestCase, self).setUp()
921
922 # fake the gsettings to have diff settings for https and http
923 http_settings = self.get_auth_proxy_settings()
924
925 #remember so that we can use them in the creds request
926 proxy_username = http_settings['username']
927 proxy_password = http_settings['password']
928
929 # delete the username and password so that we get a 407 for testing
930 del http_settings['username']
931 del http_settings['password']
932
933 https_settings = self.get_nonauth_proxy_settings()
934
935 proxy_settings = dict(http=http_settings, https=https_settings)
936 self.patch(gsettings, "get_proxy_settings", lambda: proxy_settings)
937
938 self.wc = webclient.webclient_factory()
939 self.addCleanup(self.wc.shutdown)
940
941 self.called = []
942
943 def fake_creds_request(domain, retry):
944 """Fake user interaction."""
945 self.called.append('request_proxy_auth_credentials')
946 self.wc.proxy_username = proxy_username
947 self.wc.proxy_password = proxy_password
948 return defer.succeed(True)
949
950 self.patch(self.wc, 'request_proxy_auth_credentials',
951 fake_creds_request)
952
953 def assert_header_contains(self, headers, expected):
954 """One of the headers matching key must contain a given value."""
955 self.assertTrue(any(expected in value for value in headers))
956
957 @defer.inlineCallbacks
958 def test_https_request(self):
959 """Test using the correct proxy for the ssl request.
960
961 In order to assert that the correct proxy is used we expect not to call
962 the auth dialog since we set the https proxy not to use the auth proxy
963 and to fail because we are reaching a https page with bad self-signed
964 certs.
965 """
966 # we fail due to the fake ssl cert
967 yield self.failUnlessFailure(self.wc.request(
968 self.base_ssl_iri + SIMPLERESOURCE),
969 webclient.WebClientError)
970 # https requests do not use the auth proxy therefore called should be
971 # empty. This asserts that we are using the correct settings for the
972 # request.
973 self.assertEqual([], self.called)
974
975 @defer.inlineCallbacks
976 def test_http_request(self):
977 """Test using the correct proxy for the plain request.
978
979 This tests does the opposite to the https tests. We did set the auth
980 proxy for the http request therefore we expect the proxy dialog to be
981 used and not to get an error since we are not visiting a https with bad
982 self-signed certs.
983 """
984 # we do not fail since we are not going to the https page
985 result = yield self.wc.request(self.base_iri + SIMPLERESOURCE)
986 self.assert_header_contains(result.headers["Via"], "squid")
987 # assert that we did go through the auth proxy
988 self.assertIn('request_proxy_auth_credentials', self.called)
989
990 if WEBCLIENT_MODULE_NAME.endswith(".txweb"):
991 reason = 'Multiple proxy settings is not supported.'
992 test_https_request.skip = reason
993 test_http_request.skip = reason
994
995 if WEBCLIENT_MODULE_NAME.endswith(".libsoup"):
996 reason = 'Hard to test since we need to fully mock gsettings.'
997 test_https_request.skip = reason
998 test_http_request.skip = reason
999
1000 if WEBCLIENT_MODULE_NAME.endswith(".qtnetwork"):
1001 reason = ('Updating proxy settings is not well support due to bug'
1002 ' QTBUG-14850: https://bugreports.qt-project.org/'
1003 'browse/QTBUG-14850')
1004 test_https_request.skip = reason
1005 test_http_request.skip = reason
1006
1007
1008class SSLTestCase(BaseSSLTestCase):
1009 """Test error handling when dealing with ssl."""
1010
1011 @defer.inlineCallbacks
1012 def setUp(self):
1013 """Set the diff tests."""
1014 yield super(SSLTestCase, self).setUp()
1015
1016 self.wc = webclient.webclient_factory()
1017 self.addCleanup(self.wc.shutdown)
1018
1019 self.return_code = USER_CANCELLATION
1020 self.called = []
1021
1022 def fake_launch_ssl_dialog(client, domain, details):
1023 """Fake the ssl dialog."""
1024 self.called.append(('_launch_ssl_dialog', domain, details))
1025 return defer.succeed(self.return_code)
1026
1027 self.patch(BaseWebClient, '_launch_ssl_dialog', fake_launch_ssl_dialog)
1028
1029 @defer.inlineCallbacks
1030 def _assert_ssl_fail_user_accepts(self, proxy_settings=None):
1031 """Assert the dialog is shown in an ssl fail."""
1032 self.return_code = USER_SUCCESS
1033 if proxy_settings:
1034 self.wc.force_use_proxy(proxy_settings)
1035 yield self.wc.request(self.base_ssl_iri + SIMPLERESOURCE)
1036 details = SSL_DETAILS_TEMPLATE % self.cert_details
1037 self.assertIn(('_launch_ssl_dialog', gethostname(), details),
1038 self.called)
1039
1040 def test_ssl_fail_dialog_user_accepts(self):
1041 """Test showing the dialog and accepting."""
1042 self._assert_ssl_fail_user_accepts()
1043
1044 def test_ssl_fail_dialog_user_accepts_via_proxy(self):
1045 """Test showing the dialog and accepting when using a proxy."""
1046 self._assert_ssl_fail_user_accepts(self.get_nonauth_proxy_settings())
1047
1048 def test_ssl_fail_dialog_user_rejects(self):
1049 """Test showing the dialog and rejecting."""
1050 self.failUnlessFailure(self.wc.request(self.base_iri + SIMPLERESOURCE),
1051 webclient.WebClientError)
1052
1053 def test_format_ssl_details(self):
1054 """Assert that details are correctly formatted"""
1055 details = SSL_DETAILS_TEMPLATE % self.cert_details
1056 self.assertEqual(details,
1057 self.wc.format_ssl_details(self.cert_details))
1058
1059 if (WEBCLIENT_MODULE_NAME.endswith(".txweb") or
1060 WEBCLIENT_MODULE_NAME.endswith(".libsoup")):
1061 reason = 'SSL support has not yet been implemented.'
1062 test_ssl_fail_dialog_user_accepts.skip = reason
1063 test_ssl_fail_dialog_user_accepts_via_proxy.skip = reason
1064 test_ssl_fail_dialog_user_rejects.skip = reason
5651065
=== modified file 'ubuntu_sso/utils/webclient/txweb.py'
--- ubuntu_sso/utils/webclient/txweb.py 2012-02-07 19:36:50 +0000
+++ ubuntu_sso/utils/webclient/txweb.py 2012-03-20 16:10:09 +0000
@@ -16,11 +16,9 @@
16"""A webclient backend that uses twisted.web.client."""16"""A webclient backend that uses twisted.web.client."""
1717
18import base6418import base64
1919import urlparse
20from StringIO import StringIO20
2121from twisted.internet import defer
22from twisted.internet import defer, protocol
23from zope.interface import implements
2422
25from ubuntu_sso.utils.webclient.common import (23from ubuntu_sso.utils.webclient.common import (
26 BaseWebClient,24 BaseWebClient,
@@ -31,64 +29,80 @@
31)29)
3230
3331
34class StringProtocol(protocol.Protocol):32class RawResponse(object):
35 """Hold the stuff received in a StringIO."""33 """A raw response from the webcall."""
3634
37 # pylint: disable=C010335 def __init__(self, headers, content, code=200, phrase="OK"):
38 def __init__(self):36 """Initialize this response."""
39 """Initialize this instance."""37 self.headers = headers
40 self.deferred = defer.Deferred()38 self.content = content
41 self.content = StringIO()39 self.code = code
4240 self.phrase = phrase
43 def dataReceived(self, data):
44 """Some more blocks received."""
45 self.content.write(data)
46
47 def connectionLost(self, reason=protocol.connectionDone):
48 """No more bytes available."""
49 self.deferred.callback(self.content.getvalue())
50
51
52class StringProducer(object):
53 """Simple implementation of IBodyProducer."""
54
55 # delay import, otherwise a default reactor gets installed
56 from twisted.web import iweb
57
58 implements(iweb.IBodyProducer)
59
60 def __init__(self, body):
61 """Initialize this instance with some bytes."""
62 self.body = body
63 self.length = len(body)
64
65 # pylint: disable=C0103
66 def startProducing(self, consumer):
67 """Start producing to the given IConsumer provider."""
68 consumer.write(self.body)
69 return defer.succeed(None)
70
71 def pauseProducing(self):
72 """In our case, do nothing."""
73
74 def stopProducing(self):
75 """In our case, do nothing."""
7641
7742
78class WebClient(BaseWebClient):43class WebClient(BaseWebClient):
79 """A simple web client that does not support proxies, yet."""44 """A simple web client that does not support proxies, yet."""
8045
81 # delay import, otherwise a default reactor gets installed46 def __init__(self, connector=None, context_factory=None, **kwargs):
82 from twisted.internet import reactor47 """Initialize this webclient."""
83 from twisted.web import client, http, http_headers48 super(WebClient, self).__init__(**kwargs)
8449
85 # Undefined variable 'http_headers', 'client', 'reactor', 'http'50 if connector is None:
86 # pylint: disable=E060251 from twisted.internet import reactor
52 self.connector = reactor
53 else:
54 self.connector = connector
55
56 if context_factory is None:
57 from twisted.internet import ssl
58 self.context_factory = ssl.ClientContextFactory()
59 else:
60 self.context_factory = context_factory
61
62 @defer.inlineCallbacks
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches