Merge lp:~dobey/ubuntu/oneiric/ubuntu-sso-client/release-133 into lp:ubuntu/oneiric/ubuntu-sso-client

Proposed by dobey
Status: Merged
Merged at revision: 36
Proposed branch: lp:~dobey/ubuntu/oneiric/ubuntu-sso-client/release-133
Merge into: lp:ubuntu/oneiric/ubuntu-sso-client
Diff against target: 4383 lines (+2124/-395)
39 files modified
PKG-INFO (+19/-1)
bin/windows-ubuntu-sso-login (+36/-5)
data/qt/current_user_sign_in.ui (+6/-3)
data/qt/email_verification.ui (+1/-1)
data/qt/forgotten_password.ui (+4/-1)
data/qt/reset_password.ui (+4/-1)
data/qt/setup_account.ui (+2/-15)
debian/changelog (+7/-0)
run-tests (+18/-2)
run-tests.bat (+5/-7)
setup.py (+33/-12)
ubuntu_sso/credentials.py (+46/-26)
ubuntu_sso/logger.py (+2/-3)
ubuntu_sso/main/__init__.py (+29/-7)
ubuntu_sso/main/linux.py (+20/-6)
ubuntu_sso/main/tests/test_common.py (+15/-8)
ubuntu_sso/main/tests/test_linux.py (+16/-5)
ubuntu_sso/main/tests/test_windows.py (+100/-15)
ubuntu_sso/main/windows.py (+87/-31)
ubuntu_sso/networkstate/windows.py (+4/-2)
ubuntu_sso/qt/controllers.py (+212/-78)
ubuntu_sso/qt/gui.py (+79/-16)
ubuntu_sso/qt/tests/login_u_p.py (+54/-0)
ubuntu_sso/qt/tests/show_gui.py (+16/-5)
ubuntu_sso/qt/tests/test_enchanced_line_edit.py (+94/-0)
ubuntu_sso/qt/tests/test_qt_views.py (+44/-1)
ubuntu_sso/qt/tests/test_windows.py (+502/-46)
ubuntu_sso/tests/__init__.py (+10/-10)
ubuntu_sso/tests/test_account.py (+5/-2)
ubuntu_sso/tests/test_credentials.py (+168/-82)
ubuntu_sso/utils/__init__.py (+35/-0)
ubuntu_sso/utils/tests/test_oauth_headers.py (+109/-0)
ubuntu_sso/utils/txsecrets.py (+8/-2)
ubuntu_sso/utils/ui.py (+3/-2)
ubuntu_sso/xdg_base_directory/__init__.py (+35/-0)
ubuntu_sso/xdg_base_directory/tests/__init__.py (+16/-0)
ubuntu_sso/xdg_base_directory/tests/test_common.py (+49/-0)
ubuntu_sso/xdg_base_directory/tests/test_windows.py (+112/-0)
ubuntu_sso/xdg_base_directory/windows.py (+119/-0)
To merge this branch: bzr merge lp:~dobey/ubuntu/oneiric/ubuntu-sso-client/release-133
Reviewer Review Type Date Requested Status
Ubuntu branches Pending
Review via email: mp+72943@code.launchpad.net
To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'PKG-INFO'
--- PKG-INFO 2011-07-22 20:12:20 +0000
+++ PKG-INFO 2011-08-25 19:16:19 +0000
@@ -1,6 +1,6 @@
1Metadata-Version: 1.11Metadata-Version: 1.1
2Name: ubuntu-sso-client2Name: ubuntu-sso-client
3Version: 1.3.23Version: 1.3.3
4Summary: Ubuntu Single Sign-On client4Summary: Ubuntu Single Sign-On client
5Home-page: https://launchpad.net/ubuntu-sso-client5Home-page: https://launchpad.net/ubuntu-sso-client
6Author: Natalia Bidart6Author: Natalia Bidart
@@ -8,4 +8,22 @@
8License: GPL v38License: GPL v3
9Description: Desktop service to allow applications to sign into Ubuntu services via SSO9Description: Desktop service to allow applications to sign into Ubuntu services via SSO
10Platform: UNKNOWN10Platform: UNKNOWN
11Requires: Image
12Requires: PIL
13Requires: PyQt4
14Requires: dbus
15Requires: gi.repository
16Requires: gobject
17Requires: gtk
18Requires: keyring
19Requires: mocker
20Requires: oauth
21Requires: twisted.internet
22Requires: twisted.python
23Requires: twisted.spread.pb
24Requires: twisted.trial
25Requires: ubuntuone.devtools.handlers
26Requires: ubuntuone.devtools.testcase
27Requires: webkit
28Requires: xdg.BaseDirectory
11Provides: ubuntu_sso29Provides: ubuntu_sso
1230
=== modified file 'bin/windows-ubuntu-sso-login'
--- bin/windows-ubuntu-sso-login 2011-07-22 20:12:20 +0000
+++ bin/windows-ubuntu-sso-login 2011-08-25 19:16:19 +0000
@@ -28,16 +28,25 @@
28import qtreactor.qt4reactor28import qtreactor.qt4reactor
29qtreactor.qt4reactor.install()29qtreactor.qt4reactor.install()
3030
31from twisted.internet import reactor31from twisted.internet import reactor, defer
32from twisted.spread.pb import PBServerFactory32from twisted.spread.pb import PBServerFactory
33from twisted.internet.task import LoopingCall33from twisted.internet.task import LoopingCall
34from twisted.python import log
3435
36from ubuntu_sso.logger import setup_logging
35from ubuntu_sso.main.windows import (37from ubuntu_sso.main.windows import (
36 CredentialsManagement,38 CredentialsManagement,
39 LOCALHOST,
37 SSOCredentials,40 SSOCredentials,
38 SSOLogin,41 SSOLogin,
39 UbuntuSSORoot,42 UbuntuSSORoot,
40 get_sso_pb_hostport)43 get_activation_config,
44)
45from ubuntu_sso.utils import tcpactivation
46
47
48logger = setup_logging("windows-ubuntu-sso-login")
49
4150
42def add_timeout(interval, callback, *args, **kwargs):51def add_timeout(interval, callback, *args, **kwargs):
43 """Add a timeout callback as a task."""52 """Add a timeout callback as a task."""
@@ -45,13 +54,35 @@
45 time_out_task.start(interval/1000, now=False)54 time_out_task.start(interval/1000, now=False)
4655
4756
48if __name__ == '__main__':57@defer.inlineCallbacks
49 host, port = get_sso_pb_hostport()58def main():
59 """Initialize and start this process."""
60 ai = tcpactivation.ActivationInstance(get_activation_config())
61 port = yield ai.get_port()
5062
51 login = SSOLogin('ignored')63 login = SSOLogin('ignored')
52 creds = SSOCredentials()64 creds = SSOCredentials()
53 creds_management = CredentialsManagement(add_timeout, reactor.stop)65 creds_management = CredentialsManagement(add_timeout, reactor.stop)
54 root = UbuntuSSORoot(login, creds, creds_management)66 root = UbuntuSSORoot(login, creds, creds_management)
5567
56 listener = reactor.listenTCP(port, PBServerFactory(root), interface=host)68 reactor.listenTCP(port, PBServerFactory(root), interface=LOCALHOST)
69
70
71def handle_already_started(failure):
72 """Handle the already started error by shutting down this process."""
73 failure.trap(tcpactivation.AlreadyStartedError)
74 print "Ubuntu SSO login manager already running."
75 reactor.stop()
76
77
78def utter_failure(failure):
79 """Handle an utter failure by logging it and quiting."""
80 log.err(failure)
81 reactor.stop()
82
83
84if __name__ == '__main__':
85 d = main()
86 d.addErrback(handle_already_started)
87 d.addErrback(utter_failure)
57 reactor.run()88 reactor.run()
5889
=== modified file 'data/qt/current_user_sign_in.ui'
--- data/qt/current_user_sign_in.ui 2011-07-22 20:12:20 +0000
+++ data/qt/current_user_sign_in.ui 2011-08-25 19:16:19 +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>400</width>9 <width>399</width>
10 <height>300</height>10 <height>309</height>
11 </rect>11 </rect>
12 </property>12 </property>
13 <property name="windowTitle">13 <property name="windowTitle">
14 <string>WizardPage</string>14 <string>WizardPage</string>
15 </property>15 </property>
16 <layout class="QHBoxLayout" name="horizontalLayout">16 <layout class="QVBoxLayout" name="verticalLayout_4">
17 <item>17 <item>
18 <layout class="QHBoxLayout" name="horizontalLayout_3">18 <layout class="QHBoxLayout" name="horizontalLayout_3">
19 <item>19 <item>
@@ -122,6 +122,9 @@
122 </item>122 </item>
123 <item>123 <item>
124 <widget class="QPushButton" name="sign_in_button">124 <widget class="QPushButton" name="sign_in_button">
125 <property name="enabled">
126 <bool>false</bool>
127 </property>
125 <property name="text">128 <property name="text">
126 <string>Sign In</string>129 <string>Sign In</string>
127 </property>130 </property>
128131
=== modified file 'data/qt/email_verification.ui'
--- data/qt/email_verification.ui 2011-06-24 19:14:17 +0000
+++ data/qt/email_verification.ui 2011-08-25 19:16:19 +0000
@@ -13,7 +13,7 @@
13 <property name="windowTitle">13 <property name="windowTitle">
14 <string>WizardPage</string>14 <string>WizardPage</string>
15 </property>15 </property>
16 <layout class="QHBoxLayout" name="horizontalLayout">16 <layout class="QVBoxLayout" name="verticalLayout_2">
17 <item>17 <item>
18 <layout class="QHBoxLayout" name="horizontalLayout_3">18 <layout class="QHBoxLayout" name="horizontalLayout_3">
19 <item>19 <item>
2020
=== modified file 'data/qt/forgotten_password.ui'
--- data/qt/forgotten_password.ui 2011-06-24 19:14:17 +0000
+++ data/qt/forgotten_password.ui 2011-08-25 19:16:19 +0000
@@ -7,7 +7,7 @@
7 <x>0</x>7 <x>0</x>
8 <y>0</y>8 <y>0</y>
9 <width>446</width>9 <width>446</width>
10 <height>209</height>10 <height>317</height>
11 </rect>11 </rect>
12 </property>12 </property>
13 <property name="windowTitle">13 <property name="windowTitle">
@@ -109,6 +109,9 @@
109 </item>109 </item>
110 <item>110 <item>
111 <widget class="QPushButton" name="send_button">111 <widget class="QPushButton" name="send_button">
112 <property name="enabled">
113 <bool>false</bool>
114 </property>
112 <property name="text">115 <property name="text">
113 <string/>116 <string/>
114 </property>117 </property>
115118
=== modified file 'data/qt/reset_password.ui'
--- data/qt/reset_password.ui 2011-06-24 19:14:17 +0000
+++ data/qt/reset_password.ui 2011-08-25 19:16:19 +0000
@@ -7,7 +7,7 @@
7 <x>0</x>7 <x>0</x>
8 <y>0</y>8 <y>0</y>
9 <width>476</width>9 <width>476</width>
10 <height>282</height>10 <height>262</height>
11 </rect>11 </rect>
12 </property>12 </property>
13 <property name="windowTitle">13 <property name="windowTitle">
@@ -65,6 +65,9 @@
65 </item>65 </item>
66 <item>66 <item>
67 <widget class="QPushButton" name="reset_password_button">67 <widget class="QPushButton" name="reset_password_button">
68 <property name="enabled">
69 <bool>false</bool>
70 </property>
68 <property name="text">71 <property name="text">
69 <string/>72 <string/>
70 </property>73 </property>
7174
=== modified file 'data/qt/setup_account.ui'
--- data/qt/setup_account.ui 2011-06-24 19:14:17 +0000
+++ data/qt/setup_account.ui 2011-08-25 19:16:19 +0000
@@ -7,29 +7,16 @@
7 <x>0</x>7 <x>0</x>
8 <y>0</y>8 <y>0</y>
9 <width>407</width>9 <width>407</width>
10 <height>453</height>10 <height>572</height>
11 </rect>11 </rect>
12 </property>12 </property>
13 <property name="windowTitle">13 <property name="windowTitle">
14 <string>WizardPage</string>14 <string>WizardPage</string>
15 </property>15 </property>
16 <layout class="QHBoxLayout" name="horizontalLayout">16 <layout class="QVBoxLayout" name="verticalLayout_5">
17 <item>17 <item>
18 <layout class="QVBoxLayout" name="verticalLayout">18 <layout class="QVBoxLayout" name="verticalLayout">
19 <item>19 <item>
20 <spacer name="verticalSpacer_3">
21 <property name="orientation">
22 <enum>Qt::Vertical</enum>
23 </property>
24 <property name="sizeHint" stdset="0">
25 <size>
26 <width>20</width>
27 <height>40</height>
28 </size>
29 </property>
30 </spacer>
31 </item>
32 <item>
33 <widget class="QFrame" name="_signInFrame">20 <widget class="QFrame" name="_signInFrame">
34 <property name="frameShape">21 <property name="frameShape">
35 <enum>QFrame::NoFrame</enum>22 <enum>QFrame::NoFrame</enum>
3623
=== modified file 'debian/changelog'
--- debian/changelog 2011-07-26 13:58:21 +0000
+++ debian/changelog 2011-08-25 19:16:19 +0000
@@ -1,3 +1,10 @@
1ubuntu-sso-client (1.3.3-0ubuntu1) oneiric; urgency=low
2
3 * New upstream release.
4 - Work correctly with static and GI bindings of gobject (LP: #829186)
5
6 -- Rodney Dawes <rodney.dawes@ubuntu.com> Thu, 25 Aug 2011 15:08:27 -0400
7
1ubuntu-sso-client (1.3.2-0ubuntu2) oneiric; urgency=low8ubuntu-sso-client (1.3.2-0ubuntu2) oneiric; urgency=low
29
3 * Require python-qt4 to build, as distutils doesn't have optional deps10 * Require python-qt4 to build, as distutils doesn't have optional deps
411
=== modified file 'run-tests'
--- run-tests 2011-07-22 20:12:20 +0000
+++ run-tests 2011-08-25 19:16:19 +0000
@@ -16,8 +16,18 @@
16# You should have received a copy of the GNU General Public License along16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.17# with this program. If not, see <http://www.gnu.org/licenses/>.
1818
19QT_TESTS_PATH=ubuntu_sso/qt/tests
20GTK_TESTS_PATH=ubuntu_sso/gtk/tests
21
19set -e22set -e
2023
24if [ "$1" == "-qt" ]; then
25 USE_QT=1
26 shift
27else
28 USE_QT=0
29fi
30
21if [ $# -ne 0 ]; then31if [ $# -ne 0 ]; then
22 # run specific module given by the caller32 # run specific module given by the caller
23 MODULE="$@"33 MODULE="$@"
@@ -30,7 +40,7 @@
30 ./setup.py clean40 ./setup.py clean
31 u1lint41 u1lint
32 if [ -x `which pep8` ]; then42 if [ -x `which pep8` ]; then
33 pep8 --repeat bin/ $MODULE43 pep8 --repeat .
34 else44 else
35 echo "Please install the 'pep8' package."45 echo "Please install the 'pep8' package."
36 fi46 fi
@@ -39,6 +49,12 @@
39unset GTK_MODULES49unset GTK_MODULES
4050
41echo "Running test suite for ""$MODULE"51echo "Running test suite for ""$MODULE"
42`which xvfb-run` u1trial "$MODULE" -i "test_windows.py, test_qt_views.py"52if [ "$USE_QT" -eq 0 ]; then
53 `which xvfb-run` u1trial -p "$QT_TESTS_PATH" -i "test_windows.py" "$MODULE"
54else
55 ./setup.py build
56 `which xvfb-run` u1trial -p "$GTK_TESTS_PATH" -i "test_windows.py" --reactor=qt4 --gui "$MODULE"
57fi
43style_check58style_check
44rm -rf _trial_temp59rm -rf _trial_temp
60rm -rf build
4561
=== modified file 'run-tests.bat'
--- run-tests.bat 2011-07-22 20:12:20 +0000
+++ run-tests.bat 2011-08-25 19:16:19 +0000
@@ -60,13 +60,11 @@
60ECHO Cleaning the generated code before running the style checks...60ECHO Cleaning the generated code before running the style checks...
61"%PYTHONEXEPATH%\python.exe" setup.py clean61"%PYTHONEXEPATH%\python.exe" setup.py clean
62ECHO Performing style checks...62ECHO Performing style checks...
63"%PYTHONEXEPATH%\python.exe" "%PYTHONEXEPATH%\Scripts\u1lint" ubuntu_sso63SET IGNORE_LINT="ubuntu_sso\gtk,ubuntu_sso\networkstate\linux.py,ubuntu_sso\main\linux.py,ubuntu_sso\main\tests\test_linux.py,ubuntu_sso\utils\txsecrets.py,ubuntu_sso\utils\tests\test_txsecrets.py,ubuntu_sso\tests\bin,bin\ubuntu-sso-login"
64"%PYTHONEXEPATH%\python.exe" "%PYTHONEXEPATH%\Scripts\u1lint" -i "%IGNORE_LINT%" ubuntu_sso
64:: test for style if we can, if pep8 is not present, move to the end65:: test for style if we can, if pep8 is not present, move to the end
65IF EXIST "%PYTHONEXEPATH%\Scripts\pep8.exe"66"%PYTHONEXEPATH%\Scripts\pep8.exe" --repeat .
66"%PYTHONEXEPATH%\Scripts\pep8.exe" --repeat ubuntu_sso
67ELSE
68ECHO Style checks were not done
69:: Delete the temp folders67:: Delete the temp folders
70RMDIR /s /q _trial_temp68RMDIR /q /s _trial_temp
71RMDIR /s /q .coverage69RMDIR /q /s .coverage
72:END70:END
7371
=== modified file 'setup.py'
--- setup.py 2011-07-22 20:12:20 +0000
+++ setup.py 2011-08-25 19:16:19 +0000
@@ -22,6 +22,7 @@
2222
23# pylint: disable=W0404, W051123# pylint: disable=W0404, W0511
2424
25import distutils
25import os26import os
26import sys27import sys
2728
@@ -43,6 +44,7 @@
43CLEANFILES = [SERVICE_FILE, POT_FILE, 'MANIFEST']44CLEANFILES = [SERVICE_FILE, POT_FILE, 'MANIFEST']
44QT_UI_DIR = os.path.join('ubuntu_sso', 'qt')45QT_UI_DIR = os.path.join('ubuntu_sso', 'qt')
4546
47
46def replace_prefix(prefix):48def replace_prefix(prefix):
47 """Replace every '@prefix@' with prefix within 'filename' content."""49 """Replace every '@prefix@' with prefix within 'filename' content."""
48 # replace .service file50 # replace .service file
@@ -242,7 +244,7 @@
242 # this some day. If this doesn't work, try import modulefinder244 # this some day. If this doesn't work, try import modulefinder
243 for package_path in win32com.__path__[1:]:245 for package_path in win32com.__path__[1:]:
244 modulefinder.AddPackagePath("win32com", package_path)246 modulefinder.AddPackagePath("win32com", package_path)
245 for extra_mod in ["win32com.server" ,"win32com.client"]: 247 for extra_mod in ["win32com.server", "win32com.client"]:
246 __import__(extra_mod)248 __import__(extra_mod)
247 module = sys.modules[extra_mod]249 module = sys.modules[extra_mod]
248 for module_path in module.__path__[1:]:250 for module_path in module.__path__[1:]:
@@ -251,7 +253,7 @@
251 # lazr uses namespaces packages, which does add some problems to py2exe253 # lazr uses namespaces packages, which does add some problems to py2exe
252 # the following is a way to work arround the issue254 # the following is a way to work arround the issue
253 for path in lazr.__path__:255 for path in lazr.__path__:
254 modulefinder.AddPackagePath(__name__, path) 256 modulefinder.AddPackagePath(__name__, path)
255257
256258
257def get_py2exe_extension():259def get_py2exe_extension():
@@ -261,14 +263,10 @@
261 from py2exe.build_exe import py2exe as build_exe263 from py2exe.build_exe import py2exe as build_exe
262 # pylint: enable=F0401264 # pylint: enable=F0401
263265
264 # pylint: disable=E1101266 # pylint: disable=E1101,W0232
265 class MediaCollector(build_exe):267 class MediaCollector(build_exe):
266 """Extension that copies lazr missing data."""268 """Extension that copies lazr missing data."""
267269
268 def __init__(self, *args, **kwargs):
269 """Create a new instance."""
270 build_exe.__init__(self, *args, **kwargs)
271
272 def _add_module_data(self, module_name):270 def _add_module_data(self, module_name):
273 """Add the data from a given path."""271 """Add the data from a given path."""
274 # Create the media subdir where the272 # Create the media subdir where the
@@ -310,6 +308,24 @@
310 'clean': SSOClean,308 'clean': SSOClean,
311}309}
312310
311
312class dummy_build_i18n(distutils.cmd.Command):
313
314 """Dummy for windows."""
315
316 def initialize_options(self, *args):
317 """Dummy."""
318
319 def finalize_options(self, *args):
320 """Dummy."""
321
322 def run(self, *args):
323 """Dummy."""
324
325if sys.platform == 'win32':
326 cmdclass['build_i18n'] = dummy_build_i18n
327
328
313def setup_windows():329def setup_windows():
314 """Provide the required info to setup the project on windows."""330 """Provide the required info to setup the project on windows."""
315 set_py2exe_paths()331 set_py2exe_paths()
@@ -329,7 +345,7 @@
329 },345 },
330 },346 },
331 # add the console script so that py2exe compiles it347 # add the console script so that py2exe compiles it
332 'console': ['bin/windows-ubuntu-sso-login',],348 'console': ['bin/windows-ubuntu-sso-login'],
333 'zipfile': None,349 'zipfile': None,
334 }350 }
335 return _data_files, _extra351 return _data_files, _extra
@@ -344,7 +360,7 @@
344360
345DistUtilsExtra.auto.setup(361DistUtilsExtra.auto.setup(
346 name='ubuntu-sso-client',362 name='ubuntu-sso-client',
347 version='1.3.2',363 version='1.3.3',
348 license='GPL v3',364 license='GPL v3',
349 author='Natalia Bidart',365 author='Natalia Bidart',
350 author_email='natalia.bidart@canonical.com',366 author_email='natalia.bidart@canonical.com',
@@ -355,9 +371,14 @@
355 extra_path='ubuntu-sso-client',371 extra_path='ubuntu-sso-client',
356 data_files=data_files,372 data_files=data_files,
357 packages=[373 packages=[
358 'ubuntu_sso', 'ubuntu_sso.utils', 'ubuntu_sso.keyring',374 'ubuntu_sso',
359 'ubuntu_sso.networkstate', 'ubuntu_sso.main',375 'ubuntu_sso.utils',
360 'ubuntu_sso.gtk', 'ubuntu_sso.qt',376 'ubuntu_sso.keyring',
377 'ubuntu_sso.networkstate',
378 'ubuntu_sso.main',
379 'ubuntu_sso.gtk',
380 'ubuntu_sso.qt',
381 'ubuntu_sso.xdg_base_directory',
361 ],382 ],
362 cmdclass=cmdclass,383 cmdclass=cmdclass,
363 **extra)384 **extra)
364385
=== modified file 'ubuntu_sso/credentials.py'
--- ubuntu_sso/credentials.py 2011-01-12 18:56:56 +0000
+++ ubuntu_sso/credentials.py 2011-08-25 19:16:19 +0000
@@ -43,14 +43,12 @@
4343
44from functools import wraps44from functools import wraps
4545
46from oauth import oauth
47from twisted.internet.defer import inlineCallbacks, returnValue46from twisted.internet.defer import inlineCallbacks, returnValue
4847
49from ubuntu_sso import NO_OP48from ubuntu_sso import NO_OP, utils
50from ubuntu_sso.keyring import Keyring49from ubuntu_sso.keyring import Keyring
51from ubuntu_sso.logger import setup_logging50from ubuntu_sso.logger import setup_logging
5251
53
54logger = setup_logging('ubuntu_sso.credentials')52logger = setup_logging('ubuntu_sso.credentials')
5553
5654
@@ -183,7 +181,7 @@
183 self._success_cb = success_cb181 self._success_cb = success_cb
184 self._error_cb = error_cb182 self._error_cb = error_cb
185 self.denial_cb = denial_cb183 self.denial_cb = denial_cb
186 self.gui = None # will hold the GUI instance184 self.inner = None # will hold the GUI or SSOLoginRoot instance
187185
188 @handle_failures(msg='Problem while retrieving credentials')186 @handle_failures(msg='Problem while retrieving credentials')
189 @inlineCallbacks187 @inlineCallbacks
@@ -226,27 +224,28 @@
226 defined if this method is being called.224 defined if this method is being called.
227225
228 """226 """
229 logger.info('Pinging server for app_name "%s", ping_url: "%s", '227 logger.info('Pinging server for app_name %r, ping_url: %r, '
230 'email "%s".', app_name, self.ping_url, email)228 'email %r.', app_name, self.ping_url, email)
231 url = self.ping_url + email229 try:
232 consumer = oauth.OAuthConsumer(credentials['consumer_key'],230 url = self.ping_url.format(email=email)
233 credentials['consumer_secret'])231 except IndexError: # tuple index out of range
234 token = oauth.OAuthToken(credentials['token'],232 url = self.ping_url.format(email) # format the first substitution
235 credentials['token_secret'])233
236 get_request = oauth.OAuthRequest.from_consumer_and_token234 if url == self.ping_url:
237 oauth_req = get_request(oauth_consumer=consumer, token=token,235 logger.debug('Original url (%r) could not be formatted, '
238 http_method='GET', http_url=url)236 'appending email (%r).', self.ping_url, email)
239 oauth_req.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(),237 url = self.ping_url + email
240 consumer, token)238
241 request = urllib2.Request(url, headers=oauth_req.to_header())239 headers = utils.oauth_headers(url, credentials)
242 logger.debug('Opening the url "%s" with urllib2.urlopen.',240 request = urllib2.Request(url, headers=headers)
241 logger.debug('Opening the url %r with urllib2.urlopen.',
243 request.get_full_url())242 request.get_full_url())
244 # This code is blocking, we should change this.243 # This code is blocking, we should change this.
245 # I've tried with deferToThread an twisted.web.client.getPage244 # I've tried with deferToThread an twisted.web.client.getPage
246 # but the returned deferred will never be fired (nataliabidart).245 # but the returned deferred will never be fired (nataliabidart).
247 response = urllib2.urlopen(request)246 response = urllib2.urlopen(request)
248 logger.debug('Url opened. Response: %s.', response.code)247 logger.debug('Url opened. Response: %s.', response.code)
249 returnValue(response.code)248 returnValue(response)
250249
251 @handle_exceptions(msg='Problem opening the Ubuntu SSO user interface')250 @handle_exceptions(msg='Problem opening the Ubuntu SSO user interface')
252 def _show_ui(self, login_only):251 def _show_ui(self, login_only):
@@ -255,23 +254,39 @@
255 __import__(self.ui_module)254 __import__(self.ui_module)
256 gui = sys.modules[self.ui_module]255 gui = sys.modules[self.ui_module]
257256
258 self.gui = getattr(gui, self.ui_class)(app_name=self.app_name,257 self.inner = getattr(gui, self.ui_class)(app_name=self.app_name,
259 tc_url=self.tc_url, help_text=self.help_text,258 tc_url=self.tc_url, help_text=self.help_text,
260 window_id=self.window_id, login_only=login_only)259 window_id=self.window_id, login_only=login_only)
261260
262 self.gui.login_success_callback = self._login_success_cb261 self.inner.login_success_callback = self._login_success_cb
263 self.gui.registration_success_callback = self._login_success_cb262 self.inner.registration_success_callback = self._login_success_cb
264 self.gui.user_cancellation_callback = self._auth_denial_cb263 self.inner.user_cancellation_callback = self._auth_denial_cb
264
265 @handle_exceptions(msg='Problem logging with email and password.')
266 def _do_login(self, email, password):
267 """Login using email/password, connect outcome signals."""
268 from ubuntu_sso.main import SSOLoginRoot
269 self.inner = SSOLoginRoot()
270 self.inner.login(app_name=self.app_name, email=email,
271 password=password,
272 result_cb=self._login_success_cb,
273 error_cb=self._error_cb,
274 not_validated_cb=self._error_cb)
265275
266 @handle_failures(msg='Problem while retrieving credentials')276 @handle_failures(msg='Problem while retrieving credentials')
267 @inlineCallbacks277 @inlineCallbacks
268 def _login_or_register(self, login_only):278 def _login_or_register(self, login_only, email=None, password=None):
269 """Get credentials if found else prompt the GUI."""279 """Get credentials if found else prompt the GUI."""
280 logger.info("_login_or_register: login_only=%r email=%r.",
281 login_only, email)
270 token = yield self.find_credentials()282 token = yield self.find_credentials()
271 if token is not None and len(token) > 0:283 if token is not None and len(token) > 0:
272 self.success_cb(token)284 self.success_cb(token)
273 elif token == {}:285 elif token == {}:
274 self._show_ui(login_only)286 if email and password:
287 self._do_login(email, password)
288 else:
289 self._show_ui(login_only)
275 else:290 else:
276 # something went wrong with find_credentials, already handled.291 # something went wrong with find_credentials, already handled.
277 logger.info('_login_or_register: call to "find_credentials" went '292 logger.info('_login_or_register: call to "find_credentials" went '
@@ -292,7 +307,7 @@
292 def find_credentials(self):307 def find_credentials(self):
293 """Get the credentials for 'self.app_name'. Return {} if not there."""308 """Get the credentials for 'self.app_name'. Return {} if not there."""
294 creds = yield Keyring().get_credentials(self.app_name)309 creds = yield Keyring().get_credentials(self.app_name)
295 logger.info('find_credentials: self.app_name "%s", '310 logger.info('find_credentials: self.app_name %r, '
296 'result is {}? %s', self.app_name, creds is None)311 'result is {}? %s', self.app_name, creds is None)
297 returnValue(creds if creds is not None else {})312 returnValue(creds if creds is not None else {})
298313
@@ -313,3 +328,8 @@
313 def login(self):328 def login(self):
314 """Get credentials if found else prompt the GUI to login."""329 """Get credentials if found else prompt the GUI to login."""
315 return self._login_or_register(login_only=True)330 return self._login_or_register(login_only=True)
331
332 def login_email_password(self, email, password):
333 """Get credentials if found else login using email and password."""
334 return self._login_or_register(login_only=True,
335 email=email, password=password)
316336
=== modified file 'ubuntu_sso/logger.py'
--- ubuntu_sso/logger.py 2011-01-12 18:56:56 +0000
+++ ubuntu_sso/logger.py 2011-08-25 19:16:19 +0000
@@ -25,12 +25,11 @@
25import os25import os
26import sys26import sys
2727
28import xdg.BaseDirectory
29
30from logging.handlers import RotatingFileHandler28from logging.handlers import RotatingFileHandler
3129
30from ubuntu_sso import xdg_base_directory
3231
33LOGFOLDER = os.path.join(xdg.BaseDirectory.xdg_cache_home, 'sso')32LOGFOLDER = os.path.join(xdg_base_directory.xdg_cache_home, 'sso')
34# create log folder if it doesn't exists33# create log folder if it doesn't exists
35if not os.path.exists(LOGFOLDER):34if not os.path.exists(LOGFOLDER):
36 os.makedirs(LOGFOLDER)35 os.makedirs(LOGFOLDER)
3736
=== modified file 'ubuntu_sso/main/__init__.py'
--- ubuntu_sso/main/__init__.py 2011-07-22 20:12:20 +0000
+++ ubuntu_sso/main/__init__.py 2011-08-25 19:16:19 +0000
@@ -75,7 +75,7 @@
75 self.processor = self.sso_login_processor_class(75 self.processor = self.sso_login_processor_class(
76 sso_service_class=sso_service_class)76 sso_service_class=sso_service_class)
7777
78 def generate_captcha(self, app_name, filename, thread_execute, result_cb,78 def generate_captcha(self, app_name, filename, result_cb,
79 error_cb):79 error_cb):
80 """Call the matching method in the processor."""80 """Call the matching method in the processor."""
81 def f():81 def f():
@@ -84,7 +84,7 @@
84 thread_execute(f, app_name, result_cb, error_cb)84 thread_execute(f, app_name, result_cb, error_cb)
8585
86 def register_user(self, app_name, email, password, name, captcha_id,86 def register_user(self, app_name, email, password, name, captcha_id,
87 captcha_solution, thread_execute, result_cb, error_cb):87 captcha_solution, result_cb, error_cb):
88 """Call the matching method in the processor."""88 """Call the matching method in the processor."""
89 def f():89 def f():
90 """Inner function that will be run in a thread."""90 """Inner function that will be run in a thread."""
@@ -92,7 +92,7 @@
92 captcha_id, captcha_solution)92 captcha_id, captcha_solution)
93 thread_execute(f, app_name, result_cb, error_cb)93 thread_execute(f, app_name, result_cb, error_cb)
9494
95 def login(self, app_name, email, password, thread_execute, result_cb,95 def login(self, app_name, email, password, result_cb,
96 error_cb, not_validated_cb):96 error_cb, not_validated_cb):
97 """Call the matching method in the processor."""97 """Call the matching method in the processor."""
98 def f():98 def f():
@@ -121,7 +121,7 @@
121 thread_execute(f, app_name, success_cb, error_cb)121 thread_execute(f, app_name, success_cb, error_cb)
122122
123 def validate_email(self, app_name, email, password, email_token,123 def validate_email(self, app_name, email, password, email_token,
124 thread_execute, result_cb, error_cb):124 result_cb, error_cb):
125 """Call the matching method in the processor."""125 """Call the matching method in the processor."""
126126
127 def f():127 def f():
@@ -141,7 +141,7 @@
141141
142 thread_execute(f, app_name, success_cb, error_cb)142 thread_execute(f, app_name, success_cb, error_cb)
143143
144 def request_password_reset_token(self, app_name, email, thread_execute,144 def request_password_reset_token(self, app_name, email,
145 result_cb, error_cb):145 result_cb, error_cb):
146 """Call the matching method in the processor."""146 """Call the matching method in the processor."""
147 def f():147 def f():
@@ -150,7 +150,7 @@
150 thread_execute(f, app_name, result_cb, error_cb)150 thread_execute(f, app_name, result_cb, error_cb)
151151
152 def set_new_password(self, app_name, email, token, new_password,152 def set_new_password(self, app_name, email, token, new_password,
153 thread_execute, result_cb, error_cb):153 result_cb, error_cb):
154 """Call the matching method in the processor."""154 """Call the matching method in the processor."""
155 def f():155 def f():
156 """Inner function that will be run in a thread."""156 """Inner function that will be run in a thread."""
@@ -160,10 +160,16 @@
160160
161161
162class SSOCredentialsRoot(object):162class SSOCredentialsRoot(object):
163 """Object that gets credentials, and login/registers if needed."""163 """Object that gets credentials, and login/registers if needed.
164
165 This class is DEPRECATED, use CredentialsManagementRoot instead.
166
167 """
164168
165 def __init__(self):169 def __init__(self):
166 self.ping_url = os.environ.get('USSOC_PING_URL', U1_PING_URL)170 self.ping_url = os.environ.get('USSOC_PING_URL', U1_PING_URL)
171 msg = 'Use ubuntu_sso.main.CredentialsManagementRoot instead.'
172 warnings.warn(msg, DeprecationWarning)
167173
168 def find_credentials(self, app_name, callback=NO_OP, errback=NO_OP):174 def find_credentials(self, app_name, callback=NO_OP, errback=NO_OP):
169 """Get the credentials from the keyring or {} if not there."""175 """Get the credentials from the keyring or {} if not there."""
@@ -399,6 +405,20 @@
399 obj = Credentials(app_name, **self._parse_args(args))405 obj = Credentials(app_name, **self._parse_args(args))
400 obj.login()406 obj.login()
401407
408 def login_email_password(self, app_name, args):
409 """Get credentials if found else try to login.
410
411 Login will be done by inspecting 'args' and expecting to find two keys:
412 'email' and 'password'.
413
414 """
415 self.ref_count += 1
416 email = args.pop('email')
417 password = args.pop('password')
418 obj = Credentials(app_name, **self._parse_args(args))
419 obj.login_email_password(email=email, password=password)
420
421
402# pylint: disable=C0103422# pylint: disable=C0103
403SSOLogin = None423SSOLogin = None
404SSOCredentials = None424SSOCredentials = None
@@ -410,8 +430,10 @@
410 SSOCredentials = windows.SSOCredentials430 SSOCredentials = windows.SSOCredentials
411 CredentialsManagement = windows.CredentialsManagement431 CredentialsManagement = windows.CredentialsManagement
412 TIMEOUT_INTERVAL = 10000000000 # forever432 TIMEOUT_INTERVAL = 10000000000 # forever
433 thread_execute = windows.blocking
413else:434else:
414 from ubuntu_sso.main import linux435 from ubuntu_sso.main import linux
415 SSOLogin = linux.SSOLogin436 SSOLogin = linux.SSOLogin
416 SSOCredentials = linux.SSOCredentials437 SSOCredentials = linux.SSOCredentials
417 CredentialsManagement = linux.CredentialsManagement438 CredentialsManagement = linux.CredentialsManagement
439 thread_execute = linux.blocking
418440
=== modified file 'ubuntu_sso/main/linux.py'
--- ubuntu_sso/main/linux.py 2011-06-24 19:14:17 +0000
+++ ubuntu_sso/main/linux.py 2011-08-25 19:16:19 +0000
@@ -74,6 +74,8 @@
74 dbus.service.Object.__init__(self, object_path=object_path,74 dbus.service.Object.__init__(self, object_path=object_path,
75 bus_name=bus_name)75 bus_name=bus_name)
76 self.root = SSOLoginRoot(sso_login_processor_class, sso_service_class)76 self.root = SSOLoginRoot(sso_login_processor_class, sso_service_class)
77 msg = 'Use ubuntu_sso.main.CredentialsManagement instead.'
78 warnings.warn(msg, DeprecationWarning)
7779
78 # generate_capcha signals80 # generate_capcha signals
79 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")81 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
@@ -92,7 +94,7 @@
92 in_signature='ss')94 in_signature='ss')
93 def generate_captcha(self, app_name, filename):95 def generate_captcha(self, app_name, filename):
94 """Call the matching method in the processor."""96 """Call the matching method in the processor."""
95 self.root.generate_captcha(app_name, filename, blocking,97 self.root.generate_captcha(app_name, filename,
96 self.CaptchaGenerated,98 self.CaptchaGenerated,
97 self.CaptchaGenerationError)99 self.CaptchaGenerationError)
98100
@@ -115,7 +117,7 @@
115 captcha_id, captcha_solution):117 captcha_id, captcha_solution):
116 """Call the matching method in the processor."""118 """Call the matching method in the processor."""
117 self.root.register_user(app_name, email, password, name, captcha_id,119 self.root.register_user(app_name, email, password, name, captcha_id,
118 captcha_solution, blocking,120 captcha_solution,
119 self.UserRegistered,121 self.UserRegistered,
120 self.UserRegistrationError)122 self.UserRegistrationError)
121123
@@ -142,7 +144,7 @@
142 in_signature='sss')144 in_signature='sss')
143 def login(self, app_name, email, password):145 def login(self, app_name, email, password):
144 """Call the matching method in the processor."""146 """Call the matching method in the processor."""
145 self.root.login(app_name, email, password, blocking, self.LoggedIn,147 self.root.login(app_name, email, password, self.LoggedIn,
146 self.LoginError, self.UserNotValidated)148 self.LoginError, self.UserNotValidated)
147149
148 # validate_email signals150 # validate_email signals
@@ -163,7 +165,7 @@
163 def validate_email(self, app_name, email, password, email_token):165 def validate_email(self, app_name, email, password, email_token):
164 """Call the matching method in the processor."""166 """Call the matching method in the processor."""
165 self.root.validate_email(app_name, email, password, email_token,167 self.root.validate_email(app_name, email, password, email_token,
166 blocking, self.EmailValidated,168 self.EmailValidated,
167 self.EmailValidationError)169 self.EmailValidationError)
168170
169 # request_password_reset_token signals171 # request_password_reset_token signals
@@ -183,7 +185,7 @@
183 in_signature='ss')185 in_signature='ss')
184 def request_password_reset_token(self, app_name, email):186 def request_password_reset_token(self, app_name, email):
185 """Call the matching method in the processor."""187 """Call the matching method in the processor."""
186 self.root.request_password_reset_token(app_name, email, blocking,188 self.root.request_password_reset_token(app_name, email,
187 self.PasswordResetTokenSent,189 self.PasswordResetTokenSent,
188 self.PasswordResetError)190 self.PasswordResetError)
189191
@@ -205,7 +207,7 @@
205 def set_new_password(self, app_name, email, token, new_password):207 def set_new_password(self, app_name, email, token, new_password):
206 """Call the matching method in the processor."""208 """Call the matching method in the processor."""
207 self.root.set_new_password(app_name, email, token, new_password,209 self.root.set_new_password(app_name, email, token, new_password,
208 blocking, self.PasswordChanged,210 self.PasswordChanged,
209 self.PasswordChangeError)211 self.PasswordChangeError)
210212
211213
@@ -462,3 +464,15 @@
462 def login(self, app_name, args):464 def login(self, app_name, args):
463 """Get credentials if found else prompt GUI to login."""465 """Get credentials if found else prompt GUI to login."""
464 self.root.login(app_name, args)466 self.root.login(app_name, args)
467
468 @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
469 in_signature='sa{ss}', out_signature='')
470 def login_email_password(self, app_name, args):
471 """Get credentials if found, else login using email and password.
472
473 - 'args' should contain at least the follwing keys: 'email' and
474 'password'. Those will be used to issue a new SSO token, which will be
475 returned trough the CredentialsFound signal.
476
477 """
478 self.root.login_email_password(app_name, args)
465479
=== modified file 'ubuntu_sso/main/tests/test_common.py'
--- ubuntu_sso/main/tests/test_common.py 2011-06-24 19:14:17 +0000
+++ ubuntu_sso/main/tests/test_common.py 2011-08-25 19:16:19 +0000
@@ -75,7 +75,7 @@
75 """Test that the call is relayed."""75 """Test that the call is relayed."""
76 app_name = 'app'76 app_name = 'app'
77 filename = 'file'77 filename = 'file'
78 self.root.generate_captcha(app_name, filename, MATCH(callable),78 self.root.generate_captcha(app_name, filename,
79 MATCH(callable), MATCH(callable))79 MATCH(callable), MATCH(callable))
80 self.mocker.replay()80 self.mocker.replay()
81 self.login.generate_captcha(app_name, filename)81 self.login.generate_captcha(app_name, filename)
@@ -89,7 +89,7 @@
89 captcha_id = 'id'89 captcha_id = 'id'
90 captcha_solution = 'hello'90 captcha_solution = 'hello'
91 self.root.register_user(app_name, email, password, name, captcha_id,91 self.root.register_user(app_name, email, password, name, captcha_id,
92 captcha_solution, MATCH(callable),92 captcha_solution,
93 MATCH(callable), MATCH(callable))93 MATCH(callable), MATCH(callable))
94 self.mocker.replay()94 self.mocker.replay()
95 self.login.register_user(app_name, email, password, name, captcha_id,95 self.login.register_user(app_name, email, password, name, captcha_id,
@@ -100,7 +100,7 @@
100 app_name = 'app'100 app_name = 'app'
101 email = 'email'101 email = 'email'
102 password = 'password'102 password = 'password'
103 self.root.login(app_name, email, password, MATCH(callable),103 self.root.login(app_name, email, password,
104 MATCH(callable), MATCH(callable),104 MATCH(callable), MATCH(callable),
105 MATCH(callable))105 MATCH(callable))
106 self.mocker.mock()106 self.mocker.mock()
@@ -114,18 +114,17 @@
114 password = 'passwrd'114 password = 'passwrd'
115 email_token = 'token'115 email_token = 'token'
116 self.root.validate_email(app_name, email, password, email_token,116 self.root.validate_email(app_name, email, password, email_token,
117 MATCH(callable), MATCH(callable),117 MATCH(callable),
118 MATCH(callable))118 MATCH(callable))
119 self.mocker.replay()119 self.mocker.replay()
120 self.login.validate_email(app_name, email, password, email_token)120 self.login.validate_email(app_name, email, password, email_token)
121121
122 def test_request_password_reset_tolen(self):122 def test_request_password_reset_token(self):
123 """Test that the call is relayed."""123 """Test that the call is relayed."""
124 app_name = 'app'124 app_name = 'app'
125 email = 'email'125 email = 'email'
126 self.root.request_password_reset_token(app_name, email,126 self.root.request_password_reset_token(app_name, email,
127 MATCH(callable),127 MATCH(callable),
128 MATCH(callable),
129 MATCH(callable))128 MATCH(callable))
130 self.mocker.replay()129 self.mocker.replay()
131 self.login.request_password_reset_token(app_name, email)130 self.login.request_password_reset_token(app_name, email)
@@ -137,7 +136,7 @@
137 token = 'token'136 token = 'token'
138 new_password = 'new'137 new_password = 'new'
139 self.root.set_new_password(app_name, email, token, new_password,138 self.root.set_new_password(app_name, email, token, new_password,
140 MATCH(callable), MATCH(callable),139 MATCH(callable),
141 MATCH(callable))140 MATCH(callable))
142 self.mocker.replay()141 self.mocker.replay()
143 self.login.set_new_password(app_name, email, token, new_password)142 self.login.set_new_password(app_name, email, token, new_password)
@@ -190,7 +189,7 @@
190 self.cred.clear_token(app_name, result_cb, error_cb)189 self.cred.clear_token(app_name, result_cb, error_cb)
191190
192191
193class CredentialsManagementMockedTestCase(MockerTestCase):192class CredentialsManagementMockedTestCase(MockerTestCase, TestCase):
194 """Test that the call are relied correctly."""193 """Test that the call are relied correctly."""
195194
196 def setUp(self):195 def setUp(self):
@@ -242,3 +241,11 @@
242 self.root.login(app_name, args)241 self.root.login(app_name, args)
243 self.mocker.replay()242 self.mocker.replay()
244 self.cred.login(app_name, args)243 self.cred.login(app_name, args)
244
245 def test_login_email_password(self):
246 """Test that the call is relayed."""
247 app_name = 'app'
248 args = 'args'
249 self.root.login_email_password(app_name, args)
250 self.mocker.replay()
251 self.cred.login_email_password(app_name, args)
245252
=== modified file 'ubuntu_sso/main/tests/test_linux.py'
--- ubuntu_sso/main/tests/test_linux.py 2011-06-24 19:14:17 +0000
+++ ubuntu_sso/main/tests/test_linux.py 2011-08-25 19:16:19 +0000
@@ -144,7 +144,7 @@
144 expected_result = "expected result"144 expected_result = "expected result"
145 self.create_mock_processor().generate_captcha(filename)145 self.create_mock_processor().generate_captcha(filename)
146 self.mocker.result(expected_result)146 self.mocker.result(expected_result)
147 self.patch(ubuntu_sso.main.linux, "blocking", fake_err_blocking)147 self.patch(ubuntu_sso.main, "thread_execute", fake_err_blocking)
148 self.mocker.replay()148 self.mocker.replay()
149149
150 def verify(app_name, errdict):150 def verify(app_name, errdict):
@@ -191,7 +191,7 @@
191 self.create_mock_processor().register_user(EMAIL, PASSWORD, NAME,191 self.create_mock_processor().register_user(EMAIL, PASSWORD, NAME,
192 CAPTCHA_ID, CAPTCHA_SOLUTION)192 CAPTCHA_ID, CAPTCHA_SOLUTION)
193 self.mocker.result(expected_result)193 self.mocker.result(expected_result)
194 self.patch(ubuntu_sso.main.linux, "blocking", fake_err_blocking)194 self.patch(ubuntu_sso.main, "thread_execute", fake_err_blocking)
195 self.mocker.replay()195 self.mocker.replay()
196196
197 def verify(app_name, errdict):197 def verify(app_name, errdict):
@@ -377,7 +377,7 @@
377 d = Deferred()377 d = Deferred()
378 processor = self.create_mock_processor()378 processor = self.create_mock_processor()
379 processor.request_password_reset_token(EMAIL)379 processor.request_password_reset_token(EMAIL)
380 self.patch(ubuntu_sso.main.linux, "blocking", fake_ok_blocking)380 self.patch(ubuntu_sso.main, "thread_execute", fake_ok_blocking)
381 self.mocker.result(EMAIL)381 self.mocker.result(EMAIL)
382 self.mocker.replay()382 self.mocker.replay()
383383
@@ -400,7 +400,7 @@
400400
401 self.create_mock_processor().request_password_reset_token(EMAIL)401 self.create_mock_processor().request_password_reset_token(EMAIL)
402 self.mocker.result(EMAIL)402 self.mocker.result(EMAIL)
403 self.patch(ubuntu_sso.main.linux, "blocking", fake_err_blocking)403 self.patch(ubuntu_sso.main, "thread_execute", fake_err_blocking)
404 self.mocker.replay()404 self.mocker.replay()
405405
406 def verify(app_name, errdict):406 def verify(app_name, errdict):
@@ -446,7 +446,7 @@
446 self.create_mock_processor().set_new_password(EMAIL, EMAIL_TOKEN,446 self.create_mock_processor().set_new_password(EMAIL, EMAIL_TOKEN,
447 PASSWORD)447 PASSWORD)
448 self.mocker.result(expected_result)448 self.mocker.result(expected_result)
449 self.patch(ubuntu_sso.main.linux, "blocking", fake_err_blocking)449 self.patch(ubuntu_sso.main, "thread_execute", fake_err_blocking)
450 self.mocker.replay()450 self.mocker.replay()
451451
452 def verify(app_name, errdict):452 def verify(app_name, errdict):
@@ -1222,6 +1222,17 @@
1222 self.client.login(APP_NAME, self.args)1222 self.client.login(APP_NAME, self.args)
1223 self.assert_dbus_method_correct(self.client.login)1223 self.assert_dbus_method_correct(self.client.login)
12241224
1225 def test_login_email_password(self):
1226 """The login_email_password is correct."""
1227 self.create_mock_backend().login_email_password(email=EMAIL,
1228 password=PASSWORD)
1229 self.mocker.replay()
1230
1231 self.args['email'] = EMAIL
1232 self.args['password'] = PASSWORD
1233 self.client.login_email_password(APP_NAME, self.args)
1234 self.assert_dbus_method_correct(self.client.login_email_password)
1235
12251236
1226class CredentialsManagementParamsTestCase(CredentialsManagementOpsTestCase):1237class CredentialsManagementParamsTestCase(CredentialsManagementOpsTestCase):
1227 """Tests for the CredentialsManagement extra parameters handling."""1238 """Tests for the CredentialsManagement extra parameters handling."""
12281239
=== modified file 'ubuntu_sso/main/tests/test_windows.py'
--- ubuntu_sso/main/tests/test_windows.py 2011-07-22 20:12:20 +0000
+++ ubuntu_sso/main/tests/test_windows.py 2011-08-25 19:16:19 +0000
@@ -30,10 +30,10 @@
30)30)
31from ubuntu_sso.main import windows31from ubuntu_sso.main import windows
32from ubuntu_sso.main.windows import (32from ubuntu_sso.main.windows import (
33 blocking,
34 signal,33 signal,
35 CredentialsManagement,34 CredentialsManagement,
36 CredentialsManagementClient,35 CredentialsManagementClient,
36 LOCALHOST,
37 SignalBroadcaster,37 SignalBroadcaster,
38 SSOCredentials,38 SSOCredentials,
39 SSOCredentialsClient,39 SSOCredentialsClient,
@@ -41,7 +41,8 @@
41 SSOLoginClient,41 SSOLoginClient,
42 UbuntuSSORoot,42 UbuntuSSORoot,
43 UbuntuSSOClient,43 UbuntuSSOClient,
44 get_sso_pb_hostport)44 get_sso_pb_port,
45)
4546
46# because we are using twisted we have java like names C0103 and47# because we are using twisted we have java like names C0103 and
47# the issues that mocker brings with it like W010448# the issues that mocker brings with it like W0104
@@ -191,10 +192,10 @@
191 self.sso_root = UbuntuSSORoot(self.login, None, None)192 self.sso_root = UbuntuSSORoot(self.login, None, None)
192 self.server_factory = SaveProtocolServerFactory(self.sso_root)193 self.server_factory = SaveProtocolServerFactory(self.sso_root)
193 # pylint: disable=E1101194 # pylint: disable=E1101
194 host, port = get_sso_pb_hostport()195 port = get_sso_pb_port()
195 self.listener = reactor.listenTCP(port, self.server_factory)196 self.listener = reactor.listenTCP(port, self.server_factory)
196 self.client_factory = PBClientFactory()197 self.client_factory = PBClientFactory()
197 self.connector = reactor.connectTCP(host, port,198 self.connector = reactor.connectTCP(LOCALHOST, port,
198 self.client_factory)199 self.client_factory)
199 # pylint: enable=E1101200 # pylint: enable=E1101
200201
@@ -292,7 +293,7 @@
292 @defer.inlineCallbacks293 @defer.inlineCallbacks
293 def test_execution(client):294 def test_execution(client):
294 """Actual test."""295 """Actual test."""
295 self.root.generate_captcha(app_name, filename, blocking,296 self.root.generate_captcha(app_name, filename,
296 self.login.emit_captcha_generated,297 self.login.emit_captcha_generated,
297 self.login.emit_captcha_generation_error)298 self.login.emit_captcha_generation_error)
298 self.mocker.replay()299 self.mocker.replay()
@@ -364,7 +365,7 @@
364 def test_execution(client):365 def test_execution(client):
365 """Actual test."""366 """Actual test."""
366 self.root.register_user(app_name, email, password, displayname,367 self.root.register_user(app_name, email, password, displayname,
367 captcha_id, captcha_solution, blocking,368 captcha_id, captcha_solution,
368 self.login.emit_user_registered,369 self.login.emit_user_registered,
369 self.login.emit_user_registration_error)370 self.login.emit_user_registration_error)
370 self.mocker.replay()371 self.mocker.replay()
@@ -453,7 +454,7 @@
453 @defer.inlineCallbacks454 @defer.inlineCallbacks
454 def test_execution(client):455 def test_execution(client):
455 """Actual test."""456 """Actual test."""
456 self.root.login(app_name, email, password, blocking,457 self.root.login(app_name, email, password,
457 self.login.emit_logged_in,458 self.login.emit_logged_in,
458 self.login.emit_login_error,459 self.login.emit_login_error,
459 self.login.emit_user_not_validated)460 self.login.emit_user_not_validated)
@@ -524,7 +525,7 @@
524 def test_execution(client):525 def test_execution(client):
525 """Actual test."""526 """Actual test."""
526 self.root.validate_email(app_name, email, password, email_token,527 self.root.validate_email(app_name, email, password, email_token,
527 blocking, self.login.emit_email_validated,528 self.login.emit_email_validated,
528 self.login.emit_email_validation_error)529 self.login.emit_email_validation_error)
529 self.mocker.replay()530 self.mocker.replay()
530 yield client.validate_email(app_name, email, password, email_token)531 yield client.validate_email(app_name, email, password, email_token)
@@ -590,7 +591,7 @@
590 @defer.inlineCallbacks591 @defer.inlineCallbacks
591 def test_execution(client):592 def test_execution(client):
592 """Actual test."""593 """Actual test."""
593 self.root.request_password_reset_token(app_name, email, blocking,594 self.root.request_password_reset_token(app_name, email,
594 self.login.emit_password_reset_token_sent,595 self.login.emit_password_reset_token_sent,
595 self.login.emit_password_reset_error)596 self.login.emit_password_reset_error)
596 self.mocker.replay()597 self.mocker.replay()
@@ -660,7 +661,7 @@
660 def test_execution(client):661 def test_execution(client):
661 """Actual test."""662 """Actual test."""
662 self.root.set_new_password(app_name, email, token, new_password,663 self.root.set_new_password(app_name, email, token, new_password,
663 blocking, self.login.emit_password_changed,664 self.login.emit_password_changed,
664 self.login.emit_password_change_error)665 self.login.emit_password_change_error)
665 self.mocker.replay()666 self.mocker.replay()
666 yield client.set_new_password(app_name, email, token, new_password)667 yield client.set_new_password(app_name, email, token, new_password)
@@ -690,10 +691,10 @@
690 self.sso_root = UbuntuSSORoot(None, self.creds, None)691 self.sso_root = UbuntuSSORoot(None, self.creds, None)
691 self.server_factory = SaveProtocolServerFactory(self.sso_root)692 self.server_factory = SaveProtocolServerFactory(self.sso_root)
692 # pylint: disable=E1101693 # pylint: disable=E1101
693 host, port = get_sso_pb_hostport()694 port = get_sso_pb_port()
694 self.listener = reactor.listenTCP(port, self.server_factory)695 self.listener = reactor.listenTCP(port, self.server_factory)
695 self.client_factory = PBClientFactory()696 self.client_factory = PBClientFactory()
696 self.connector = reactor.connectTCP(host, port,697 self.connector = reactor.connectTCP(LOCALHOST, port,
697 self.client_factory)698 self.client_factory)
698 # pylint: enable=E1101699 # pylint: enable=E1101
699700
@@ -945,10 +946,10 @@
945 self.sso_root = UbuntuSSORoot(None, None, self.creds)946 self.sso_root = UbuntuSSORoot(None, None, self.creds)
946 self.server_factory = SaveProtocolServerFactory(self.sso_root)947 self.server_factory = SaveProtocolServerFactory(self.sso_root)
947 # pylint: disable=E1101948 # pylint: disable=E1101
948 host, port = get_sso_pb_hostport()949 port = get_sso_pb_port()
949 self.listener = reactor.listenTCP(port, self.server_factory)950 self.listener = reactor.listenTCP(port, self.server_factory)
950 self.client_factory = PBClientFactory()951 self.client_factory = PBClientFactory()
951 self.connector = reactor.connectTCP(host, port,952 self.connector = reactor.connectTCP(LOCALHOST, port,
952 self.client_factory)953 self.client_factory)
953 # pylint: enable=E1101954 # pylint: enable=E1101
954955
@@ -1257,6 +1258,25 @@
1257 # pylint: enable=E11011258 # pylint: enable=E1101
1258 return d1259 return d
12591260
1261 def test_login_email_password(self):
1262 """Test that root is called."""
1263 app_name = 'app'
1264 args = 'args'
1265
1266 @defer.inlineCallbacks
1267 def test_execution(client):
1268 """Actual test."""
1269 self.root.login_email_password(app_name, args)
1270 self.mocker.replay()
1271 yield client.login_email_password(app_name, args)
1272 yield client.unregister_to_signals()
1273
1274 d = self._get_client()
1275 # pylint: disable=E1101
1276 d.addCallback(test_execution)
1277 # pylint: enable=E1101
1278 return d
1279
12601280
1261class MockRemoteObject(object):1281class MockRemoteObject(object):
1262 """A mock RemoteObject."""1282 """A mock RemoteObject."""
@@ -1330,7 +1350,6 @@
1330 self.assertEqual(connect_result, ussoc)1350 self.assertEqual(connect_result, ussoc)
1331 self.assertTrue(mac.port_requested, "The port must be requested.")1351 self.assertTrue(mac.port_requested, "The port must be requested.")
1332 self.assertNotEqual(ussoc.sso_login, None)1352 self.assertNotEqual(ussoc.sso_login, None)
1333 self.assertNotEqual(ussoc.sso_cred, None)
1334 self.assertNotEqual(ussoc.cred_management, None)1353 self.assertNotEqual(ussoc.cred_management, None)
13351354
1336 def test_get_activation_cmdline(self):1355 def test_get_activation_cmdline(self):
@@ -1340,3 +1359,69 @@
1340 self.patch(windows, "QueryValueEx", lambda key, v: (key, REG_SZ))1359 self.patch(windows, "QueryValueEx", lambda key, v: (key, REG_SZ))
1341 cmdline = windows.get_activation_cmdline(windows.SSO_SERVICE_NAME)1360 cmdline = windows.get_activation_cmdline(windows.SSO_SERVICE_NAME)
1342 self.assertEqual(SAMPLE_VALUE, cmdline)1361 self.assertEqual(SAMPLE_VALUE, cmdline)
1362
1363
1364class MockWin32APIs(object):
1365 """Some mock win32apis."""
1366
1367 process_handle = object()
1368 TOKEN_ALL_ACCESS = object()
1369 TokenUser = object()
1370
1371 def __init__(self, sample_token):
1372 """Initialize this mock instance."""
1373 self.sample_token = sample_token
1374 self.token_handle = object()
1375
1376 def GetCurrentProcess(self):
1377 """Returns a fake process_handle."""
1378 return self.process_handle
1379
1380 def OpenProcessToken(self, process_handle, access):
1381 """Open the process token."""
1382 assert process_handle is self.process_handle
1383 assert access is self.TOKEN_ALL_ACCESS
1384 return self.token_handle
1385
1386 def GetTokenInformation(self, token_handle, info):
1387 """Get the information for this token."""
1388 assert token_handle == self.token_handle
1389 assert info == self.TokenUser
1390 return (self.sample_token, 0)
1391
1392
1393class AssortedTestCase(TestCase):
1394 """Tests for functions in the windows module."""
1395
1396 def test_get_userid(self):
1397 """The returned user id is parsed ok."""
1398 expected_id = 1001
1399 sample_token = "abc-123-1001"
1400
1401 win32apis = MockWin32APIs(sample_token)
1402 self.patch(windows, "win32process", win32apis)
1403 self.patch(windows, "win32security", win32apis)
1404
1405 userid = windows.get_user_id()
1406 self.assertEqual(userid, expected_id)
1407
1408 def _test_port_assignation(self, uid, expected_port):
1409 """Test a given uid/expected port combo."""
1410 self.patch(windows, "get_user_id", lambda: uid)
1411 self.assertEqual(windows.get_sso_pb_port(), expected_port)
1412
1413 def test_get_sso_pb_port(self):
1414 """Test the get_sso_pb_port function."""
1415 uid = 1001
1416 uid_modulo = uid % windows.SSO_RESERVED_PORTS
1417 expected_port = (windows.SSO_BASE_PB_PORT +
1418 uid_modulo * windows.SSO_PORT_ALLOCATION_STEP)
1419 self._test_port_assignation(uid, expected_port)
1420
1421 def test_get_sso_pb_port_alt(self):
1422 """Test the get_sso_pb_port function."""
1423 uid = 2011 + windows.SSO_RESERVED_PORTS
1424 uid_modulo = uid % windows.SSO_RESERVED_PORTS
1425 expected_port = (windows.SSO_BASE_PB_PORT +
1426 uid_modulo * windows.SSO_PORT_ALLOCATION_STEP)
1427 self._test_port_assignation(uid, expected_port)
13431428
=== modified file 'ubuntu_sso/main/windows.py'
--- ubuntu_sso/main/windows.py 2011-07-22 20:12:20 +0000
+++ ubuntu_sso/main/windows.py 2011-08-25 19:16:19 +0000
@@ -17,11 +17,16 @@
17# with this program. If not, see <http://www.gnu.org/licenses/>.17# with this program. If not, see <http://www.gnu.org/licenses/>.
18"""Main implementation on windows."""18"""Main implementation on windows."""
1919
20import warnings
21
20# pylint: disable=F040122# pylint: disable=F0401
21from _winreg import HKEY_LOCAL_MACHINE, OpenKey, QueryValueEx23from _winreg import HKEY_LOCAL_MACHINE, OpenKey, QueryValueEx
2224
23from functools import wraps25from functools import wraps
2426
27import win32process
28import win32security
29
25from twisted.internet import defer, reactor30from twisted.internet import defer, reactor
26from twisted.internet.threads import deferToThread31from twisted.internet.threads import deferToThread
27from twisted.spread.pb import (32from twisted.spread.pb import (
@@ -44,19 +49,54 @@
44U1_REG_PATH = r'Software\Ubuntu One'49U1_REG_PATH = r'Software\Ubuntu One'
45SSO_INSTALL_PATH = 'SSOInstallPath'50SSO_INSTALL_PATH = 'SSOInstallPath'
46LOCALHOST = "127.0.0.1"51LOCALHOST = "127.0.0.1"
47SSO_DEFAULT_PB_PORT = 5000152SSO_BASE_PB_PORT = 50000
53SSO_RESERVED_PORTS = 3000
54SSO_PORT_ALLOCATION_STEP = 3 # contiguous ports for sso, u1client, and u1cp
48SSO_SERVICE_NAME = "ubuntu-sso-client"55SSO_SERVICE_NAME = "ubuntu-sso-client"
4956
5057
51def get_sso_pb_hostport():58def get_user_id():
52 """Get the host and port on which the SSO pb is running."""59 """Find the numeric user id."""
53 return LOCALHOST, SSO_DEFAULT_PB_PORT60 process_handle = win32process.GetCurrentProcess()
61 token_handle = win32security.OpenProcessToken(process_handle,
62 win32security.TOKEN_ALL_ACCESS)
63 user_sid = win32security.GetTokenInformation(token_handle,
64 win32security.TokenUser)[0]
65 sid_parts = str(user_sid).split("-")
66 uid = int(sid_parts[-1])
67 return uid
68
69
70def get_sso_pb_port():
71 """Get the port on which the SSO pb is running."""
72 uid = get_user_id()
73 uid_modulo = uid % SSO_RESERVED_PORTS
74 port = SSO_BASE_PB_PORT + uid_modulo * SSO_PORT_ALLOCATION_STEP
75 return port
5476
5577
56def remote_handler(handler):78def remote_handler(handler):
57 """Execute a callback in a remote object."""79 """Execute a callback in a remote object.
80
81 If the callback takes arguments, it's assumed that the last
82 one is a twisted Failure, and it has no keyword arguments.
83 """
58 if handler:84 if handler:
59 return lambda: handler.callRemote('execute')85
86 def f(*args):
87 """Process arguments and call remote."""
88 try:
89 args = list(args)
90 if args:
91 args[-1] = except_to_errdict(args[-1].value)
92 return handler.callRemote('execute', *args)
93 # Yes, I want to catch everything
94 # pylint: disable=W0703
95 except Exception:
96 logger.exception("Remote handler argument processing error:")
97 return f
98
99 logger.warning("Remote handler got an empty handler.")
60 return lambda: None100 return lambda: None
61101
62102
@@ -70,7 +110,7 @@
70110
71def get_activation_config():111def get_activation_config():
72 """Get the configuration to activate the sso service."""112 """Get the configuration to activate the sso service."""
73 _, port = get_sso_pb_hostport()113 port = get_sso_pb_port()
74 service_name = SSO_SERVICE_NAME114 service_name = SSO_SERVICE_NAME
75 cmdline = get_activation_cmdline(service_name)115 cmdline = get_activation_cmdline(service_name)
76 return ActivationConfig(service_name, cmdline, port)116 return ActivationConfig(service_name, cmdline, port)
@@ -82,7 +122,8 @@
82 # the calls in twisted will be called with the args in a diff order,122 # the calls in twisted will be called with the args in a diff order,
83 # in order to follow the linux api, we swap them around with a lambda123 # in order to follow the linux api, we swap them around with a lambda
84 d.addCallback(lambda result, app: result_cb(app, result), app_name)124 d.addCallback(lambda result, app: result_cb(app, result), app_name)
85 d.addErrback(lambda err, app: error_cb(app, err), app_name)125 d.addErrback(lambda err, app:
126 error_cb(app, except_to_errdict(err.value)), app_name)
86127
87128
88class RemoteMeta(type):129class RemoteMeta(type):
@@ -177,7 +218,7 @@
177218
178 def generate_captcha(self, app_name, filename):219 def generate_captcha(self, app_name, filename):
179 """Call the matching method in the processor."""220 """Call the matching method in the processor."""
180 self.root.generate_captcha(app_name, filename, blocking,221 self.root.generate_captcha(app_name, filename,
181 self.emit_captcha_generated,222 self.emit_captcha_generated,
182 self.emit_captcha_generation_error)223 self.emit_captcha_generation_error)
183224
@@ -199,7 +240,7 @@
199 captcha_id, captcha_solution):240 captcha_id, captcha_solution):
200 """Call the matching method in the processor."""241 """Call the matching method in the processor."""
201 self.root.register_user(app_name, email, password, displayname,242 self.root.register_user(app_name, email, password, displayname,
202 captcha_id, captcha_solution, blocking,243 captcha_id, captcha_solution,
203 self.emit_user_registered,244 self.emit_user_registered,
204 self.emit_user_registration_error)245 self.emit_user_registration_error)
205246
@@ -225,7 +266,7 @@
225266
226 def login(self, app_name, email, password):267 def login(self, app_name, email, password):
227 """Call the matching method in the processor."""268 """Call the matching method in the processor."""
228 self.root.login(app_name, email, password, blocking,269 self.root.login(app_name, email, password,
229 self.emit_logged_in, self.emit_login_error,270 self.emit_logged_in, self.emit_login_error,
230 self.emit_user_not_validated)271 self.emit_user_not_validated)
231272
@@ -246,7 +287,7 @@
246 def validate_email(self, app_name, email, password, email_token):287 def validate_email(self, app_name, email, password, email_token):
247 """Call the matching method in the processor."""288 """Call the matching method in the processor."""
248 self.root.validate_email(app_name, email, password, email_token,289 self.root.validate_email(app_name, email, password, email_token,
249 blocking, self.emit_email_validated,290 self.emit_email_validated,
250 self.emit_email_validation_error)291 self.emit_email_validation_error)
251292
252 # request_password_reset_token signals293 # request_password_reset_token signals
@@ -265,7 +306,7 @@
265306
266 def request_password_reset_token(self, app_name, email):307 def request_password_reset_token(self, app_name, email):
267 """Call the matching method in the processor."""308 """Call the matching method in the processor."""
268 self.root.request_password_reset_token(app_name, email, blocking,309 self.root.request_password_reset_token(app_name, email,
269 self.emit_password_reset_token_sent,310 self.emit_password_reset_token_sent,
270 self.emit_password_reset_error)311 self.emit_password_reset_error)
271312
@@ -286,7 +327,7 @@
286 def set_new_password(self, app_name, email, token, new_password):327 def set_new_password(self, app_name, email, token, new_password):
287 """Call the matching method in the processor."""328 """Call the matching method in the processor."""
288 self.root.set_new_password(app_name, email, token, new_password,329 self.root.set_new_password(app_name, email, token, new_password,
289 blocking, self.emit_password_changed,330 self.emit_password_changed,
290 self.emit_password_change_error)331 self.emit_password_change_error)
291332
292333
@@ -420,6 +461,7 @@
420 'register',461 'register',
421 'shutdown',462 'shutdown',
422 'login',463 'login',
464 'login_email_password',
423 ]465 ]
424466
425 def __init__(self, timeout_func, shutdown_func, *args, **kwargs):467 def __init__(self, timeout_func, shutdown_func, *args, **kwargs):
@@ -537,6 +579,10 @@
537 """Get credentials if found else prompt GUI to login."""579 """Get credentials if found else prompt GUI to login."""
538 self.root.login(app_name, args)580 self.root.login(app_name, args)
539581
582 def login_email_password(self, app_name, args):
583 """Get credentials if found, else login."""
584 self.root.login_email_password(app_name, args)
585
540586
541class UbuntuSSORoot(object, Root):587class UbuntuSSORoot(object, Root):
542 """Root object that exposes the diff referenceable objects."""588 """Root object that exposes the diff referenceable objects."""
@@ -571,27 +617,31 @@
571617
572def remote(function):618def remote(function):
573 """Decorate the function to make the remote call."""619 """Decorate the function to make the remote call."""
620
574 @wraps(function)621 @wraps(function)
575 def remote_wrapper(*args, **kwargs):622 def remote_wrapper(instance, *args, **kwargs):
576 """Return the deferred for the remote call."""623 """Return the deferred for the remote call."""
577 fixed_args = args[1:]624 fname = function.__name__
578 logger.info('Performing %s as a remote call.', function.func_name)625 logger.info('Performing %s as a remote call.', fname)
579 return args[0].remote.callRemote(function.func_name, *fixed_args,626 result = instance.remote.callRemote(fname, *args, **kwargs)
580 **kwargs)627 return result
628
581 return remote_wrapper629 return remote_wrapper
582630
583631
584def signal(function):632def signal(function):
585 """Decorate a function to perform the signal callback."""633 """Decorate a function to perform the signal callback."""
634
586 @wraps(function)635 @wraps(function)
587 def callback_wrapper(*args, **kwargs):636 def callback_wrapper(instance, *args, **kwargs):
588 """Return the result of the callback if present."""637 """Return the result of the callback if present."""
589 callback = getattr(args[0], function.func_name + '_cb', None)638 fname = function.__name__
639 callback = getattr(instance, fname + '_cb', None)
590 if callback is not None:640 if callback is not None:
591 fixed_args = args[1:]641 logger.info('Emitting remote signal for %s with callback %r.',
592 if not kwargs:642 fname, callback)
593 return callback(*fixed_args)643 return callback(*args, **kwargs)
594 return callback(*fixed_args, **kwargs)644
595 return callback_wrapper645 return callback_wrapper
596646
597647
@@ -618,10 +668,10 @@
618 """Create a new instance."""668 """Create a new instance."""
619 self.cb = cb669 self.cb = cb
620670
621 def remote_execute(self):671 def remote_execute(self, *args, **kwargs):
622 """Execute the callback."""672 """Execute the callback."""
623 if self.cb:673 if self.cb:
624 self.cb()674 self.cb(*args, **kwargs)
625675
626676
627def callbacks(callbacks_indexes=None, callbacks_names=None):677def callbacks(callbacks_indexes=None, callbacks_names=None):
@@ -755,7 +805,10 @@
755805
756806
757class SSOCredentialsClient(RemoteClient, Referenceable):807class SSOCredentialsClient(RemoteClient, Referenceable):
758 """Client that can perform calls to the remote SSOCredentials object."""808 """Deprecated client for the remote SSOCredentials object.
809
810 This class is deprecated!
811 """
759812
760 __metaclass__ = RemoteMeta813 __metaclass__ = RemoteMeta
761814
@@ -768,6 +821,8 @@
768821
769 def __init__(self, remote_login):822 def __init__(self, remote_login):
770 """Create a client for the cred API."""823 """Create a client for the cred API."""
824 warnings.warn("SSOCredentialsClient is deprecated.",
825 DeprecationWarning)
771 super(SSOCredentialsClient, self).__init__(remote_login)826 super(SSOCredentialsClient, self).__init__(remote_login)
772827
773 @signal828 @signal
@@ -913,6 +968,10 @@
913 def login(self, app_name, args):968 def login(self, app_name, args):
914 """Get credentials if found else prompt GUI to login."""969 """Get credentials if found else prompt GUI to login."""
915970
971 @remote
972 def login_email_password(self, app_name, args):
973 """Get credentials if found else login."""
974
916975
917class UbuntuSSOClientException(Exception):976class UbuntuSSOClientException(Exception):
918 """Raised when there are issues connecting to the process."""977 """Raised when there are issues connecting to the process."""
@@ -923,7 +982,6 @@
923982
924 def __init__(self):983 def __init__(self):
925 self.sso_login = None984 self.sso_login = None
926 self.sso_cred = None
927 self.cred_management = None985 self.cred_management = None
928 self.factory = None986 self.factory = None
929 self.client = None987 self.client = None
@@ -934,8 +992,6 @@
934 sso_login = yield root.callRemote('get_sso_login')992 sso_login = yield root.callRemote('get_sso_login')
935 logger.debug('SSOLogin is %s', sso_login)993 logger.debug('SSOLogin is %s', sso_login)
936 self.sso_login = SSOLoginClient(sso_login)994 self.sso_login = SSOLoginClient(sso_login)
937 sso_cred = yield root.callRemote('get_sso_credentials')
938 self.sso_cred = SSOCredentialsClient(sso_cred)
939 cred_management = yield root.callRemote('get_cred_manager')995 cred_management = yield root.callRemote('get_cred_manager')
940 self.cred_management = CredentialsManagementClient(cred_management)996 self.cred_management = CredentialsManagementClient(cred_management)
941 defer.returnValue(self)997 defer.returnValue(self)
942998
=== modified file 'ubuntu_sso/networkstate/windows.py'
--- ubuntu_sso/networkstate/windows.py 2011-03-22 23:29:20 +0000
+++ ubuntu_sso/networkstate/windows.py 2011-08-25 19:16:19 +0000
@@ -102,9 +102,9 @@
102102
103 def __init__(self, connected_cb=None, connected_cb_info=None,103 def __init__(self, connected_cb=None, connected_cb_info=None,
104 disconnected_cb=None):104 disconnected_cb=None):
105 # pylint: disable=E1101105 # pylint: disable=E1101,E1120,W0231
106 self._wrap_(self)106 self._wrap_(self)
107 # pylint: enable=E1101107 # pylint: enable=E1101,E1120
108 self.connected_cb = connected_cb108 self.connected_cb = connected_cb
109 self.connected_cb_info = connected_cb_info109 self.connected_cb_info = connected_cb_info
110 self.disconnected_cb = disconnected_cb110 self.disconnected_cb = disconnected_cb
@@ -131,6 +131,7 @@
131 """Register to listen to network events."""131 """Register to listen to network events."""
132 # call the CoInitialize to allow the registration to run in another132 # call the CoInitialize to allow the registration to run in another
133 # thread133 # thread
134 # pylint: disable=E1101
134 pythoncom.CoInitialize()135 pythoncom.CoInitialize()
135 # interface to be used by com136 # interface to be used by com
136 manager_interface = pythoncom.WrapObject(self)137 manager_interface = pythoncom.WrapObject(self)
@@ -157,6 +158,7 @@
157 'Error registering %s to event %s', e, current_event[1])158 'Error registering %s to event %s', e, current_event[1])
158159
159 pythoncom.PumpMessages()160 pythoncom.PumpMessages()
161 # pylint: enable=E1101
160162
161163
162def is_machine_connected():164def is_machine_connected():
163165
=== modified file 'ubuntu_sso/qt/controllers.py'
--- ubuntu_sso/qt/controllers.py 2011-07-22 20:12:20 +0000
+++ ubuntu_sso/qt/controllers.py 2011-08-25 19:16:19 +0000
@@ -20,10 +20,12 @@
20import StringIO20import StringIO
21import tempfile21import tempfile
2222
23# pylint: disable=F0401
23try:24try:
24 from PIL import Image25 from PIL import Image
25except ImportError:26except ImportError:
26 import Image27 import Image
28# pylint: enable=F0401
2729
28from PyQt4.QtGui import QMessageBox, QWizard, QPixmap30from PyQt4.QtGui import QMessageBox, QWizard, QPixmap
29from PyQt4.QtCore import QUrl31from PyQt4.QtCore import QUrl
@@ -34,6 +36,7 @@
34from ubuntu_sso.utils.ui import (36from ubuntu_sso.utils.ui import (
35 CAPTCHA_LOAD_ERROR,37 CAPTCHA_LOAD_ERROR,
36 CAPTCHA_SOLUTION_ENTRY,38 CAPTCHA_SOLUTION_ENTRY,
39 CAPTCHA_REQUIRED_ERROR,
37 EMAIL1_ENTRY,40 EMAIL1_ENTRY,
38 EMAIL2_ENTRY,41 EMAIL2_ENTRY,
39 EMAIL_LABEL,42 EMAIL_LABEL,
@@ -76,14 +79,38 @@
76# disabled warnings about TODO comments79# disabled warnings about TODO comments
7780
7881
82# Based on the gtk implementation, but added error_message
83# support, and rewritten.
84def _build_general_error_message(errordict):
85 """Concatenate __all__ and message from the errordict."""
86 result = None
87 msg1 = errordict.get('__all__')
88 msg2 = errordict.get('message')
89 if msg2 is None:
90 # See the errordict in LP: 828417
91 msg2 = errordict.get('error_message')
92 if msg1 is not None and msg2 is not None:
93 result = '\n'.join((msg1, msg2))
94 elif msg1 is not None:
95 result = msg1
96 elif msg2 is not None:
97 result = msg2
98 else:
99 # I give up.
100 result = "Error: %r" % errordict
101 return result
102
103
79class BackendController(object):104class BackendController(object):
80 """Represent a controller that talks with the sso self.backend."""105 """Represent a controller that talks with the sso self.backend."""
81106
82 def __init__(self):107 def __init__(self, title='', subtitle=''):
83 """Create a new instance."""108 """Create a new instance."""
84 self.root = None109 self.root = None
85 self.view = None110 self.view = None
86 self.backend = None111 self.backend = None
112 self._title = title
113 self._subtitle = subtitle
87114
88 def __del__(self):115 def __del__(self):
89 """Clean the resources."""116 """Clean the resources."""
@@ -105,22 +132,30 @@
105 yield self.backend.register_to_signals()132 yield self.backend.register_to_signals()
106 returnValue(self.backend)133 returnValue(self.backend)
107134
108135 #pylint: disable=C0103
109class ChooseSignInController(object):136 def pageInitialized(self):
137 """Call to prepare the page just before it is shown."""
138 pass
139 #pylint: enable=C0103
140
141
142class ChooseSignInController(BackendController):
110 """Controlled to the ChooseSignIn view/widget."""143 """Controlled to the ChooseSignIn view/widget."""
111144
112 def __init__(self, title=''):145 def __init__(self, title='', subtitle=''):
113 """Create a new instance to manage the view."""146 """Create a new instance to manage the view."""
147 super(ChooseSignInController, self).__init__(title, subtitle)
114 self.view = None148 self.view = None
115 self._title = title
116149
117 # use an ugly name just so have a simlar api as found in PyQt150 # use an ugly name just so have a simlar api as found in PyQt
118 #pylint: disable=C0103151 #pylint: disable=C0103
152
119 def setupUi(self, view):153 def setupUi(self, view):
120 """Perform the required actions to set up the ui."""154 """Perform the required actions to set up the ui."""
121 self.view = view155 self.view = view
122 self.view.setTitle(self._title)
123 self._set_up_translated_strings()156 self._set_up_translated_strings()
157 self.view.header.set_title(self._title)
158 self.view.header.set_subtitle(self._subtitle)
124 self._connect_buttons()159 self._connect_buttons()
125 #pylint: enable=C0103160 #pylint: enable=C0103
126161
@@ -157,12 +192,10 @@
157192
158 def __init__(self, backend=None, title='', subtitle='', message_box=None):193 def __init__(self, backend=None, title='', subtitle='', message_box=None):
159 """Create a new instance."""194 """Create a new instance."""
160 super(CurrentUserController, self).__init__()195 super(CurrentUserController, self).__init__(title, subtitle)
161 if message_box is None:196 if message_box is None:
162 message_box = QMessageBox197 message_box = QMessageBox
163 self.message_box = message_box198 self.message_box = message_box
164 self._title = title
165 self._subtitle = subtitle
166199
167 def _set_translated_strings(self):200 def _set_translated_strings(self):
168 """Set the translated strings."""201 """Set the translated strings."""
@@ -185,13 +218,24 @@
185 self.backend.on_logged_in_cb = self.on_logged_in218 self.backend.on_logged_in_cb = self.on_logged_in
186 self.view.ui.forgot_password_label.linkActivated.connect(219 self.view.ui.forgot_password_label.linkActivated.connect(
187 self.on_forgotten_password)220 self.on_forgotten_password)
221 self.view.ui.email_edit.textChanged.connect(self._validate)
222 self.view.ui.password_edit.textChanged.connect(self._validate)
223
224 def _validate(self):
225 """Perform input validation."""
226 valid = True
227 if not is_correct_email(unicode(self.view.ui.email_edit.text())):
228 valid = False
229 elif not unicode(self.view.ui.password_edit.text()):
230 valid = False
231 self.view.ui.sign_in_button.setEnabled(valid)
188232
189 def login(self):233 def login(self):
190 """Perform the login using the self.backend."""234 """Perform the login using the self.backend."""
191 logger.debug('CurrentUserController.login')235 logger.debug('CurrentUserController.login')
192 # grab the data from the view and call the backend236 # grab the data from the view and call the backend
193 email = str(self.view.ui.email_edit.text())237 email = unicode(self.view.ui.email_edit.text())
194 password = str(self.view.ui.password_edit.text())238 password = unicode(self.view.ui.password_edit.text())
195 d = self.backend.login(self.view.wizard().app_name, email, password)239 d = self.backend.login(self.view.wizard().app_name, email, password)
196 d.addErrback(self.on_login_error)240 d.addErrback(self.on_login_error)
197241
@@ -200,13 +244,12 @@
200 # let the user know244 # let the user know
201 logger.error('Got error when login %s, error: %s',245 logger.error('Got error when login %s, error: %s',
202 self.view.wizard().app_name, error)246 self.view.wizard().app_name, error)
203 self.message_box.critical(self.view, self.view.wizard().app_name,247 self.message_box.critical(_build_general_error_message(error))
204 error['message'])
205248
206 def on_logged_in(self, app_name, result):249 def on_logged_in(self, app_name, result):
207 """We managed to log in."""250 """We managed to log in."""
208 logger.info('Logged in for %s', app_name)251 logger.info('Logged in for %s', app_name)
209 email = str(self.view.ui.email_edit.text())252 email = unicode(self.view.ui.email_edit.text())
210 self.view.wizard().loginSuccess.emit(app_name, email)253 self.view.wizard().loginSuccess.emit(app_name, email)
211 logger.debug('Wizard.loginSuccess emitted.')254 logger.debug('Wizard.loginSuccess emitted.')
212255
@@ -223,9 +266,8 @@
223 """Setup the view."""266 """Setup the view."""
224 self.view = view267 self.view = view
225 self.backend = yield self.get_backend()268 self.backend = yield self.get_backend()
226 self.view.setTitle(self._title)269 self.view.header.set_title(self._title)
227 if self._subtitle:270 self.view.header.set_subtitle(self._subtitle)
228 self.view.setSubTitle(self._subtitle)
229 self._set_translated_strings()271 self._set_translated_strings()
230 self._connect_ui()272 self._connect_ui()
231 #pylint: enable=C0103273 #pylint: enable=C0103
@@ -234,9 +276,9 @@
234class SetUpAccountController(BackendController):276class SetUpAccountController(BackendController):
235 """Conroller for the setup account view."""277 """Conroller for the setup account view."""
236278
237 def __init__(self, message_box=None):279 def __init__(self, message_box=None, title='', subtitle=''):
238 """Create a new instance."""280 """Create a new instance."""
239 super(SetUpAccountController, self).__init__()281 super(SetUpAccountController, self).__init__(title, subtitle)
240 if message_box is None:282 if message_box is None:
241 message_box = QMessageBox283 message_box = QMessageBox
242 self.message_box = message_box284 self.message_box = message_box
@@ -285,8 +327,6 @@
285 logger.debug('SetUpAccountController._connect_ui_elements')327 logger.debug('SetUpAccountController._connect_ui_elements')
286 self.view.ui.terms_button.clicked.connect(self.set_next_tos)328 self.view.ui.terms_button.clicked.connect(self.set_next_tos)
287 self.view.ui.set_up_button.clicked.connect(self.set_next_validation)329 self.view.ui.set_up_button.clicked.connect(self.set_next_validation)
288 self.view.ui.terms_checkbox.stateChanged.connect(
289 self.view.ui.set_up_button.setEnabled)
290 self.view.ui.refresh_label.linkActivated.connect(lambda url: \330 self.view.ui.refresh_label.linkActivated.connect(lambda url: \
291 self._refresh_captcha())331 self._refresh_captcha())
292 # set the callbacks for the captcha generation332 # set the callbacks for the captcha generation
@@ -296,6 +336,38 @@
296 self.backend.on_user_registered_cb = self.on_user_registered336 self.backend.on_user_registered_cb = self.on_user_registered
297 self.backend.on_user_registration_error_cb = \337 self.backend.on_user_registration_error_cb = \
298 self.on_user_registration_error338 self.on_user_registration_error
339 # We need to check if we enable the button on many signals
340 self.view.ui.name_edit.textEdited.connect(self._enable_setup_button)
341 self.view.ui.email_edit.textEdited.connect(self._enable_setup_button)
342 self.view.ui.confirm_email_edit.textEdited.connect(
343 self._enable_setup_button)
344 self.view.ui.password_edit.textEdited.connect(
345 self._enable_setup_button)
346 self.view.ui.confirm_password_edit.textEdited.connect(
347 self._enable_setup_button)
348 self.view.ui.captcha_solution_edit.textEdited.connect(
349 self._enable_setup_button)
350 self.view.ui.terms_checkbox.stateChanged.connect(
351 self._enable_setup_button)
352
353 def _enable_setup_button(self):
354 """Only enable the setup button if the form is valid."""
355 email = unicode(self.view.ui.email_edit.text())
356 confirm_email = unicode(self.view.ui.confirm_email_edit.text())
357 password = unicode(self.view.ui.password_edit.text())
358 confirm_password = unicode(self.view.ui.confirm_password_edit.text())
359 captcha_solution = unicode(self.view.ui.captcha_solution_edit.text())
360
361 enabled = self.view.ui.terms_checkbox.isChecked() and \
362 captcha_solution and is_min_required_password(password) and \
363 password == confirm_password and is_correct_email(email) and \
364 email == confirm_email
365
366 self.view.ui.set_up_button.setEnabled(enabled)
367 self.view.ui.set_up_button.setProperty("DisabledState",
368 not self.view.ui.set_up_button.isEnabled())
369 self.view.ui.set_up_button.style().unpolish(self.view.ui.set_up_button)
370 self.view.ui.set_up_button.style().polish(self.view.ui.set_up_button)
299371
300 def _refresh_captcha(self):372 def _refresh_captcha(self):
301 """Refresh the captcha image shown in the ui."""373 """Refresh the captcha image shown in the ui."""
@@ -316,9 +388,9 @@
316 def _set_titles(self):388 def _set_titles(self):
317 """Set the diff titles of the view."""389 """Set the diff titles of the view."""
318 logger.debug('SetUpAccountController._set_titles')390 logger.debug('SetUpAccountController._set_titles')
319 wizard = self.view.wizard()391 self.view.header.set_title(
320 self.view.setTitle(JOIN_HEADER_LABEL % {'app_name': wizard.app_name})392 JOIN_HEADER_LABEL % {'app_name': self.view.wizard().app_name})
321 self.view.setSubTitle(wizard.help_text)393 self.view.header.set_subtitle(self.view.wizard().help_text)
322394
323 def _register_fields(self):395 def _register_fields(self):
324 """Register the diff fields of the Ui."""396 """Register the diff fields of the Ui."""
@@ -348,19 +420,14 @@
348 def on_captcha_generation_error(self, error):420 def on_captcha_generation_error(self, error):
349 """An error ocurred."""421 """An error ocurred."""
350 logger.debug('SetUpAccountController.on_captcha_generation_error')422 logger.debug('SetUpAccountController.on_captcha_generation_error')
351 self.message_box.critical(self.view, self.view.wizard().app_name,423 self.message_box.critical(CAPTCHA_LOAD_ERROR)
352 CAPTCHA_LOAD_ERROR)
353424
354 def on_user_registration_error(self, app_name, error):425 def on_user_registration_error(self, app_name, error):
355 """Let the user know we could not register."""426 """Let the user know we could not register."""
356 logger.debug('SetUpAccountController.on_user_registration_error')427 logger.debug('SetUpAccountController.on_user_registration_error')
357 # errors are returned as a dict with the data we want to show.428 # errors are returned as a dict with the data we want to show.
358 self._refresh_captcha()429 self._refresh_captcha()
359 errors = [v for _, v in sorted(error.iteritems())]430 self.message_box.critical(_build_general_error_message(error))
360 self.message_box.critical(
361 self.view,
362 self.view.wizard().app_name,
363 '\n'.join(errors))
364431
365 def on_user_registered(self, app_name, result):432 def on_user_registered(self, app_name, result):
366 """Execute when the user did register."""433 """Execute when the user did register."""
@@ -377,35 +444,35 @@
377 def validate_form(self):444 def validate_form(self):
378 """Validate the info of the form and return an error."""445 """Validate the info of the form and return an error."""
379 logger.debug('SetUpAccountController.validate_form')446 logger.debug('SetUpAccountController.validate_form')
380 email = str(self.view.ui.email_edit.text())447 email = unicode(self.view.ui.email_edit.text())
381 confirm_email = str(self.view.ui.confirm_email_edit.text())448 confirm_email = unicode(self.view.ui.confirm_email_edit.text())
382 password = str(self.view.ui.password_edit.text())449 password = unicode(self.view.ui.password_edit.text())
383 confirm_password = str(self.view.ui.confirm_password_edit.text())450 confirm_password = unicode(self.view.ui.confirm_password_edit.text())
451 captcha_solution = unicode(self.view.ui.captcha_solution_edit.text())
384 if not is_correct_email(email):452 if not is_correct_email(email):
385 self.message_box.critical(self.view, self.view.wizard().app_name,453 self.message_box.critical(EMAIL_INVALID)
386 EMAIL_INVALID)
387 if email != confirm_email:454 if email != confirm_email:
388 self.message_box.critical(self.view, self.view.wizard().app_name,455 self.message_box.critical(EMAIL_MISMATCH)
389 EMAIL_MISMATCH)
390 return False456 return False
391 if not is_min_required_password(password):457 if not is_min_required_password(password):
392 self.message_box.critical(self.view, self.view.wizard().app_name,458 self.message_box.critical(PASSWORD_TOO_WEAK)
393 PASSWORD_TOO_WEAK)
394 return False459 return False
395 if password != confirm_password:460 if password != confirm_password:
396 self.message_box.critical(self.view, self.view.wizard().app_name,461 self.message_box.critical(PASSWORD_MISMATCH)
397 PASSWORD_MISMATCH)462 return False
463 if not captcha_solution:
464 self.message_box.critical(CAPTCHA_REQUIRED_ERROR)
398 return False465 return False
399 return True466 return True
400467
401 def set_next_validation(self):468 def set_next_validation(self):
402 """Set the validation as the next page."""469 """Set the validation as the next page."""
403 logger.debug('SetUpAccountController.set_next_validation')470 logger.debug('SetUpAccountController.set_next_validation')
404 email = str(self.view.ui.email_edit.text())471 email = unicode(self.view.ui.email_edit.text())
405 password = str(self.view.ui.password_edit.text())472 password = unicode(self.view.ui.password_edit.text())
406 name = str(self.view.ui.name_edit.text())473 name = unicode(self.view.ui.name_edit.text())
407 captcha_id = self.view.captcha_id474 captcha_id = self.view.captcha_id
408 captcha_solution = str(self.view.ui.captcha_solution_edit.text())475 captcha_solution = unicode(self.view.ui.captcha_solution_edit.text())
409 # validate the current info of the form, try to perform the action476 # validate the current info of the form, try to perform the action
410 # to register the user, and then move foward477 # to register the user, and then move foward
411 if self.validate_form():478 if self.validate_form():
@@ -424,12 +491,12 @@
424 def is_correct_email_confirmation(self, email_address):491 def is_correct_email_confirmation(self, email_address):
425 """Return that the email is the same."""492 """Return that the email is the same."""
426 logger.debug('SetUpAccountController.is_correct_email_confirmation')493 logger.debug('SetUpAccountController.is_correct_email_confirmation')
427 return self.view.ui.email_edit.text() == email_address494 return unicode(self.view.ui.email_edit.text()) == email_address
428495
429 def is_correct_password_confirmation(self, password):496 def is_correct_password_confirmation(self, password):
430 """Return that the passwords are correct."""497 """Return that the passwords are correct."""
431 logger.debug('SetUpAccountController.is_correct_password_confirmation')498 logger.debug('SetUpAccountController.is_correct_password_confirmation')
432 return self.view.ui.password_edit.text() == password499 return unicode(self.view.ui.password_edit.text()) == password
433500
434 #pylint: disable=C0103501 #pylint: disable=C0103
435 @inlineCallbacks502 @inlineCallbacks
@@ -441,28 +508,29 @@
441 self._connect_ui_elements()508 self._connect_ui_elements()
442 self._refresh_captcha()509 self._refresh_captcha()
443 self._set_titles()510 self._set_titles()
511 self.view.header.set_title(self._title)
512 self.view.header.set_subtitle(self._subtitle)
444 self._set_translated_strings()513 self._set_translated_strings()
445 self._set_line_edits_validations()514 self._set_line_edits_validations()
446 self._register_fields()515 self._register_fields()
447 #pylint: enable=C0103516 #pylint: enable=C0103
448517
449518
450class TosController(object):519class TosController(BackendController):
451 """Controller used for the tos page."""520 """Controller used for the tos page."""
452521
453 def __init__(self, title='', subtitle='', tos_url=''):522 def __init__(self, title='', subtitle='', tos_url=''):
454 """Create a new instance."""523 """Create a new instance."""
524 super(TosController, self).__init__(title, subtitle)
455 self.view = None525 self.view = None
456 self._title = title
457 self._subtitle = subtitle
458 self._tos_url = tos_url526 self._tos_url = tos_url
459527
460 #pylint: disable=C0103528 #pylint: disable=C0103
461 def setupUi(self, view):529 def setupUi(self, view):
462 """Set up the ui."""530 """Set up the ui."""
463 self.view = view531 self.view = view
464 self.view.setTitle(self._title)532 self.view.header.set_title(self._title)
465 self.view.setSubTitle(self._subtitle)533 self.view.header.set_subtitle(self._subtitle)
466 # load the tos page534 # load the tos page
467 self.view.ui.terms_webkit.load(QUrl(self._tos_url))535 self.view.ui.terms_webkit.load(QUrl(self._tos_url))
468 self.view.ui.tos_link_label.setText(536 self.view.ui.tos_link_label.setText(
@@ -474,6 +542,13 @@
474class EmailVerificationController(BackendController):542class EmailVerificationController(BackendController):
475 """Controller used for the verification page."""543 """Controller used for the verification page."""
476544
545 def __init__(self, message_box=None, title='', subtitle=''):
546 """Create a new instance."""
547 super(EmailVerificationController, self).__init__(title, subtitle)
548 if message_box is None:
549 message_box = QMessageBox
550 self.message_box = message_box
551
477 def _set_translated_strings(self):552 def _set_translated_strings(self):
478 """Set the trnaslated strings."""553 """Set the trnaslated strings."""
479 logger.debug('EmailVerificationController._set_translated_strings')554 logger.debug('EmailVerificationController._set_translated_strings')
@@ -492,11 +567,11 @@
492 def _set_titles(self):567 def _set_titles(self):
493 """Set the different titles."""568 """Set the different titles."""
494 logger.debug('EmailVerificationController._set_titles')569 logger.debug('EmailVerificationController._set_titles')
495 self.view.setTitle(VERIFY_EMAIL_TITLE)570 self.view.header.set_title(VERIFY_EMAIL_TITLE)
496 self.view.setSubTitle(VERIFY_EMAIL_CONTENT % {571 self.view.header.set_subtitle(VERIFY_EMAIL_CONTENT % {
497 "app_name": self.view.wizard().app_name,572 "app_name": self.view.wizard().app_name,
498 "email": self.view.wizard().field("email_address").toString(),573 "email": self.view.wizard().field("email_address").toString(),
499 })574 })
500575
501 def set_titles(self):576 def set_titles(self):
502 """This class needs to have a public set_titles.577 """This class needs to have a public set_titles.
@@ -520,9 +595,9 @@
520 def validate_email(self):595 def validate_email(self):
521 """Call the next action."""596 """Call the next action."""
522 logger.debug('EmailVerificationController.validate_email')597 logger.debug('EmailVerificationController.validate_email')
523 email = str(self.view.wizard().field('email_address').toString())598 email = unicode(self.view.wizard().field('email_address').toString())
524 password = str(self.view.wizard().field('password').toString())599 password = unicode(self.view.wizard().field('password').toString())
525 code = str(self.view.ui.verification_code_edit.text())600 code = unicode(self.view.ui.verification_code_edit.text())
526 self.backend.validate_email(self.view.wizard().app_name, email,601 self.backend.validate_email(self.view.wizard().app_name, email,
527 password, code)602 password, code)
528603
@@ -532,16 +607,30 @@
532 email = self.view.wizard().field('email_address').toString()607 email = self.view.wizard().field('email_address').toString()
533 self.view.wizard().registrationSuccess.emit(app_name, email)608 self.view.wizard().registrationSuccess.emit(app_name, email)
534609
535 def on_email_validation_error(self, app_name, raised_error):610 def on_email_validation_error(self, app_name, error):
536 """Signal thrown when there's a problem validating the email."""611 """Signal thrown when there's a problem validating the email."""
537612 msg = error.get('email_token')
538613 if msg is None:
539class ErrorController(object):614 msg = _build_general_error_message(error)
615 self.message_box.critical(msg)
616
617 #pylint: disable=C0103
618 def pageInitialized(self):
619 """Called to prepare the page just before it is shown."""
620 self.view.next_button.setDefault(True)
621 self.view.next_button.style().unpolish(
622 self.view.next_button)
623 self.view.next_button.style().polish(
624 self.view.next_button)
625 #pylint: enable=C0103
626
627
628class ErrorController(BackendController):
540 """Controller used for the error page."""629 """Controller used for the error page."""
541630
542 def __init__(self):631 def __init__(self, title='', subtitle=''):
543 """Create a new instance."""632 """Create a new instance."""
544 super(ErrorController, self).__init__()633 super(ErrorController, self).__init__(title, subtitle)
545 self.view = None634 self.view = None
546635
547 #pylint: disable=C0103636 #pylint: disable=C0103
@@ -550,15 +639,21 @@
550 self.view = view639 self.view = view
551 self.view.next = -1640 self.view.next = -1
552 self.view.ui.error_message_label.setText(ERROR)641 self.view.ui.error_message_label.setText(ERROR)
642 self.view.header.set_title(self._title)
643 self.view.header.set_subtitle(self._subtitle)
553 #pylint: enable=C0103644 #pylint: enable=C0103
554645
555646
556class ForgottenPasswordController(BackendController):647class ForgottenPasswordController(BackendController):
557 """Controller used to deal with the forgotten pwd page."""648 """Controller used to deal with the forgotten pwd page."""
558649
559 def __init__(self):650 def __init__(self, message_box=None, title='', subtitle=''):
560 """Create a new instance."""651 """Create a new instance."""
561 super(ForgottenPasswordController, self).__init__()652 super(ForgottenPasswordController, self).__init__()
653 if message_box is None:
654 message_box = QMessageBox
655 self.message_box = message_box
656 super(ForgottenPasswordController, self).__init__(title, subtitle)
562657
563 def _register_fields(self):658 def _register_fields(self):
564 """Register the fields of the wizard page."""659 """Register the fields of the wizard page."""
@@ -582,6 +677,7 @@
582677
583 def _connect_ui(self):678 def _connect_ui(self):
584 """Connect the diff signals from the Ui."""679 """Connect the diff signals from the Ui."""
680 self.view.email_address_line_edit.textChanged.connect(self._validate)
585 self.view.send_button.clicked.connect(681 self.view.send_button.clicked.connect(
586 lambda: self.backend.request_password_reset_token(682 lambda: self.backend.request_password_reset_token(
587 self.view.wizard().app_name,683 self.view.wizard().app_name,
@@ -592,6 +688,11 @@
592 self.on_password_reset_token_sent()688 self.on_password_reset_token_sent()
593 self.backend.on_password_reset_error_cb = self.on_password_reset_error689 self.backend.on_password_reset_error_cb = self.on_password_reset_error
594690
691 def _validate(self):
692 """Validate that we have an email."""
693 email = unicode(self.view.email_address_line_edit.text())
694 self.view.send_button.setEnabled(is_correct_email(email))
695
595 def on_try_again(self):696 def on_try_again(self):
596 """Set back the widget to the initial state."""697 """Set back the widget to the initial state."""
597 self.view.error_label.setVisible(False)698 self.view.error_label.setVisible(False)
@@ -632,15 +733,20 @@
632 self._connect_ui()733 self._connect_ui()
633 self._set_enhanced_line_edit()734 self._set_enhanced_line_edit()
634 self._register_fields()735 self._register_fields()
736 self.view.header.set_title(self._title)
737 self.view.header.set_subtitle(self._subtitle)
635 #pylint: enable=C0103738 #pylint: enable=C0103
636739
637740
638class ResetPasswordController(BackendController):741class ResetPasswordController(BackendController):
639 """Controller used to deal with reseintg the password."""742 """Controller used to deal with reseintg the password."""
640743
641 def __init__(self):744 def __init__(self, title='', subtitle='', message_box=None):
642 """Create a new instance."""745 """Create a new instance."""
643 super(ResetPasswordController, self).__init__()746 if message_box is None:
747 message_box = QMessageBox
748 self.message_box = message_box
749 super(ResetPasswordController, self).__init__(title, subtitle)
644750
645 def _set_translated_strings(self):751 def _set_translated_strings(self):
646 """Translate the diff strings used in the app."""752 """Translate the diff strings used in the app."""
@@ -658,6 +764,25 @@
658 self.backend.on_password_changed_cb = self.on_password_changed764 self.backend.on_password_changed_cb = self.on_password_changed
659 self.backend.on_password_change_error_cb = \765 self.backend.on_password_change_error_cb = \
660 self.on_password_change_error766 self.on_password_change_error
767 self.view.ui.reset_code_line_edit.textChanged.connect(self._validate)
768 self.view.ui.password_line_edit.textChanged.connect(self._validate)
769 self.view.ui.confirm_password_line_edit.textChanged.connect(
770 self._validate)
771
772 def _validate(self):
773 """Enable the submit button if data is valid."""
774 enabled = True
775 code = unicode(self.view.ui.reset_code_line_edit.text())
776 password = unicode(self.view.ui.password_line_edit.text())
777 confirm_password = unicode(
778 self.view.ui.confirm_password_line_edit.text())
779 if not is_min_required_password(password):
780 enabled = False
781 elif not self.is_correct_password_confirmation(confirm_password):
782 enabled = False
783 elif not code:
784 enabled = False
785 self.view.ui.reset_password_button.setEnabled(enabled)
661786
662 def _add_line_edits_validations(self):787 def _add_line_edits_validations(self):
663 """Add the validations to be use by the line edits."""788 """Add the validations to be use by the line edits."""
@@ -676,20 +801,23 @@
676801
677 def on_password_change_error(self, app_name, error):802 def on_password_change_error(self, app_name, error):
678 """Let the user know that there was an error."""803 """Let the user know that there was an error."""
804 logger.error('Got error changing password for %s, error: %s',
805 self.view.wizard().app_name, error)
806 self.message_box.critical(_build_general_error_message(error))
679807
680 def set_new_password(self):808 def set_new_password(self):
681 """Request a new password to be set."""809 """Request a new password to be set."""
682 app_name = self.view.wizard().app_name810 app_name = self.view.wizard().app_name
683 email = str(self.view.wizard().field('email_address').toString())811 email = unicode(self.view.wizard().field('email_address').toString())
684 code = str(self.view.ui.reset_code_line_edit.text())812 code = unicode(self.view.ui.reset_code_line_edit.text())
685 password = str(self.view.ui.password_line_edit.text())813 password = unicode(self.view.ui.password_line_edit.text())
686 logger.info('Settig new password for %s and email %s with code %s',814 logger.info('Settig new password for %s and email %s with code %s',
687 app_name, email, code)815 app_name, email, code)
688 self.backend.set_new_password(app_name, email, code, password)816 self.backend.set_new_password(app_name, email, code, password)
689817
690 def is_correct_password_confirmation(self, password):818 def is_correct_password_confirmation(self, password):
691 """Return if the password is correct."""819 """Return if the password is correct."""
692 return self.view.ui.password_line_edit.text() == password820 return unicode(self.view.ui.password_line_edit.text()) == password
693821
694 #pylint: disable=C0103822 #pylint: disable=C0103
695 @inlineCallbacks823 @inlineCallbacks
@@ -700,14 +828,17 @@
700 self._set_translated_strings()828 self._set_translated_strings()
701 self._connect_ui()829 self._connect_ui()
702 self._add_line_edits_validations()830 self._add_line_edits_validations()
831 self.view.header.set_title(self._title)
832 self.view.header.set_subtitle(self._subtitle)
703 #pylint: enable=C0103833 #pylint: enable=C0103
704834
705835
706class SuccessController(object):836class SuccessController(BackendController):
707 """Controller used for the success page."""837 """Controller used for the success page."""
708838
709 def __init__(self):839 def __init__(self, title='', subtitle=''):
710 """Create a new instance."""840 """Create a new instance."""
841 super(SuccessController, self).__init__(title, subtitle)
711 self.view = None842 self.view = None
712843
713 #pylint: disable=C0103844 #pylint: disable=C0103
@@ -716,6 +847,8 @@
716 self.view = view847 self.view = view
717 self.view.next = -1848 self.view.next = -1
718 self.view.ui.success_message_label.setText(SUCCESS)849 self.view.ui.success_message_label.setText(SUCCESS)
850 self.view.header.set_title(self._title)
851 self.view.header.set_subtitle(self._subtitle)
719 #pylint: enable=C0103852 #pylint: enable=C0103
720853
721854
@@ -741,7 +874,8 @@
741 def on_login_success(self, app_name, email):874 def on_login_success(self, app_name, email):
742 """Process the success of a login."""875 """Process the success of a login."""
743 logger.debug('UbuntuSSOWizardController.on_login_success')876 logger.debug('UbuntuSSOWizardController.on_login_success')
744 result = yield self.login_success_callback(str(app_name), str(email))877 result = yield self.login_success_callback(
878 unicode(app_name), unicode(email))
745 logger.debug('Result from callback is %s', result)879 logger.debug('Result from callback is %s', result)
746 if result == 0:880 if result == 0:
747 logger.info('Success in calling the given success_callback')881 logger.info('Success in calling the given success_callback')
@@ -754,8 +888,8 @@
754 def on_registration_success(self, app_name, email):888 def on_registration_success(self, app_name, email):
755 """Process the success of a registration."""889 """Process the success of a registration."""
756 logger.debug('UbuntuSSOWizardController.on_registration_success')890 logger.debug('UbuntuSSOWizardController.on_registration_success')
757 result = yield self.registration_success_callback(str(app_name),891 result = yield self.registration_success_callback(unicode(app_name),
758 str(email))892 unicode(email))
759 # TODO: what to do?893 # TODO: what to do?
760 logger.debug('Result from callback is %s', result)894 logger.debug('Result from callback is %s', result)
761 if result == 0:895 if result == 0:
762896
=== modified file 'ubuntu_sso/qt/gui.py'
--- ubuntu_sso/qt/gui.py 2011-06-24 19:14:17 +0000
+++ ubuntu_sso/qt/gui.py 2011-08-25 19:16:19 +0000
@@ -17,15 +17,18 @@
17"""Qt implementation of the UI."""17"""Qt implementation of the UI."""
1818
19from PyQt4.QtCore import pyqtSignal19from PyQt4.QtCore import pyqtSignal
20from PyQt4.QtCore import Qt
20from PyQt4.QtGui import (21from PyQt4.QtGui import (
21 QApplication,22 QApplication,
23 QWidget,
22 QCursor,24 QCursor,
23 QHBoxLayout,25 QHBoxLayout,
26 QVBoxLayout,
24 QPixmap,27 QPixmap,
25 QPushButton,
26 QStyle,28 QStyle,
27 QWizard,29 QWizard,
28 QWizardPage)30 QWizardPage,
31 QLabel)
2932
30from ubuntu_sso.logger import setup_logging33from ubuntu_sso.logger import setup_logging
31# pylint: disable=F0401,E061134# pylint: disable=F0401,E0611
@@ -55,6 +58,40 @@
55logger = setup_logging('ubuntu_sso.gui')58logger = setup_logging('ubuntu_sso.gui')
5659
5760
61class Header(QWidget):
62 """Header Class for Title and Subtitle in all wizard pages."""
63
64 def __init__(self):
65 """Create a new instance."""
66 QWidget.__init__(self)
67 vbox = QVBoxLayout(self)
68 self.title_label = QLabel()
69 self.title_label.setWordWrap(True)
70 self.title_label.setObjectName('title_label')
71 self.subtitle_label = QLabel()
72 self.subtitle_label.setWordWrap(True)
73 vbox.addWidget(self.title_label)
74 vbox.addWidget(self.subtitle_label)
75 self.title_label.setVisible(False)
76 self.subtitle_label.setVisible(False)
77
78 def set_title(self, title):
79 """Set the Title of the page or hide it otherwise"""
80 if title:
81 self.title_label.setText(title)
82 self.title_label.setVisible(True)
83 else:
84 self.title_label.setVisible(False)
85
86 def set_subtitle(self, subtitle):
87 """Set the Subtitle of the page or hide it otherwise"""
88 if subtitle:
89 self.subtitle_label.setText(subtitle)
90 self.subtitle_label.setVisible(True)
91 else:
92 self.subtitle_label.setVisible(False)
93
94
58class SSOWizardPage(QWizardPage):95class SSOWizardPage(QWizardPage):
59 """Root class for all wizard pages."""96 """Root class for all wizard pages."""
6097
@@ -63,8 +100,11 @@
63 QWizardPage.__init__(self, parent)100 QWizardPage.__init__(self, parent)
64 self.ui = ui101 self.ui = ui
65 self.ui.setupUi(self)102 self.ui.setupUi(self)
66 self.controller = controller103 self.header = Header()
67 self.controller.setupUi(self)104 self.layout().insertWidget(0, self.header)
105 if controller:
106 self.controller = controller
107 self.controller.setupUi(self)
68 self.next = -1108 self.next = -1
69109
70 # pylint: disable=C0103110 # pylint: disable=C0103
@@ -73,6 +113,25 @@
73 return self.next113 return self.next
74 # pylint: enable=C0103114 # pylint: enable=C0103
75115
116 # pylint: disable=C0103
117 def initializePage(self):
118 """Called to prepare the page just before it is shown."""
119 if self.controller:
120 self.controller.pageInitialized()
121 # pylint: enable=C0103
122
123 # pylint: disable=C0103
124 def setTitle(self, title=''):
125 """Set the Wizard Page Title."""
126 self.header.set_title(title)
127 # pylint: enable=C0103
128
129 # pylint: disable=C0103
130 def setSubTitle(self, subtitle=''):
131 """Set the Wizard Page Subtitle."""
132 self.header.set_subtitle(subtitle)
133 # pylint: enable=C0103
134
76135
77class EnhancedLineEdit(object):136class EnhancedLineEdit(object):
78 """Represents and enhanced lineedit.137 """Represents and enhanced lineedit.
@@ -81,7 +140,8 @@
81 that we are just adding extra items to it.140 that we are just adding extra items to it.
82 """141 """
83142
84 def __init__(self, line_edit, valid_cb=lambda x: False):143 def __init__(self, line_edit, valid_cb=lambda x: False,
144 warning_sign=False):
85 """Create an instance."""145 """Create an instance."""
86 self._line_edit = line_edit146 self._line_edit = line_edit
87 layout = QHBoxLayout(self._line_edit)147 layout = QHBoxLayout(self._line_edit)
@@ -89,24 +149,27 @@
89 self._line_edit.setLayout(layout)149 self._line_edit.setLayout(layout)
90 self.valid_cb = valid_cb150 self.valid_cb = valid_cb
91 layout.addStretch()151 layout.addStretch()
92 self.clear_button = QPushButton(self._line_edit)152 self.clear_label = QLabel(self._line_edit)
93 layout.addWidget(self.clear_button)153 self.clear_label.setMargin(2)
94 self.clear_button.setMinimumSize(16, 16)154 self.clear_label.setProperty("lineEditWarning", True)
95 self.clear_button.setVisible(False)155 layout.addWidget(self.clear_label)
96 self.clear_button.setFlat(True)156 self.clear_label.setMinimumSize(16, 16)
97 self.clear_button.setCursor(QCursor(0))157 self.clear_label.setVisible(False)
98 self.clear_button.setIcon(QApplication.style().standardIcon(158 self.clear_label.setCursor(QCursor(Qt.ArrowCursor))
99 QStyle.SP_MessageBoxWarning))159 if warning_sign:
160 icon = QApplication.style().standardIcon(
161 QStyle.SP_MessageBoxWarning)
162 self.clear_label.setPixmap(icon.pixmap(16, 16))
100 # connect the change of text to the cation that will check if the163 # connect the change of text to the cation that will check if the
101 # text is valid and if the icon should be shown.164 # text is valid and if the icon should be shown.
102 self._line_edit.textChanged.connect(self.show_button)165 self._line_edit.textChanged.connect(self.show_button)
103166
104 def show_button(self, string):167 def show_button(self, string):
105 """Decide if we show the button or not."""168 """Decide if we show the button or not."""
106 if not self.valid_cb(string):169 if not self.valid_cb(string) and self.clear_label.pixmap() is not None:
107 self.clear_button.setVisible(True)170 self.clear_label.setVisible(True)
108 else:171 else:
109 self.clear_button.setVisible(False)172 self.clear_label.setVisible(False)
110173
111174
112class SSOWizardEnhancedEditPage(SSOWizardPage):175class SSOWizardEnhancedEditPage(SSOWizardPage):
113176
=== added file 'ubuntu_sso/qt/tests/login_u_p.py'
--- ubuntu_sso/qt/tests/login_u_p.py 1970-01-01 00:00:00 +0000
+++ ubuntu_sso/qt/tests/login_u_p.py 2011-08-25 19:16:19 +0000
@@ -0,0 +1,54 @@
1# -*- coding: utf-8 -*-
2# Author: Manuel de la Pena <manuel@canonical.com>
3#
4# Copyright 2011 Canonical Ltd.
5#
6# This program is free software: you can redistribute it and/or modify it
7# under the terms of the GNU General Public License version 3, as published
8# by the Free Software Foundation.
9#
10# This program is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranties of
12# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
13# PURPOSE. See the GNU General Public License for more details.
14#
15# 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/>.
17"""Script that shows the qt gui."""
18
19# pylint: disable=F0401, E1101
20from twisted.internet import reactor
21from twisted.internet.defer import inlineCallbacks
22from ubuntu_sso.main.windows import UbuntuSSOClient
23
24
25def found(*args):
26 """The credentials were found."""
27 print "creds found", args
28 reactor.stop()
29
30
31def errback(*args):
32 """Unsuccessful login."""
33 print "EB:", args
34
35
36@inlineCallbacks
37def main():
38 """Perform a client request to be logged in."""
39 client = UbuntuSSOClient()
40 client = yield client.connect()
41 yield client.cred_management.register_to_signals()
42 client.cred_management.on_credentials_found_cb = found
43 client.cred_management.on_credentials_error_cb = errback
44 yield client.cred_management.login_email_password('SUPER', dict(
45 app_name='SUPER',
46 email='xyz@canonical.com',
47 password='ABC',
48 ))
49 print "called ok"
50
51
52if __name__ == '__main__':
53 main()
54 reactor.run()
055
=== modified file 'ubuntu_sso/qt/tests/show_gui.py'
--- ubuntu_sso/qt/tests/show_gui.py 2011-07-22 20:12:20 +0000
+++ ubuntu_sso/qt/tests/show_gui.py 2011-08-25 19:16:19 +0000
@@ -20,6 +20,12 @@
20from twisted.internet import reactor20from twisted.internet import reactor
21from twisted.internet.defer import inlineCallbacks21from twisted.internet.defer import inlineCallbacks
22from ubuntu_sso.main.windows import UbuntuSSOClient22from ubuntu_sso.main.windows import UbuntuSSOClient
23from ubuntu_sso.credentials import (
24 TC_URL_KEY,
25 HELP_TEXT_KEY,
26 WINDOW_ID_KEY,
27 UI_MODULE_KEY,
28)
2329
2430
25def found(*args):31def found(*args):
@@ -33,11 +39,16 @@
33 """Perform a client request to be logged in."""39 """Perform a client request to be logged in."""
34 client = UbuntuSSOClient()40 client = UbuntuSSOClient()
35 client = yield client.connect()41 client = yield client.connect()
36 client.sso_cred.on_credentials_found_cb = found42 client.cred_management.on_credentials_found_cb = found
37 yield client.sso_cred.register_to_signals()43 yield client.cred_management.register_to_signals()
38 yield client.sso_cred.login_or_register_to_get_credentials('Ubuntu One',44 yield client.cred_management.login(
39 'http://www.google.com',45 'Ubuntu One',
40 'This is a test.', 0)46 {
47 TC_URL_KEY: 'http://www.google.com',
48 HELP_TEXT_KEY: 'This is a test.',
49 WINDOW_ID_KEY: 0,
50 UI_MODULE_KEY: 'ubuntu_sso.qt.gui',
51 })
41 print "called ok"52 print "called ok"
4253
4354
4455
=== added file 'ubuntu_sso/qt/tests/test_enchanced_line_edit.py'
--- ubuntu_sso/qt/tests/test_enchanced_line_edit.py 1970-01-01 00:00:00 +0000
+++ ubuntu_sso/qt/tests/test_enchanced_line_edit.py 2011-08-25 19:16:19 +0000
@@ -0,0 +1,94 @@
1# -*- coding: utf-8 -*-
2# Author: Diego Sarmentero <diego.sarmentero@canonical.com>
3#
4# Copyright 2011 Canonical Ltd.
5#
6# This program is free software: you can redistribute it and/or modify it
7# under the terms of the GNU General Public License version 3, as published
8# by the Free Software Foundation.
9#
10# This program is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranties of
12# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
13# PURPOSE. See the GNU General Public License for more details.
14#
15# 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/>.
17"""Test the EnhancedLineEdit."""
18
19import collections
20
21from twisted.trial.unittest import TestCase
22
23from PyQt4 import QtGui
24from PyQt4 import QtCore
25
26from ubuntu_sso.qt.gui import EnhancedLineEdit
27
28
29class EnhancedLineEditTestCase(TestCase):
30 """Tests EnhancedLineEdit for valid and invalid states."""
31
32 def get_pixmap_data(self, pixmap):
33 """Get the raw data of a QPixmap."""
34 byte_array = QtCore.QByteArray()
35 array_buffer = QtCore.QBuffer(byte_array)
36 pixmap.save(array_buffer, "PNG")
37 return byte_array
38
39 # pylint: disable=C0103
40 def assertEqualPixmaps(self, pixmap1, pixmap2):
41 """Compare two Qt pixmaps."""
42 d1 = self.get_pixmap_data(pixmap1)
43 d2 = self.get_pixmap_data(pixmap2)
44 self.assertEqual(d1, d2)
45 # pylint: enable=C0103
46
47 def test_enhanced_line_edit_init(self):
48 """Tests the initial state of the enchanced line edit."""
49 line_edit = QtGui.QLineEdit()
50 line_edit.show()
51 enhanced = EnhancedLineEdit(line_edit)
52 self.assertIsInstance(enhanced.valid_cb, collections.Callable)
53 self.assertTrue(enhanced.clear_label.property("lineEditWarning"))
54 self.assertFalse(enhanced.clear_label.isVisible())
55 self.assertEqual(enhanced.clear_label.cursor().shape(),
56 QtCore.Qt.ArrowCursor)
57 self.assertFalse(enhanced.clear_label.isVisible())
58 self.assertEqual(enhanced.clear_label.pixmap(), None)
59
60 def test_enhanced_line_edit_init_with_icon(self):
61 """Tests the initial state of the enchanced line edit."""
62 line_edit = QtGui.QLineEdit()
63 line_edit.show()
64 enhanced = EnhancedLineEdit(line_edit, warning_sign=True)
65 self.assertIsInstance(enhanced.valid_cb, collections.Callable)
66 edit_property = enhanced.clear_label.property("lineEditWarning")
67 self.assertTrue(edit_property)
68 self.assertFalse(enhanced.clear_label.isVisible())
69 self.assertEqual(enhanced.clear_label.cursor().shape(),
70 QtCore.Qt.ArrowCursor)
71 self.assertFalse(enhanced.clear_label.isVisible())
72 icon = QtGui.QApplication.style().standardIcon(
73 QtGui.QStyle.SP_MessageBoxWarning)
74 self.assertEqualPixmaps(enhanced.clear_label.pixmap(),
75 icon.pixmap(16, 16))
76
77 def test_enhanced_line_edit_function_show_true(self):
78 """Tests the EnchancedLineEdit show method with validation = True."""
79 line_edit = QtGui.QLineEdit()
80 line_edit.show()
81 func = lambda x: False
82 enhanced = EnhancedLineEdit(line_edit, valid_cb=func,
83 warning_sign=True)
84 line_edit.setText("testing")
85 self.assertTrue(enhanced.clear_label.isVisible())
86
87 def test_enhanced_line_edit_function_show_false(self):
88 """Tests the EnchancedLineEdit show method with validation = False."""
89 line_edit = QtGui.QLineEdit()
90 line_edit.show()
91 func = lambda x: True
92 enhanced = EnhancedLineEdit(line_edit, valid_cb=func)
93 line_edit.setText("testing")
94 self.assertFalse(enhanced.clear_label.isVisible())
095
=== modified file 'ubuntu_sso/qt/tests/test_qt_views.py'
--- ubuntu_sso/qt/tests/test_qt_views.py 2011-06-24 19:14:17 +0000
+++ ubuntu_sso/qt/tests/test_qt_views.py 2011-08-25 19:16:19 +0000
@@ -24,6 +24,7 @@
24 CurrentUserSignInPage,24 CurrentUserSignInPage,
25 EmailVerificationPage,25 EmailVerificationPage,
26 ErrorPage,26 ErrorPage,
27 Header,
27 SetupAccountPage,28 SetupAccountPage,
28 SuccessPage,29 SuccessPage,
29 TosPage)30 TosPage)
@@ -72,7 +73,7 @@
72 ui.setupUi(MATCH(lambda x: isinstance(x, ChooseSignInPage)))73 ui.setupUi(MATCH(lambda x: isinstance(x, ChooseSignInPage)))
73 controller.setupUi(MATCH(lambda x: isinstance(x, ChooseSignInPage)))74 controller.setupUi(MATCH(lambda x: isinstance(x, ChooseSignInPage)))
74 mocker.replay()75 mocker.replay()
75 self.widget = ChooseSignInPage(ui, controller)76 self.widget = ChooseSignInPage(self.ui, self.controller)
7677
77 def test_next_id(self):78 def test_next_id(self):
78 """Test the nextId method."""79 """Test the nextId method."""
@@ -217,3 +218,45 @@
217 controller.setupUi(MATCH(lambda x: isinstance(x, ErrorPage)))218 controller.setupUi(MATCH(lambda x: isinstance(x, ErrorPage)))
218 mocker.replay()219 mocker.replay()
219 self.widget = ErrorPage(self.ui, self.controller)220 self.widget = ErrorPage(self.ui, self.controller)
221
222
223class HeaderTest(TestCase):
224 """Tests for injected Header in each Wizard Page."""
225
226 def setUp(self):
227 """Setup test."""
228 super(HeaderTest, self).setUp()
229 self.header = Header()
230
231 def test_label_state(self):
232 """Check the title and subtitle properties."""
233 self.assertTrue(self.header.title_label.wordWrap())
234 self.assertTrue(self.header.subtitle_label.wordWrap())
235 self.assertFalse(self.header.title_label.isVisible())
236 self.assertFalse(self.header.subtitle_label.isVisible())
237
238 def test_set_title(self):
239 """Check if set_title works ok, showing the widget if necessary."""
240 self.header.set_title('title')
241 self.assertEqual(self.header.title_label.text(), 'title')
242 self.header.show()
243 self.assertTrue(self.header.title_label.isVisible())
244 self.header.hide()
245
246 def test_set_empty_title(self):
247 """Check if the widget is hidden for empty title."""
248 self.header.set_title('')
249 self.assertFalse(self.header.title_label.isVisible())
250
251 def test_set_subtitle(self):
252 """Check if set_subtitle works ok, showing the widget if necessary."""
253 self.header.set_subtitle('subtitle')
254 self.assertEqual(self.header.subtitle_label.text(), 'subtitle')
255 self.header.show()
256 self.assertTrue(self.header.subtitle_label.isVisible())
257 self.header.hide()
258
259 def test_set_empty_subtitle(self):
260 """Check if the widget is hidden for empty subtitle."""
261 self.header.set_title('')
262 self.assertFalse(self.header.title_label.isVisible())
220263
=== modified file 'ubuntu_sso/qt/tests/test_windows.py'
--- ubuntu_sso/qt/tests/test_windows.py 2011-07-22 20:12:20 +0000
+++ ubuntu_sso/qt/tests/test_windows.py 2011-08-25 19:16:19 +0000
@@ -35,6 +35,7 @@
35 UbuntuSSOWizardController)35 UbuntuSSOWizardController)
36from ubuntu_sso.utils.ui import (36from ubuntu_sso.utils.ui import (
37 CAPTCHA_SOLUTION_ENTRY,37 CAPTCHA_SOLUTION_ENTRY,
38 CAPTCHA_REQUIRED_ERROR,
38 EMAIL1_ENTRY,39 EMAIL1_ENTRY,
39 EMAIL2_ENTRY,40 EMAIL2_ENTRY,
40 EMAIL_MISMATCH,41 EMAIL_MISMATCH,
@@ -69,7 +70,7 @@
69 is_correct_email)70 is_correct_email)
7071
71#ignore the comon mocker issues with lint72#ignore the comon mocker issues with lint
72# pylint: disable=W0212,W0104,W010673# pylint: disable=W0212,W0104,W0106,E1103
7374
7475
75class ChooseSignInControllerTestCase(MockerTestCase):76class ChooseSignInControllerTestCase(MockerTestCase):
@@ -83,6 +84,19 @@
83 self.controller = ChooseSignInController()84 self.controller = ChooseSignInController()
84 self.controller.view = self.view85 self.controller.view = self.view
85 self.controller.backend = self.backend86 self.controller.backend = self.backend
87 self.title = 'title'
88 self.subtitle = 'sub'
89
90 def test_setup_ui(self):
91 """Test the set up of the ui."""
92 self.controller._title = self.title
93 self.controller._subtitle = self.subtitle
94 self.controller._set_up_translated_strings()
95 self.view.header.set_title(self.title)
96 self.view.header.set_subtitle(self.subtitle)
97 self.controller._connect_buttons()
98 self.mocker.replay()
99 self.controller.setupUi(self.view)
86100
87 def test_set_up_translated_strings(self):101 def test_set_up_translated_strings(self):
88 """Ensure that the translations are used."""102 """Ensure that the translations are used."""
@@ -156,6 +170,8 @@
156 self.backend.on_logged_in_cb = MATCH(callable)170 self.backend.on_logged_in_cb = MATCH(callable)
157 self.view.ui.forgot_password_label.linkActivated.connect(171 self.view.ui.forgot_password_label.linkActivated.connect(
158 MATCH(callable))172 MATCH(callable))
173 self.view.ui.email_edit.textChanged.connect(MATCH(callable))
174 self.view.ui.password_edit.textChanged.connect(MATCH(callable))
159 self.mocker.replay()175 self.mocker.replay()
160 self.controller._connect_ui()176 self.controller._connect_ui()
161177
@@ -166,6 +182,227 @@
166 self.assertEqual(self.controller._subtitle, 'the subtitle')182 self.assertEqual(self.controller._subtitle, 'the subtitle')
167183
168184
185class FakeLineEdit(object):
186
187 """A fake QLineEdit."""
188
189 def __init__(self, *args):
190 """Initialize."""
191 self._text = u""
192
193 def text(self):
194 """Save text."""
195 return self._text
196
197 # setText is inherited
198 # pylint: disable=C0103
199 def setText(self, text):
200 """Return saved text."""
201 self._text = text
202
203
204class FakePage(object):
205
206 """A fake wizard page."""
207
208 app_name = "APP"
209
210 def wizard(self):
211 """Fake wizard."""
212 return self
213
214
215class FakeCurrentUserPage(FakePage):
216
217 """Fake CurrentuserPage."""
218
219 def __init__(self, *args):
220 """Initialize."""
221 super(FakeCurrentUserPage, self).__init__(*args)
222 self.ui = self
223 self.ui.email_edit = FakeLineEdit()
224 self.ui.password_edit = FakeLineEdit()
225 self.sign_in_button = self
226 self._enabled = False
227
228 # setEnabled is inherited
229 # pylint: disable=C0103
230 def setEnabled(self, value):
231 """Fake setEnabled."""
232 self._enabled = value
233
234 def enabled(self):
235 """Fake enabled."""
236 return self._enabled
237
238
239class CurrentUserControllerValidationMockTest(MockerTestCase):
240 """Test the choose sign in controller."""
241
242 def setUp(self):
243 """Set tests."""
244 super(CurrentUserControllerValidationMockTest, self).setUp()
245 self.view = self.mocker.mock()
246 self.backend = self.mocker.mock()
247 self.controller = CurrentUserController()
248 self.controller.view = self.view
249 self.controller.backend = self.backend
250 self.title = 'title'
251 self.subtitle = 'sub'
252
253 def test_setup_ui(self):
254 """Test the set up of the ui."""
255 self.controller._title = self.title
256 self.controller._subtitle = self.subtitle
257 self.backend = yield self.controller.get_backend()
258 self.view.header.set_title(self.title)
259 self.view.header.set_subtitle(self.subtitle)
260 self.controller._set_translated_strings()
261 self.mocker.replay()
262 self.controller.setupUi(self.view)
263
264
265class CurrentUserControllerErrorTestCase(TestCase):
266
267 """Tests for CurrentUserController's error handler."""
268
269 on_error_method_name = "on_login_error"
270 controller_class = CurrentUserController
271
272 def setUp(self):
273 """Setup test."""
274 super(CurrentUserControllerErrorTestCase, self).setUp()
275 self.message_box = FakeMessageBox()
276 self.controller = self.controller_class(
277 message_box=self.message_box)
278 self.controller.view = FakePage()
279 self._called = False
280 self.on_error_method = getattr(
281 self.controller, self.on_error_method_name)
282
283 def test_error_message_key(self):
284 """Test that on_login_error reacts to errors with "error_message"."""
285 self.on_error_method({"error_message": "WORRY!"})
286 self.assertEqual(self.message_box.critical_args, (('WORRY!',), {}))
287
288 def test_message_key(self):
289 """Test that on_login_error reacts to errors with "message"."""
290 self.on_error_method({"message": "WORRY!"})
291 self.assertEqual(self.message_box.critical_args, (('WORRY!',), {}))
292
293 def test_broken_error(self):
294 """Test that on_login_error reacts to broken errors."""
295 self.on_error_method({"boo!": "WORRY!"})
296 self.assertEqual(self.message_box.critical_args,
297 (("Error: {'boo!': 'WORRY!'}",), {}))
298
299 def test_all_and_message(self):
300 """Test that on_login_error reacts to broken errors."""
301 self.on_error_method(
302 {"message": "WORRY!", "__all__": "MORE!"})
303 self.assertEqual(self.message_box.critical_args,
304 (('MORE!\nWORRY!',), {}))
305
306 def test_all_and_error_message(self):
307 """Test that on_login_error reacts to broken errors."""
308 self.on_error_method(
309 {"error_message": "WORRY!", "__all__": "MORE!"})
310 self.assertEqual(self.message_box.critical_args,
311 (('MORE!\nWORRY!',), {}))
312
313 def test_only_all(self):
314 """Test that on_login_error reacts to broken errors."""
315 self.on_error_method(
316 {"__all__": "MORE!"})
317 self.assertEqual(self.message_box.critical_args,
318 (('MORE!',), {}))
319
320
321class EmailVerificationControllerErrorTestCase(
322 CurrentUserControllerErrorTestCase):
323
324 """Tests for EmailVerificationController's error handler."""
325
326 on_error_method_name = "on_email_validation_error"
327 controller_class = EmailVerificationController
328
329 def setUp(self):
330 """Setup test."""
331 super(EmailVerificationControllerErrorTestCase, self).setUp()
332 # This error handler takes one extra argument.
333 self.on_error_method = lambda error: getattr(
334 self.controller, self.on_error_method_name)('APP', error)
335
336
337class SetUpAccountControllerErrorTestCase(
338 EmailVerificationControllerErrorTestCase):
339
340 """Tests for SetUpAccountController's error handler."""
341
342 on_error_method_name = "on_user_registration_error"
343 controller_class = SetUpAccountController
344
345 def setUp(self):
346 """Setup test."""
347 super(SetUpAccountControllerErrorTestCase, self).setUp()
348 self.patch(self.controller, "_refresh_captcha", lambda *args: None)
349
350
351class ResetPasswordControllerErrorTestCase(
352 EmailVerificationControllerErrorTestCase):
353
354 """Tests for ResetPasswordController's error handler."""
355
356 on_error_method_name = "on_password_change_error"
357 controller_class = ResetPasswordController
358
359
360class CurrentUserControllerValidationTest(TestCase):
361
362 """Tests for CurrentUserController, but without Mocker."""
363
364 def setUp(self):
365 """Setup test."""
366 super(CurrentUserControllerValidationTest, self).setUp()
367 self.message_box = FakeMessageBox()
368 self.controller = CurrentUserController(
369 message_box=self.message_box)
370 self.controller.view = FakeCurrentUserPage()
371 self._called = False
372
373 def _set_called(self, *args, **kwargs):
374 """Store 'args' and 'kwargs' for test assertions."""
375 self._called = (args, kwargs)
376
377 def test_valid(self):
378 """Enable the button with a valid email/password."""
379 self.controller.view.email_edit.setText("a@b")
380 self.controller.view.password_edit.setText("pass")
381 self.controller._validate()
382 self.assertTrue(self.controller.view.sign_in_button.enabled())
383
384 def test_invalid_email(self):
385 """The submit button should be disabled with an invalid email."""
386 self.controller.view.email_edit.setText("ab")
387 self.controller.view.password_edit.setText("pass")
388 self.controller._validate()
389 self.assertFalse(self.controller.view.sign_in_button.enabled())
390
391 def test_invalid_password(self):
392 """The submit button should be disabled with an invalid password."""
393 self.controller.view.email_edit.setText("a@b")
394 self.controller.view.password_edit.setText("")
395 self.controller._validate()
396 self.assertFalse(self.controller.view.sign_in_button.enabled())
397
398 def test_invalid_both(self):
399 """The submit button should be disabled with invalid data."""
400 self.controller.view.email_edit.setText("ab")
401 self.controller.view.password_edit.setText("")
402 self.controller._validate()
403 self.assertFalse(self.controller.view.sign_in_button.enabled())
404
405
169class SetUpAccountControllerTestCase(MockerTestCase):406class SetUpAccountControllerTestCase(MockerTestCase):
170 """test the controller used to setup a new account."""407 """test the controller used to setup a new account."""
171408
@@ -204,14 +441,13 @@
204441
205 def test_set_titles(self):442 def test_set_titles(self):
206 """Test how the different titles are set."""443 """Test how the different titles are set."""
207 self.view.wizard()444 self.view.wizard().app_name
208 self.mocker.result(self.view)
209 self.view.app_name
210 self.mocker.result(self.app_name)445 self.mocker.result(self.app_name)
211 self.view.help_text446 self.view.wizard().help_text
212 self.mocker.result(self.help)447 self.mocker.result(self.help)
213 self.view.setTitle(JOIN_HEADER_LABEL % {'app_name': self.app_name})448 self.view.header.set_title(
214 self.view.setSubTitle(self.help)449 JOIN_HEADER_LABEL % {'app_name': self.app_name})
450 self.view.header.set_subtitle(self.help)
215 self.mocker.replay()451 self.mocker.replay()
216 self.controller._set_titles()452 self.controller._set_titles()
217453
@@ -220,11 +456,17 @@
220 self.view.ui.terms_button.clicked.connect(self.controller.set_next_tos)456 self.view.ui.terms_button.clicked.connect(self.controller.set_next_tos)
221 self.view.ui.set_up_button.clicked.connect(457 self.view.ui.set_up_button.clicked.connect(
222 self.controller.set_next_validation)458 self.controller.set_next_validation)
223 self.view.ui.set_up_button.setEnabled459 self.controller._enable_setup_button
224 self.mocker.result(lambda: None)460 self.mocker.result(lambda: None)
461 self.view.ui.name_edit.textEdited.connect(MATCH(callable))
462 self.view.ui.email_edit.textEdited.connect(MATCH(callable))
463 self.view.ui.confirm_email_edit.textEdited.connect(MATCH(callable))
464 self.view.ui.password_edit.textEdited.connect(MATCH(callable))
465 self.view.ui.confirm_password_edit.textEdited.connect(MATCH(callable))
466 self.view.ui.captcha_solution_edit.textEdited.connect(MATCH(callable))
225 self.view.ui.terms_checkbox.stateChanged.connect(MATCH(callable))467 self.view.ui.terms_checkbox.stateChanged.connect(MATCH(callable))
226 self.view.ui.refresh_label.linkActivated.connect(MATCH(callable))468 self.view.ui.refresh_label.linkActivated.connect(MATCH(callable))
227 # set the callbacks for the captcha generatio469 # set the callbacks for the captcha generation
228 self.backend.on_captcha_generated_cb = MATCH(callable)470 self.backend.on_captcha_generated_cb = MATCH(callable)
229 self.backend.on_captcha_generation_error_cb = MATCH(callable)471 self.backend.on_captcha_generation_error_cb = MATCH(callable)
230 self.backend.on_user_registration_error_cb = MATCH(callable)472 self.backend.on_user_registration_error_cb = MATCH(callable)
@@ -309,6 +551,7 @@
309 captcha_id, captcha_solution)551 captcha_id, captcha_solution)
310 self.view.wizard().email_verification_page_id552 self.view.wizard().email_verification_page_id
311 self.view.wizard().page(None).controller.set_titles()553 self.view.wizard().page(None).controller.set_titles()
554 self.view.ui.captcha_solution_edit.text()
312 self.mocker.replay()555 self.mocker.replay()
313 self.controller.set_next_validation()556 self.controller.set_next_validation()
314557
@@ -316,8 +559,7 @@
316 """Test the callback when there is a wrong email."""559 """Test the callback when there is a wrong email."""
317 email = 'email@example.com'560 email = 'email@example.com'
318 password = 'Qwerty9923'561 password = 'Qwerty9923'
319 self.view.wizard().app_name562 captcha_solution = 'captcha'
320 self.mocker.result(self.app_name)
321 self.view.ui.email_edit.text()563 self.view.ui.email_edit.text()
322 self.mocker.result(email)564 self.mocker.result(email)
323 self.view.ui.confirm_email_edit.text()565 self.view.ui.confirm_email_edit.text()
@@ -326,7 +568,9 @@
326 self.mocker.result(password)568 self.mocker.result(password)
327 self.view.ui.confirm_password_edit.text()569 self.view.ui.confirm_password_edit.text()
328 self.mocker.result(password)570 self.mocker.result(password)
329 self.message_box.critical(self.view, self.app_name, EMAIL_MISMATCH)571 self.view.ui.captcha_solution_edit.text()
572 self.mocker.result(captcha_solution)
573 self.message_box.critical(EMAIL_MISMATCH)
330 self.mocker.replay()574 self.mocker.replay()
331 self.assertFalse(self.controller.validate_form())575 self.assertFalse(self.controller.validate_form())
332576
@@ -334,17 +578,18 @@
334 """Test the callback with a weak password."""578 """Test the callback with a weak password."""
335 weak_password = 'weak'579 weak_password = 'weak'
336 email = 'email@example.com'580 email = 'email@example.com'
337 self.view.wizard().app_name581 captcha_solution = 'captcha'
338 self.mocker.result(self.app_name)
339 self.view.ui.email_edit.text()582 self.view.ui.email_edit.text()
340 self.mocker.result(email)583 self.mocker.result(email)
341 self.view.ui.confirm_email_edit.text()584 self.view.ui.confirm_email_edit.text()
342 self.mocker.result(email)585 self.mocker.result(email)
343 self.view.ui.password_edit.text()586 self.view.ui.password_edit.text()
344 self.mocker.result(weak_password)587 self.mocker.result(weak_password)
588 self.view.ui.captcha_solution_edit.text()
589 self.mocker.result(captcha_solution)
345 self.view.ui.confirm_password_edit.text()590 self.view.ui.confirm_password_edit.text()
346 self.mocker.result(weak_password)591 self.mocker.result(weak_password)
347 self.message_box.critical(self.view, self.app_name, PASSWORD_TOO_WEAK)592 self.message_box.critical(PASSWORD_TOO_WEAK)
348 self.mocker.replay()593 self.mocker.replay()
349 self.assertFalse(self.controller.validate_form())594 self.assertFalse(self.controller.validate_form())
350595
@@ -352,8 +597,7 @@
352 """Test the callback where the password is wrong."""597 """Test the callback where the password is wrong."""
353 password = 'Qwerty9923'598 password = 'Qwerty9923'
354 email = 'email@example.com'599 email = 'email@example.com'
355 self.view.wizard().app_name600 captcha_solution = 'captcha'
356 self.mocker.result(self.app_name)
357 self.view.ui.email_edit.text()601 self.view.ui.email_edit.text()
358 self.mocker.result(email)602 self.mocker.result(email)
359 self.view.ui.confirm_email_edit.text()603 self.view.ui.confirm_email_edit.text()
@@ -362,7 +606,28 @@
362 self.mocker.result(password)606 self.mocker.result(password)
363 self.view.ui.confirm_password_edit.text()607 self.view.ui.confirm_password_edit.text()
364 self.mocker.result('other_password')608 self.mocker.result('other_password')
365 self.message_box.critical(self.view, self.app_name, PASSWORD_MISMATCH)609 self.view.ui.captcha_solution_edit.text()
610 self.mocker.result(captcha_solution)
611 self.message_box.critical(PASSWORD_MISMATCH)
612 self.mocker.replay()
613 self.assertFalse(self.controller.validate_form())
614
615 def test_set_next_validation_empty_captcha(self):
616 """Test the callback where the password is wrong."""
617 password = 'Qwerty9923'
618 email = 'email@example.com'
619 captcha_solution = ''
620 self.view.ui.email_edit.text()
621 self.mocker.result(email)
622 self.view.ui.confirm_email_edit.text()
623 self.mocker.result(email)
624 self.view.ui.password_edit.text()
625 self.mocker.result(password)
626 self.view.ui.confirm_password_edit.text()
627 self.mocker.result(password)
628 self.view.ui.captcha_solution_edit.text()
629 self.mocker.result(captcha_solution)
630 self.message_box.critical(CAPTCHA_REQUIRED_ERROR)
366 self.mocker.replay()631 self.mocker.replay()
367 self.assertFalse(self.controller.validate_form())632 self.assertFalse(self.controller.validate_form())
368633
@@ -408,7 +673,7 @@
408673
409class FakeView(object):674class FakeView(object):
410675
411 """A fake view"""676 """A fake view."""
412677
413 app_name = "TestApp"678 app_name = "TestApp"
414679
@@ -417,12 +682,13 @@
417 return self682 return self
418683
419684
420class SetupAccountControllerTest2(TestCase):685class SetupAccountControllerValidationTest(TestCase):
421686
422 """Tests for SetupAccountController, but without Mocker"""687 """Tests for SetupAccountController, but without Mocker."""
423688
424 def setUp(self):689 def setUp(self):
425 super(SetupAccountControllerTest2, self).setUp()690 """Set the different tests."""
691 super(SetupAccountControllerValidationTest, self).setUp()
426 self.message_box = FakeMessageBox()692 self.message_box = FakeMessageBox()
427 self.controller = SetUpAccountController(message_box=self.message_box)693 self.controller = SetUpAccountController(message_box=self.message_box)
428 self.patch(self.controller, '_refresh_captcha', self._set_called)694 self.patch(self.controller, '_refresh_captcha', self._set_called)
@@ -443,9 +709,7 @@
443 self.controller.on_user_registration_error('TestApp',709 self.controller.on_user_registration_error('TestApp',
444 {'__all__': "Error in All"})710 {'__all__': "Error in All"})
445 self.assertEqual(self.message_box.critical_args, ((711 self.assertEqual(self.message_box.critical_args, ((
446 self.controller.view,712 "Error in All", ), {}))
447 self.controller.view.app_name,
448 "Error in All"), {}))
449713
450 def test_on_user_registration_all_fields(self):714 def test_on_user_registration_all_fields(self):
451 """Pass all known error keys, plus unknown one."""715 """Pass all known error keys, plus unknown one."""
@@ -456,11 +720,7 @@
456 'unknownfield': "Error in unknown",720 'unknownfield': "Error in unknown",
457 })721 })
458 self.assertEqual(self.message_box.critical_args, ((722 self.assertEqual(self.message_box.critical_args, ((
459 self.controller.view,723 "Error in All", ), {}))
460 self.controller.view.app_name,
461 "Error in All\nError in email\n"
462 "Error in password\nError in unknown",
463 ), {}))
464724
465725
466class TosControllerTestCase(MockerTestCase):726class TosControllerTestCase(MockerTestCase):
@@ -481,8 +741,8 @@
481741
482 def test_setup_ui(self):742 def test_setup_ui(self):
483 """Test the set up of the ui."""743 """Test the set up of the ui."""
484 self.view.setTitle(self.title)744 self.view.header.set_title(self.title)
485 self.view.setSubTitle(self.subtitle)745 self.view.header.set_subtitle(self.subtitle)
486 self.view.ui.terms_webkit.load(ANY)746 self.view.ui.terms_webkit.load(ANY)
487 self.view.ui.tos_link_label.setText(747 self.view.ui.tos_link_label.setText(
488 TOS_LABEL %748 TOS_LABEL %
@@ -499,7 +759,8 @@
499 super(EmailVerificationControllerTestCase, self).setUp()759 super(EmailVerificationControllerTestCase, self).setUp()
500 self.view = self.mocker.mock()760 self.view = self.mocker.mock()
501 self.backend = self.mocker.mock()761 self.backend = self.mocker.mock()
502 self.controller = EmailVerificationController()762 self.controller = EmailVerificationController(
763 message_box=self.mocker.mock())
503 self.controller.view = self.view764 self.controller.view = self.view
504 self.controller.backend = self.backend765 self.controller.backend = self.backend
505766
@@ -521,15 +782,17 @@
521782
522 def test_set_titles(self):783 def test_set_titles(self):
523 """Test that the titles are set."""784 """Test that the titles are set."""
524 self.view.setTitle(VERIFY_EMAIL_TITLE)785 self.view.header.set_title(VERIFY_EMAIL_CONTENT)
525 self.view.wizard().app_name786 self.view.header.set_subtitle(VERIFY_EMAIL_CONTENT % {
526 self.view.wizard().field("email_address").toString()
527 self.view.setSubTitle(VERIFY_EMAIL_CONTENT % {
528 "app_name": None,787 "app_name": None,
529 "email": None,788 "email": None,
530 })789 })
531 self.mocker.replay()790 self.mocker.replay()
532 self.controller._set_titles()791 self.view.header.set_title(VERIFY_EMAIL_CONTENT)
792 self.view.header.set_subtitle(VERIFY_EMAIL_CONTENT % {
793 "app_name": None,
794 "email": None,
795 })
533796
534 def test_validate_email(self):797 def test_validate_email(self):
535 """Test the callback."""798 """Test the callback."""
@@ -550,6 +813,27 @@
550 self.controller.validate_email()813 self.controller.validate_email()
551814
552815
816class EmailVerificationControllerValidationTestCase(TestCase):
817 """Tests for EmailVerificationController, but without Mocker."""
818
819 def setUp(self):
820 """Set the different tests."""
821 super(EmailVerificationControllerValidationTestCase, self).setUp()
822 self.message_box = FakeMessageBox()
823 self.controller = EmailVerificationController(
824 message_box=self.message_box)
825 self.patch(self.controller, 'view', FakeView())
826 self._called = False
827
828 def test_on_email_validation_error(self):
829 """Test that on_email_validation_error callback works as expected."""
830 error = dict(errtype='BadTokenError')
831 app_name = 'app_name'
832 self.controller.on_email_validation_error(app_name, error)
833 self.assertEqual(self.message_box.critical_args,
834 (("Error: {'errtype': 'BadTokenError'}",), {}))
835
836
553class ErrorControllerTestCase(MockerTestCase):837class ErrorControllerTestCase(MockerTestCase):
554 """Test the success page controller."""838 """Test the success page controller."""
555839
@@ -564,10 +848,21 @@
564848
565 def test_set_ui(self):849 def test_set_ui(self):
566 """Test the process that sets the ui."""850 """Test the process that sets the ui."""
567 self.view.next = -1851 self.controller._title = ERROR
568 self.view.ui.error_message_label852 self.controller._subtitle = ERROR
569 self.mocker.result(self.view)853 self.view.next = -1
570 self.view.setText(ERROR)854 self.view.ui.error_message_label.setText(ERROR)
855 self.view.header.set_title(ERROR)
856 self.view.header.set_subtitle(ERROR)
857 self.mocker.replay()
858 self.controller.setupUi(self.view)
859
860 def test_hide_titles(self):
861 """Test how the different titles are set."""
862 self.view.next = -1
863 self.view.ui.error_message_label.setText(ERROR)
864 self.view.header.set_title('')
865 self.view.header.set_subtitle('')
571 self.mocker.replay()866 self.mocker.replay()
572 self.controller.setupUi(self.view)867 self.controller.setupUi(self.view)
573868
@@ -586,10 +881,21 @@
586881
587 def test_set_ui(self):882 def test_set_ui(self):
588 """Test the process that sets the ui."""883 """Test the process that sets the ui."""
589 self.view.next = -1884 self.controller._title = SUCCESS
590 self.view.ui.success_message_label885 self.controller._subtitle = SUCCESS
591 self.mocker.result(self.view)886 self.view.next = -1
592 self.view.setText(SUCCESS)887 self.view.ui.success_message_label.setText(SUCCESS)
888 self.view.header.set_title(SUCCESS)
889 self.view.header.set_subtitle(SUCCESS)
890 self.mocker.replay()
891 self.controller.setupUi(self.view)
892
893 def test_hide_titles(self):
894 """Test how the different titles are set."""
895 self.view.next = -1
896 self.view.ui.success_message_label.setText(SUCCESS)
897 self.view.header.set_title('')
898 self.view.header.set_subtitle('')
593 self.mocker.replay()899 self.mocker.replay()
594 self.controller.setupUi(self.view)900 self.controller.setupUi(self.view)
595901
@@ -683,6 +989,75 @@
683 self.controller.setupUi(self.view)989 self.controller.setupUi(self.view)
684990
685991
992class FakeForgottenPasswordPage(FakeView):
993 """Fake the page."""
994
995 def __init__(self):
996 self.email_address_line_edit = self
997 self.send_button = self
998 self._text = ""
999 self._enabled = False
1000 super(FakeForgottenPasswordPage, self).__init__()
1001
1002 def text(self):
1003 """Return text."""
1004 return self._text
1005
1006 # setText, setEnabled are inherited
1007 # pylint: disable=C0103
1008 def setText(self, text):
1009 """Save text."""
1010 self._text = text
1011
1012 def setEnabled(self, value):
1013 """Fake setEnabled."""
1014 self._enabled = value
1015
1016 def enabled(self):
1017 """Fake enabled."""
1018 return self._enabled
1019
1020
1021class ForgottenPasswordControllerValidationTest(TestCase):
1022
1023 """Tests for ForgottenPasswordController, but without Mocker."""
1024
1025 def setUp(self):
1026 """Set the different tests."""
1027 super(ForgottenPasswordControllerValidationTest, self).setUp()
1028 self.message_box = FakeMessageBox()
1029 self.controller = ForgottenPasswordController(
1030 message_box=self.message_box)
1031 self.controller.view = FakeForgottenPasswordPage()
1032 self._called = False
1033
1034 def _set_called(self, *args, **kwargs):
1035 """Store 'args' and 'kwargs' for test assertions."""
1036 self._called = (args, kwargs)
1037
1038 def test_valid(self):
1039 """The submit button should be enabled with a valid email."""
1040 self.controller.view.email_address_line_edit.setText("a@b")
1041 self.assertNotEqual(unicode(
1042 self.controller.view.email_address_line_edit.text()), u"")
1043 self.controller._validate()
1044 self.assertTrue(self.controller.view.send_button.enabled())
1045
1046 def test_invalid(self):
1047 """The submit button should be disabled with an invalid email."""
1048 self.controller.view.email_address_line_edit.setText("ab")
1049 self.assertNotEqual(
1050 unicode(self.controller.view.email_address_line_edit.text()), u"")
1051 self.controller._validate()
1052 self.assertFalse(self.controller.view.send_button.enabled())
1053
1054 def test_empty(self):
1055 """The submit button should be disabled without email."""
1056 self.assertEqual(
1057 unicode(self.controller.view.email_address_line_edit.text()), u"")
1058 self.assertFalse(self.controller.view.send_button.enabled())
1059
1060
686class ForgottenPasswordControllerTestCase(MockerTestCase):1061class ForgottenPasswordControllerTestCase(MockerTestCase):
687 """Test the controller of the fogotten password page."""1062 """Test the controller of the fogotten password page."""
6881063
@@ -729,6 +1104,7 @@
7291104
730 def test_connect_ui(self):1105 def test_connect_ui(self):
731 """Test that the correct ui signals are connected."""1106 """Test that the correct ui signals are connected."""
1107 self.view.email_address_line_edit.textChanged.connect(MATCH(callable))
732 self.view.send_button.clicked.connect(MATCH(callable))1108 self.view.send_button.clicked.connect(MATCH(callable))
733 self.view.try_again_button.clicked.connect(1109 self.view.try_again_button.clicked.connect(
734 self.controller.on_try_again)1110 self.controller.on_try_again)
@@ -789,6 +1165,7 @@
7891165
790 def test_set_translated_strings(self):1166 def test_set_translated_strings(self):
791 """Ensure that the correct strings are set."""1167 """Ensure that the correct strings are set."""
1168 self.controller._subtitle = PASSWORD_HELP
792 self.view.ui.reset_code_line_edit.setPlaceholderText(RESET_CODE_ENTRY)1169 self.view.ui.reset_code_line_edit.setPlaceholderText(RESET_CODE_ENTRY)
793 self.view.ui.password_line_edit.setPlaceholderText(PASSWORD1_ENTRY)1170 self.view.ui.password_line_edit.setPlaceholderText(PASSWORD1_ENTRY)
794 self.view.ui.confirm_password_line_edit.setPlaceholderText(1171 self.view.ui.confirm_password_line_edit.setPlaceholderText(
@@ -806,6 +1183,13 @@
806 self.controller.on_password_changed1183 self.controller.on_password_changed
807 self.backend.on_password_change_error_cb = \1184 self.backend.on_password_change_error_cb = \
808 self.controller.on_password_change_error1185 self.controller.on_password_change_error
1186 self.view.ui.reset_code_line_edit.textChanged.connect(
1187 MATCH(callable))
1188 self.view.ui.password_line_edit.textChanged.connect(
1189 MATCH(callable))
1190 self.view.ui.confirm_password_line_edit.textChanged.connect(
1191 MATCH(callable))
1192
809 self.mocker.replay()1193 self.mocker.replay()
810 self.controller._connect_ui()1194 self.controller._connect_ui()
8111195
@@ -861,3 +1245,75 @@
861 self.mocker.replay()1245 self.mocker.replay()
862 self.assertFalse(self.controller.is_correct_password_confirmation(1246 self.assertFalse(self.controller.is_correct_password_confirmation(
863 password))1247 password))
1248
1249
1250class FakeResetPasswordPage(object):
1251
1252 """Fake ResetPasswordPage."""
1253
1254 def __init__(self, *args):
1255 """Initialize."""
1256 self.ui = self
1257 self.ui.reset_code_line_edit = FakeLineEdit()
1258 self.ui.password_line_edit = FakeLineEdit()
1259 self.ui.confirm_password_line_edit = FakeLineEdit()
1260 self.reset_password_button = self
1261 self._enabled = 9 # Intentionally wrong.
1262
1263 # setEnabled is inherited
1264 # pylint: disable=C0103
1265 def setEnabled(self, value):
1266 """Fake setEnabled."""
1267 self._enabled = value
1268
1269 def enabled(self):
1270 """Fake enabled."""
1271 return self._enabled
1272
1273
1274class ResetPasswordControllerValidationTest(TestCase):
1275
1276 """Tests for ResetPasswordController, but without Mocker."""
1277
1278 def setUp(self):
1279 """Setup test."""
1280 super(ResetPasswordControllerValidationTest, self).setUp()
1281 self.controller = ResetPasswordController()
1282 self.controller.view = FakeResetPasswordPage()
1283 self._called = False
1284
1285 def _set_called(self, *args, **kwargs):
1286 """Store 'args' and 'kwargs' for test assertions."""
1287 self._called = (args, kwargs)
1288
1289 def test_valid(self):
1290 """Enable the button with valid data."""
1291 self.controller.view.reset_code_line_edit.setText("ABCD")
1292 self.controller.view.password_line_edit.setText("1234567A")
1293 self.controller.view.confirm_password_line_edit.setText("1234567A")
1294 self.controller._validate()
1295 self.assertTrue(self.controller.view.reset_password_button.enabled())
1296
1297 def test_invalid_code(self):
1298 """Disable the button with an invalid code."""
1299 self.controller.view.reset_code_line_edit.setText("")
1300 self.controller.view.password_line_edit.setText("1234567A")
1301 self.controller.view.confirm_password_line_edit.setText("1234567A")
1302 self.controller._validate()
1303 self.assertFalse(self.controller.view.reset_password_button.enabled())
1304
1305 def test_invalid_password(self):
1306 """Disable the button with an invalid password."""
1307 self.controller.view.reset_code_line_edit.setText("")
1308 self.controller.view.password_line_edit.setText("1234567")
1309 self.controller.view.confirm_password_line_edit.setText("1234567")
1310 self.controller._validate()
1311 self.assertFalse(self.controller.view.reset_password_button.enabled())
1312
1313 def test_invalid_confirm(self):
1314 """Disable the button with an invalid password confirm."""
1315 self.controller.view.reset_code_line_edit.setText("")
1316 self.controller.view.password_line_edit.setText("1234567A")
1317 self.controller.view.confirm_password_line_edit.setText("1234567")
1318 self.controller._validate()
1319 self.assertFalse(self.controller.view.reset_password_button.enabled())
8641320
=== modified file 'ubuntu_sso/tests/__init__.py'
--- ubuntu_sso/tests/__init__.py 2011-01-12 18:56:56 +0000
+++ ubuntu_sso/tests/__init__.py 2011-08-25 19:16:19 +0000
@@ -22,30 +22,30 @@
2222
23from ubuntu_sso.keyring import get_token_name23from ubuntu_sso.keyring import get_token_name
2424
25APP_NAME = 'The Super App!'25APP_NAME = u'The Super App!'
26CAPTCHA_ID = 'test'26CAPTCHA_ID = u'test'
27CAPTCHA_PATH = os.path.abspath(os.path.join(os.curdir, 'ubuntu_sso', 'tests',27CAPTCHA_PATH = os.path.abspath(os.path.join(os.curdir, 'ubuntu_sso', 'tests',
28 'files', 'captcha.png'))28 'files', 'captcha.png'))
29CAPTCHA_SOLUTION = 'william Byrd'29CAPTCHA_SOLUTION = u'william Byrd'
30EMAIL = 'test@example.com'30EMAIL = u'test@example.com'
31EMAIL_TOKEN = 'B2Pgtf'31EMAIL_TOKEN = u'B2Pgtf'
32GTK_GUI_CLASS = 'UbuntuSSOClientGUI'32GTK_GUI_CLASS = 'UbuntuSSOClientGUI'
33GTK_GUI_MODULE = 'ubuntu_sso.gtk.gui'33GTK_GUI_MODULE = 'ubuntu_sso.gtk.gui'
34HELP_TEXT = """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed34HELP_TEXT = """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed
35lorem nibh. Suspendisse gravida nulla non nunc suscipit pulvinar tempus ut35lorem nibh. Suspendisse gravida nulla non nunc suscipit pulvinar tempus ut
36augue. Morbi consequat, ligula a elementum pretium, dolor nulla tempus metus,36augue. Morbi consequat, ligula a elementum pretium, dolor nulla tempus metus,
37sed viverra nisi risus non velit."""37sed viverra nisi risus non velit."""
38NAME = 'Juanito Pérez'38NAME = u'Juanito Pérez'
39PASSWORD = 'h3lloWorld'39PASSWORD = u'h3lloWorld'
40PING_URL = 'http://localhost/ping-me/'40PING_URL = u'http://localhost/ping-me/'
41RESET_PASSWORD_TOKEN = '8G5Wtq'41RESET_PASSWORD_TOKEN = u'8G5Wtq'
42TOKEN = {u'consumer_key': u'xQ7xDAz',42TOKEN = {u'consumer_key': u'xQ7xDAz',
43 u'consumer_secret': u'KzCJWCTNbbntwfyCKKjomJDzlgqxLy',43 u'consumer_secret': u'KzCJWCTNbbntwfyCKKjomJDzlgqxLy',
44 u'token_name': u'test',44 u'token_name': u'test',
45 u'token': u'GkInOfSMGwTXAUoVQwLUoPxElEEUdhsLVNTPhxHJDUIeHCPNEo',45 u'token': u'GkInOfSMGwTXAUoVQwLUoPxElEEUdhsLVNTPhxHJDUIeHCPNEo',
46 u'token_secret': u'qFYImEtlczPbsCnYyuwLoPDlPEnvNcIktZphPQklAWrvyfFMV'}46 u'token_secret': u'qFYImEtlczPbsCnYyuwLoPDlPEnvNcIktZphPQklAWrvyfFMV'}
47TOKEN_NAME = get_token_name(APP_NAME)47TOKEN_NAME = get_token_name(APP_NAME)
48TC_URL = 'http://localhost/'48TC_URL = u'http://localhost/'
49WINDOW_ID = 549WINDOW_ID = 5
5050
5151
5252
=== modified file 'ubuntu_sso/tests/test_account.py'
--- ubuntu_sso/tests/test_account.py 2011-07-22 20:12:20 +0000
+++ ubuntu_sso/tests/test_account.py 2011-08-25 19:16:19 +0000
@@ -50,9 +50,11 @@
50class FakedCaptchas(object):50class FakedCaptchas(object):
51 """Fake the captcha generator."""51 """Fake the captcha generator."""
5252
53 image_uri = 'file://localhost/%s' % CAPTCHA_PATH.replace(os.path.sep, '/')
54
53 def new(self):55 def new(self):
54 """Return a local fake captcha."""56 """Return a local fake captcha."""
55 return {'image_url': 'file://%s' % CAPTCHA_PATH,57 return {'image_url': self.image_uri,
56 'captcha_id': CAPTCHA_ID}58 'captcha_id': CAPTCHA_ID}
5759
5860
@@ -162,7 +164,8 @@
162 def test_generate_captcha(self):164 def test_generate_captcha(self):
163 """Captcha can be generated."""165 """Captcha can be generated."""
164 filename = self.mktemp()166 filename = self.mktemp()
165 self.addCleanup(lambda: os.remove(filename))167 self.addCleanup(lambda: os.remove(filename)
168 if os.path.exists(filename) else None)
166 captcha_id = self.processor.generate_captcha(filename)169 captcha_id = self.processor.generate_captcha(filename)
167 self.assertEqual(CAPTCHA_ID, captcha_id, 'captcha id must be correct.')170 self.assertEqual(CAPTCHA_ID, captcha_id, 'captcha id must be correct.')
168 self.assertTrue(os.path.isfile(filename), '%s must exist.' % filename)171 self.assertTrue(os.path.isfile(filename), '%s must exist.' % filename)
169172
=== modified file 'ubuntu_sso/tests/test_credentials.py'
--- ubuntu_sso/tests/test_credentials.py 2011-06-24 19:14:17 +0000
+++ ubuntu_sso/tests/test_credentials.py 2011-08-25 19:16:19 +0000
@@ -20,19 +20,19 @@
2020
21import logging21import logging
22import os22import os
23import urllib23import urllib2
2424
25from twisted.internet import defer25from twisted.internet import defer
26from twisted.internet.defer import inlineCallbacks
27from twisted.trial.unittest import TestCase, FailTest26from twisted.trial.unittest import TestCase, FailTest
28from ubuntuone.devtools.handlers import MementoHandler27from ubuntuone.devtools.handlers import MementoHandler
2928
30from ubuntu_sso import credentials29from ubuntu_sso import credentials
30import ubuntu_sso.main
31from ubuntu_sso.credentials import (APP_NAME_KEY, HELP_TEXT_KEY, NO_OP,31from ubuntu_sso.credentials import (APP_NAME_KEY, HELP_TEXT_KEY, NO_OP,
32 PING_URL_KEY, TC_URL_KEY, UI_CLASS_KEY, UI_MODULE_KEY, WINDOW_ID_KEY,32 PING_URL_KEY, TC_URL_KEY, UI_CLASS_KEY, UI_MODULE_KEY, WINDOW_ID_KEY,
33 ERROR_KEY, ERROR_DETAIL_KEY)33 ERROR_KEY, ERROR_DETAIL_KEY)
34from ubuntu_sso.tests import (APP_NAME, EMAIL, GTK_GUI_CLASS, GTK_GUI_MODULE,34from ubuntu_sso.tests import (APP_NAME, EMAIL, GTK_GUI_CLASS, GTK_GUI_MODULE,
35 HELP_TEXT, PING_URL, TC_URL, TOKEN, WINDOW_ID)35 HELP_TEXT, PASSWORD, PING_URL, TC_URL, TOKEN, WINDOW_ID)
3636
3737
38# Access to a protected member of a client class38# Access to a protected member of a client class
@@ -41,6 +41,9 @@
41# Attribute defined outside __init__41# Attribute defined outside __init__
42# pylint: disable=W020142# pylint: disable=W0201
4343
44# Instance of 'class' has no 'x' member (but some types could not be inferred)
45# pylint: disable=E1103
46
4447
45KWARGS = {48KWARGS = {
46 APP_NAME_KEY: APP_NAME,49 APP_NAME_KEY: APP_NAME,
@@ -64,11 +67,13 @@
64 """An error to be used while testing."""67 """An error to be used while testing."""
6568
6669
67class FailingClientGUI(object):70class FailingClient(object):
68 """Fake a failing SSO GUI."""71 """Fake a failing client."""
72
73 err_msg = 'A failing class.'
6974
70 def __init__(self, *args, **kwargs):75 def __init__(self, *args, **kwargs):
71 raise SampleMiscException('A failing GUI class.')76 raise SampleMiscException(self.err_msg)
7277
7378
74class FakedClientGUI(object):79class FakedClientGUI(object):
@@ -82,6 +87,18 @@
82 self.user_cancellation_callback = None87 self.user_cancellation_callback = None
8388
8489
90class FakedSSOLoginRoot(object):
91 """Fake a SSOLoginRoot."""
92
93 args = []
94 kwargs = {}
95
96 def login(self, *args, **kwargs):
97 """Fake login."""
98 self.args = args
99 self.kwargs = kwargs
100
101
85class BasicTestCase(TestCase):102class BasicTestCase(TestCase):
86 """Test case with a helper tracker."""103 """Test case with a helper tracker."""
87104
@@ -113,10 +130,6 @@
113 denial_cb=self.denial,130 denial_cb=self.denial,
114 **KWARGS)131 **KWARGS)
115132
116 def tearDown(self):
117 """Clean up."""
118 super(CredentialsTestCase, self).tearDown()
119
120 def success(self, *args, **kwargs):133 def success(self, *args, **kwargs):
121 """To be called on success."""134 """To be called on success."""
122 self._set_called('success', *args, **kwargs)135 self._set_called('success', *args, **kwargs)
@@ -232,7 +245,7 @@
232class CredentialsLoginSuccessTestCase(CredentialsTestCase):245class CredentialsLoginSuccessTestCase(CredentialsTestCase):
233 """Test suite for the Credentials login success callback."""246 """Test suite for the Credentials login success callback."""
234247
235 @inlineCallbacks248 @defer.inlineCallbacks
236 def test_cred_not_found(self):249 def test_cred_not_found(self):
237 """On auth success, if cred not found, self.error_cb is called."""250 """On auth success, if cred not found, self.error_cb is called."""
238 self.patch(credentials.Keyring, 'get_credentials',251 self.patch(credentials.Keyring, 'get_credentials',
@@ -245,7 +258,7 @@
245 detailed_error=AssertionError(msg))258 detailed_error=AssertionError(msg))
246 self.assertEqual(result, None)259 self.assertEqual(result, None)
247260
248 @inlineCallbacks261 @defer.inlineCallbacks
249 def test_cred_error(self):262 def test_cred_error(self):
250 """On auth success, if cred failed, self.error_cb is called."""263 """On auth success, if cred failed, self.error_cb is called."""
251 expected_error = SampleMiscException()264 expected_error = SampleMiscException()
@@ -259,7 +272,7 @@
259 self.assertEqual(result, None)272 self.assertEqual(result, None)
260 self.assertTrue(self.memento.check_exception(SampleMiscException))273 self.assertTrue(self.memento.check_exception(SampleMiscException))
261274
262 @inlineCallbacks275 @defer.inlineCallbacks
263 def test_ping_success(self):276 def test_ping_success(self):
264 """Auth success + cred found + ping success, success_cb is called."""277 """Auth success + cred found + ping success, success_cb is called."""
265 self.patch(credentials.Keyring, 'get_credentials',278 self.patch(credentials.Keyring, 'get_credentials',
@@ -271,7 +284,7 @@
271 self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}))284 self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}))
272 self.assertEqual(result, 0)285 self.assertEqual(result, 0)
273286
274 @inlineCallbacks287 @defer.inlineCallbacks
275 def test_ping_error(self):288 def test_ping_error(self):
276 """Auth success + cred found + ping error, error_cb is called.289 """Auth success + cred found + ping error, error_cb is called.
277290
@@ -299,7 +312,7 @@
299 # exception logged312 # exception logged
300 self.assertTrue(self.memento.check_exception(FailTest, error))313 self.assertTrue(self.memento.check_exception(FailTest, error))
301314
302 @inlineCallbacks315 @defer.inlineCallbacks
303 def test_pings_url(self):316 def test_pings_url(self):
304 """On auth success, self.ping_url is opened."""317 """On auth success, self.ping_url is opened."""
305 self.patch(credentials.Keyring, 'get_credentials',318 self.patch(credentials.Keyring, 'get_credentials',
@@ -315,7 +328,7 @@
315328
316 self.assertEqual(self._url_pinged, ((APP_NAME, EMAIL, TOKEN), {}))329 self.assertEqual(self._url_pinged, ((APP_NAME, EMAIL, TOKEN), {}))
317330
318 @inlineCallbacks331 @defer.inlineCallbacks
319 def test_no_ping_url_is_success(self):332 def test_no_ping_url_is_success(self):
320 """Auth success + cred found + no ping url, success_cb is called.333 """Auth success + cred found + no ping url, success_cb is called.
321334
@@ -353,15 +366,15 @@
353 def faked_urlopen(request):366 def faked_urlopen(request):
354 """Fake urlopener."""367 """Fake urlopener."""
355 self._request = request368 self._request = request
356 response = urllib.addinfourl(fp=open(os.path.devnull),369 response = urllib2.addinfourl(fp=open(os.path.devnull),
357 headers=request.headers,370 headers=request.headers,
358 url=request.get_full_url(),371 url=request.get_full_url(),
359 code=200)372 code=200)
360 return response373 return response
361374
362 self.patch(credentials.urllib2, 'urlopen', faked_urlopen)375 self.patch(credentials.urllib2, 'urlopen', faked_urlopen)
363376
364 @inlineCallbacks377 @defer.inlineCallbacks
365 def test_ping_url_if_url_is_none(self):378 def test_ping_url_if_url_is_none(self):
366 """self.ping_url is opened."""379 """self.ping_url is opened."""
367 self.patch(credentials.urllib2, 'urlopen', self.fail)380 self.patch(credentials.urllib2, 'urlopen', self.fail)
@@ -370,7 +383,7 @@
370 credentials=TOKEN)383 credentials=TOKEN)
371 # no failure384 # no failure
372385
373 @inlineCallbacks386 @defer.inlineCallbacks
374 def test_ping_url(self):387 def test_ping_url(self):
375 """On auth success, self.ping_url is opened."""388 """On auth success, self.ping_url is opened."""
376 yield self.obj._ping_url(app_name=APP_NAME, email=EMAIL,389 yield self.obj._ping_url(app_name=APP_NAME, email=EMAIL,
@@ -380,23 +393,23 @@
380 self.assertEqual(self._request.get_full_url(),393 self.assertEqual(self._request.get_full_url(),
381 self.obj.ping_url + EMAIL)394 self.obj.ping_url + EMAIL)
382395
383 @inlineCallbacks396 @defer.inlineCallbacks
384 def test_request_is_signed_with_credentials(self):397 def test_request_is_signed_with_credentials(self):
385 """The http request to self.ping_url is signed with the credentials."""398 """The http request to self.ping_url is signed with the credentials."""
399
400 def fake_it(*a, **kw):
401 """Fake oauth_headers."""
402 self._set_called(*a, **kw)
403 return {}
404
405 self.patch(credentials.utils, 'oauth_headers', fake_it)
386 result = yield self.obj._ping_url(APP_NAME, EMAIL, TOKEN)406 result = yield self.obj._ping_url(APP_NAME, EMAIL, TOKEN)
387 headers = self._request.headers407
388408 self.assertEqual(self._called,
389 self.assertIn('Authorization', headers)409 ((self.obj.ping_url + EMAIL, TOKEN), {}))
390 oauth_stuff = headers['Authorization']410 self.assertEqual(result.code, 200)
391411
392 expected = 'oauth_consumer_key="xQ7xDAz", ' \412 @defer.inlineCallbacks
393 'oauth_signature_method="HMAC-SHA1", oauth_version="1.0", ' \
394 'oauth_token="GkInOfSMGwTXAUoVQwLUoPxElEEUdhsLVNTPhxHJDUIeHCPNEo' \
395 '", oauth_signature="'
396 self.assertIn(expected, oauth_stuff)
397 self.assertEqual(result, 200)
398
399 @inlineCallbacks
400 def test_ping_url_error(self):413 def test_ping_url_error(self):
401 """Exception is handled if ping fails."""414 """Exception is handled if ping fails."""
402 error = 'Blu'415 error = 'Blu'
@@ -408,12 +421,52 @@
408 self.assert_error_cb_called(msg=msg, detailed_error=FailTest(error))421 self.assert_error_cb_called(msg=msg, detailed_error=FailTest(error))
409 self.assertTrue(self.memento.check_exception(FailTest, error))422 self.assertTrue(self.memento.check_exception(FailTest, error))
410423
424 @defer.inlineCallbacks
425 def test_ping_url_formatting(self):
426 """The email is added as the first formatting argument."""
427 self.obj.ping_url = u'http://example.com/{email}/something/else'
428 result = yield self.obj._ping_url(app_name=APP_NAME, email=EMAIL,
429 credentials=TOKEN)
430
431 expected = self.obj.ping_url.format(email=EMAIL)
432 self.assertEqual(expected, result.url)
433
434 @defer.inlineCallbacks
435 def test_ping_url_formatting_with_query_params(self):
436 """The email is added as the first formatting argument."""
437 self.obj.ping_url = u'http://example.com/{email}?something=else'
438 result = yield self.obj._ping_url(app_name=APP_NAME, email=EMAIL,
439 credentials=TOKEN)
440
441 expected = self.obj.ping_url.format(email=EMAIL)
442 self.assertEqual(expected, result.url)
443
444 @defer.inlineCallbacks
445 def test_ping_url_formatting_no_email_kwarg(self):
446 """The email is added as the first formatting argument."""
447 self.obj.ping_url = u'http://example.com/{0}/yadda/?something=else'
448 result = yield self.obj._ping_url(app_name=APP_NAME, email=EMAIL,
449 credentials=TOKEN)
450
451 expected = self.obj.ping_url.format(EMAIL)
452 self.assertEqual(expected, result.url)
453
454 @defer.inlineCallbacks
455 def test_ping_url_formatting_no_format(self):
456 """The email is appended if formatting could not be accomplished."""
457 self.obj.ping_url = u'http://example.com/yadda/'
458 result = yield self.obj._ping_url(app_name=APP_NAME, email=EMAIL,
459 credentials=TOKEN)
460
461 expected = self.obj.ping_url + EMAIL
462 self.assertEqual(expected, result.url)
463
411464
412class FindCredentialsTestCase(CredentialsTestCase):465class FindCredentialsTestCase(CredentialsTestCase):
413 """Test suite for the find_credentials method."""466 """Test suite for the find_credentials method."""
414 timeout = 5467 timeout = 5
415468
416 @inlineCallbacks469 @defer.inlineCallbacks
417 def test_find_credentials(self):470 def test_find_credentials(self):
418 """A deferred with credentials is returned when found."""471 """A deferred with credentials is returned when found."""
419 self.patch(credentials.Keyring, 'get_credentials',472 self.patch(credentials.Keyring, 'get_credentials',
@@ -422,7 +475,7 @@
422 token = yield self.obj.find_credentials()475 token = yield self.obj.find_credentials()
423 self.assertEqual(token, TOKEN)476 self.assertEqual(token, TOKEN)
424477
425 @inlineCallbacks478 @defer.inlineCallbacks
426 def test_credentials_not_found(self):479 def test_credentials_not_found(self):
427 """find_credentials returns {} when no credentials are found."""480 """find_credentials returns {} when no credentials are found."""
428 self.patch(credentials.Keyring, 'get_credentials',481 self.patch(credentials.Keyring, 'get_credentials',
@@ -431,7 +484,7 @@
431 token = yield self.obj.find_credentials()484 token = yield self.obj.find_credentials()
432 self.assertEqual(token, {})485 self.assertEqual(token, {})
433486
434 @inlineCallbacks487 @defer.inlineCallbacks
435 def test_keyring_failure(self):488 def test_keyring_failure(self):
436 """Failures from the keyring are handled."""489 """Failures from the keyring are handled."""
437 expected_error = SampleMiscException()490 expected_error = SampleMiscException()
@@ -445,7 +498,7 @@
445class ClearCredentialsTestCase(CredentialsTestCase):498class ClearCredentialsTestCase(CredentialsTestCase):
446 """Test suite for the clear_credentials method."""499 """Test suite for the clear_credentials method."""
447500
448 @inlineCallbacks501 @defer.inlineCallbacks
449 def test_clear_credentials(self):502 def test_clear_credentials(self):
450 """The credentials are cleared."""503 """The credentials are cleared."""
451 self.patch(credentials.Keyring, 'delete_credentials',504 self.patch(credentials.Keyring, 'delete_credentials',
@@ -454,7 +507,7 @@
454 yield self.obj.clear_credentials()507 yield self.obj.clear_credentials()
455 self.assertEqual(self._called, ((APP_NAME,), {}))508 self.assertEqual(self._called, ((APP_NAME,), {}))
456509
457 @inlineCallbacks510 @defer.inlineCallbacks
458 def test_keyring_failure(self):511 def test_keyring_failure(self):
459 """Failures from the keyring are handled."""512 """Failures from the keyring are handled."""
460 expected_error = SampleMiscException()513 expected_error = SampleMiscException()
@@ -468,7 +521,7 @@
468class StoreCredentialsTestCase(CredentialsTestCase):521class StoreCredentialsTestCase(CredentialsTestCase):
469 """Test suite for the store_credentials method."""522 """Test suite for the store_credentials method."""
470523
471 @inlineCallbacks524 @defer.inlineCallbacks
472 def test_store_credentials(self):525 def test_store_credentials(self):
473 """The credentials are stored."""526 """The credentials are stored."""
474 self.patch(credentials.Keyring, 'set_credentials',527 self.patch(credentials.Keyring, 'set_credentials',
@@ -477,7 +530,7 @@
477 yield self.obj.store_credentials(TOKEN)530 yield self.obj.store_credentials(TOKEN)
478 self.assertEqual(self._called, ((APP_NAME, TOKEN,), {}))531 self.assertEqual(self._called, ((APP_NAME, TOKEN,), {}))
479532
480 @inlineCallbacks533 @defer.inlineCallbacks
481 def test_keyring_failure(self):534 def test_keyring_failure(self):
482 """Failures from the keyring are handled."""535 """Failures from the keyring are handled."""
483 expected_error = SampleMiscException()536 expected_error = SampleMiscException()
@@ -493,85 +546,89 @@
493546
494 operation = 'register'547 operation = 'register'
495 login_only = False548 login_only = False
549 kwargs = {}
550 inner_class = FakedClientGUI
496551
497 def setUp(self):552 def setUp(self):
498 super(RegisterTestCase, self).setUp()553 super(RegisterTestCase, self).setUp()
499 self.ui_kwargs = UI_KWARGS.copy()554 self.inner_kwargs = UI_KWARGS.copy()
500 self.ui_kwargs['login_only'] = self.login_only555 self.inner_kwargs['login_only'] = self.login_only
556 self.method_call = getattr(self.obj, self.operation)
501557
502 @inlineCallbacks558 @defer.inlineCallbacks
503 def test_with_existent_token(self):559 def test_with_existent_token(self):
504 """The operation returns the credentials if already in keyring."""560 """The operation returns the credentials if already in keyring."""
505 self.patch(credentials.Keyring, 'get_credentials',561 self.patch(credentials.Keyring, 'get_credentials',
506 lambda kr, app: defer.succeed(TOKEN))562 lambda kr, app: defer.succeed(TOKEN))
507563
508 yield getattr(self.obj, self.operation)()564 yield self.method_call(**self.kwargs)
509565
510 self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}))566 self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}))
511567
512 @inlineCallbacks568 @defer.inlineCallbacks
513 def test_without_existent_token(self):569 def test_without_existent_token(self):
514 """The operation returns the credentials gathered by the GUI."""570 """The operation returns the credentials gathered by the inner call."""
515 self.patch(credentials.Keyring, 'get_credentials',571 self.patch(credentials.Keyring, 'get_credentials',
516 lambda kr, app: defer.succeed(None))572 lambda kr, app: defer.succeed(None))
517573
518 yield getattr(self.obj, self.operation)()574 yield self.method_call(**self.kwargs)
519575
520 self.assertEqual(self.obj.gui.kwargs, self.ui_kwargs)576 self.assertEqual(self.obj.inner.kwargs, self.inner_kwargs)
521577
522 @inlineCallbacks578 @defer.inlineCallbacks
523 def test_with_exception_on_credentials(self):579 def test_with_exception_on_credentials(self):
524 """The operation calls the error callback if a exception occurs."""580 """The operation calls the error callback if a exception occurs."""
525 expected_error = SampleMiscException()581 expected_error = SampleMiscException()
526 self.patch(credentials.Keyring, 'get_credentials',582 self.patch(credentials.Keyring, 'get_credentials',
527 lambda kr, app: defer.fail(expected_error))583 lambda kr, app: defer.fail(expected_error))
528584
529 yield getattr(self.obj, self.operation)()585 yield self.method_call(**self.kwargs)
530586
531 msg = 'Problem while retrieving credentials'587 msg = 'Problem while retrieving credentials'
532 self.assert_error_cb_called(msg=msg, detailed_error=expected_error)588 self.assert_error_cb_called(msg=msg, detailed_error=expected_error)
533 self.assertTrue(self.memento.check_exception(SampleMiscException))589 self.assertTrue(self.memento.check_exception(SampleMiscException))
534590
535 @inlineCallbacks591 @defer.inlineCallbacks
536 def test_with_exception_on_gui(self):592 def test_with_exception_on_inner_call(self, msg=None):
537 """The operation calls the error callback if a GUI exception occurs."""593 """The operation calls the error callback if a exception occurs."""
538 self.patch(credentials.Keyring, 'get_credentials',594 self.patch(credentials.Keyring, 'get_credentials',
539 lambda kr, app: defer.succeed(None))595 lambda kr, app: defer.succeed(None))
540 err = 'A failing GUI class.'596 self.obj.ui_class = 'FailingClient'
541 self.obj.ui_class = 'FailingClientGUI'597
542598 yield self.method_call(**self.kwargs)
543 yield getattr(self.obj, self.operation)()599
544600 if msg is None:
545 msg = 'Problem opening the Ubuntu SSO user interface'601 msg = 'Problem opening the Ubuntu SSO user interface'
546 self.assert_error_cb_called(msg=msg,602 self.assert_error_cb_called(msg=msg,
547 detailed_error=SampleMiscException(err))603 detailed_error=SampleMiscException(FailingClient.err_msg))
548 self.assertTrue(self.memento.check_exception(SampleMiscException, err))604 self.assertTrue(self.memento.check_exception(SampleMiscException,
605 FailingClient.err_msg))
549606
550 @inlineCallbacks607 @defer.inlineCallbacks
551 def test_connects_gui_signals(self):608 def test_connects_inner_signals(self):
552 """GUI callbacks are properly connected."""609 """Inner callbacks are properly connected."""
553 self.patch(credentials.Keyring, 'get_credentials',610 self.patch(credentials.Keyring, 'get_credentials',
554 lambda kr, app: defer.succeed(None))611 lambda kr, app: defer.succeed(None))
555 yield getattr(self.obj, self.operation)()612 yield self.method_call(**self.kwargs)
556613
557 self.assertEqual(self.obj.gui.login_success_callback,614 self.assertEqual(self.obj.inner.login_success_callback,
558 self.obj._login_success_cb)615 self.obj._login_success_cb)
559 self.assertEqual(self.obj.gui.registration_success_callback,616 self.assertEqual(self.obj.inner.registration_success_callback,
560 self.obj._login_success_cb)617 self.obj._login_success_cb)
561 self.assertEqual(self.obj.gui.user_cancellation_callback,618 self.assertEqual(self.obj.inner.user_cancellation_callback,
562 self.obj._auth_denial_cb)619 self.obj._auth_denial_cb)
563620
564 @inlineCallbacks621 @defer.inlineCallbacks
565 def test_gui_is_created(self):622 def test_inner_object_is_created(self):
566 """The GUI is created and stored."""623 """The inner object is created and stored."""
567 self.patch(credentials.Keyring, 'get_credentials',624 self.patch(credentials.Keyring, 'get_credentials',
568 lambda kr, app: defer.succeed(None))625 lambda kr, app: defer.succeed(None))
569626
570 yield getattr(self.obj, self.operation)()627 yield self.method_call(**self.kwargs)
571628
572 self.assertIsInstance(self.obj.gui, FakedClientGUI)629 self.assertIsInstance(self.obj.inner, self.inner_class)
573 self.assertEqual(self.obj.gui.args, ())630 self.assertEqual(self.obj.inner.args, ())
574 self.assertEqual(self.obj.gui.kwargs, self.ui_kwargs)631 self.assertEqual(self.obj.inner.kwargs, self.inner_kwargs)
575632
576633
577class LoginTestCase(RegisterTestCase):634class LoginTestCase(RegisterTestCase):
@@ -579,3 +636,32 @@
579636
580 operation = 'login'637 operation = 'login'
581 login_only = True638 login_only = True
639
640
641class LoginEmailPasswordTestCase(RegisterTestCase):
642 """Test suite for the login_email_password method."""
643
644 operation = 'login_email_password'
645 login_only = True
646 kwargs = {'email': EMAIL, 'password': PASSWORD}
647 inner_class = FakedSSOLoginRoot
648
649 def setUp(self):
650 super(LoginEmailPasswordTestCase, self).setUp()
651 self.inner_kwargs = {APP_NAME_KEY: APP_NAME, 'email': EMAIL,
652 'password': PASSWORD,
653 'result_cb': self.obj._login_success_cb,
654 'error_cb': self.obj._error_cb,
655 'not_validated_cb': self.obj._error_cb}
656 self.patch(ubuntu_sso.main, 'SSOLoginRoot', FakedSSOLoginRoot)
657
658 def test_with_exception_on_inner_call(self, msg=None):
659 """The operation calls the error callback if a exception occurs."""
660 self.patch(ubuntu_sso.main, 'SSOLoginRoot', FailingClient)
661 msg = 'Problem logging with email and password.'
662 return super(LoginEmailPasswordTestCase,
663 self).test_with_exception_on_inner_call(msg=msg)
664
665 def test_connects_inner_signals(self):
666 """Inner callbacks are properly connected."""
667 # there is no inner callbacks for the SSOLoginRoot object
582668
=== modified file 'ubuntu_sso/utils/__init__.py'
--- ubuntu_sso/utils/__init__.py 2010-11-30 13:21:17 +0000
+++ ubuntu_sso/utils/__init__.py 2011-08-25 19:16:19 +0000
@@ -17,3 +17,38 @@
17# with this program. If not, see <http://www.gnu.org/licenses/>.17# with this program. If not, see <http://www.gnu.org/licenses/>.
1818
19"""Utility modules that may find use outside ubuntu_sso."""19"""Utility modules that may find use outside ubuntu_sso."""
20
21import cgi
22
23from oauth import oauth
24from urlparse import urlparse
25
26
27def oauth_headers(url, credentials, http_method='GET'):
28 """Sign 'url' using 'credentials'.
29
30 * 'url' must be a valid unicode url.
31 * 'credentials' must be a valid OAuth token.
32
33 Return oauth headers that can be pass to any Request like object.
34
35 """
36 assert isinstance(url, unicode)
37 url = url.encode('utf-8')
38 _, _, _, _, query, _ = urlparse(url)
39 parameters = dict(cgi.parse_qsl(query))
40
41 consumer = oauth.OAuthConsumer(credentials['consumer_key'],
42 credentials['consumer_secret'])
43 token = oauth.OAuthToken(credentials['token'],
44 credentials['token_secret'])
45 kwargs = dict(oauth_consumer=consumer, token=token,
46 http_method=http_method, http_url=url,
47 parameters=parameters)
48 get_request = oauth.OAuthRequest.from_consumer_and_token
49 oauth_req = get_request(**kwargs)
50 hmac_sha1 = oauth.OAuthSignatureMethod_HMAC_SHA1()
51 oauth_req.sign_request(hmac_sha1, consumer, token)
52 headers = oauth_req.to_header()
53
54 return headers
2055
=== added file 'ubuntu_sso/utils/tests/test_oauth_headers.py'
--- ubuntu_sso/utils/tests/test_oauth_headers.py 1970-01-01 00:00:00 +0000
+++ ubuntu_sso/utils/tests/test_oauth_headers.py 2011-08-25 19:16:19 +0000
@@ -0,0 +1,109 @@
1# -*- coding: utf-8 -*-
2
3# Author: Natalia B. Bidart <natalia.bidart@canonical.com>
4#
5# Copyright 2011 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18
19"""Tests for the oauth_headers helper function."""
20
21from twisted.trial.unittest import TestCase
22
23from ubuntu_sso.utils import oauth, oauth_headers
24from ubuntu_sso.tests import TOKEN
25
26
27class FakedOAuthRequest(object):
28 """Replace the OAuthRequest class."""
29
30 params = {}
31
32 def __init__(self):
33 self.sign_request = lambda *args, **kwargs: None
34 self.to_header = lambda *args, **kwargs: {}
35
36 def from_consumer_and_token(oauth_consumer, **kwargs):
37 """Fake the method storing the params for check."""
38 FakedOAuthRequest.params.update(kwargs)
39 return FakedOAuthRequest()
40 from_consumer_and_token = staticmethod(from_consumer_and_token)
41
42
43class SignWithCredentialsTestCase(TestCase):
44 """Test suite for the oauth_headers method."""
45
46 url = u'http://example.com'
47
48 def build_header(self, url, http_method='GET'):
49 """Build an Oauth header for comparison."""
50 consumer = oauth.OAuthConsumer(TOKEN['consumer_key'],
51 TOKEN['consumer_secret'])
52 token = oauth.OAuthToken(TOKEN['token'],
53 TOKEN['token_secret'])
54 get_request = oauth.OAuthRequest.from_consumer_and_token
55 oauth_req = get_request(oauth_consumer=consumer, token=token,
56 http_method=http_method, http_url=url)
57 oauth_req.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(),
58 consumer, token)
59 return oauth_req.to_header()
60
61 def dictify_header(self, header):
62 """Convert an OAuth header into a dict."""
63 result = {}
64 fields = header.split(', ')
65 for field in fields:
66 key, value = field.split('=')
67 result[key] = value.strip('"')
68
69 return result
70
71 def assert_header_equal(self, expected, actual):
72 """Is 'expected' equals to 'actual'?"""
73 expected = self.dictify_header(expected['Authorization'])
74 actual = self.dictify_header(actual['Authorization'])
75 for header in (expected, actual):
76 header.pop('oauth_nonce')
77 header.pop('oauth_timestamp')
78 header.pop('oauth_signature')
79
80 self.assertEqual(expected, actual)
81
82 def assert_method_called(self, path, query_str='', http_method='GET'):
83 """Assert that the url build by joining 'paths' was called."""
84 expected = (self.url, path, query_str)
85 expected = ''.join(expected).encode('utf8')
86 expected = self.build_header(expected, http_method=http_method)
87 actual = oauth_headers(url=self.url + path, credentials=TOKEN)
88 self.assert_header_equal(expected, actual)
89
90 def test_call(self):
91 """Calling 'get' triggers an OAuth signed GET request."""
92 path = u'/test/'
93 self.assert_method_called(path)
94
95 def test_quotes_path(self):
96 """Calling 'get' quotes the path."""
97 path = u'/test me more, sí!/'
98 self.assert_method_called(path)
99
100 def test_adds_parameters_to_oauth_request(self):
101 """The query string from the path is used in the oauth request."""
102 self.patch(oauth, 'OAuthRequest', FakedOAuthRequest)
103
104 path = u'/test/something?foo=bar'
105 oauth_headers(url=self.url + path, credentials=TOKEN)
106
107 self.assertIn('parameters', FakedOAuthRequest.params)
108 self.assertEqual(FakedOAuthRequest.params['parameters'],
109 {'foo': 'bar'})
0110
=== modified file 'ubuntu_sso/utils/txsecrets.py'
--- ubuntu_sso/utils/txsecrets.py 2011-07-22 20:12:20 +0000
+++ ubuntu_sso/utils/txsecrets.py 2011-08-25 19:16:19 +0000
@@ -21,13 +21,19 @@
21 * http://code.confuego.org/secrets-xdg-specs/21 * http://code.confuego.org/secrets-xdg-specs/
22"""22"""
2323
24import gobject24import sys
25# pylint: disable=E0611
26if 'gobject' in sys.modules:
27 import gobject as GObject
28else:
29 from gi.repository import GObject
30# pylint: enable=E0611
25import dbus31import dbus
26from dbus.mainloop.glib import DBusGMainLoop32from dbus.mainloop.glib import DBusGMainLoop
27import dbus.mainloop.glib33import dbus.mainloop.glib
28from twisted.internet.defer import Deferred34from twisted.internet.defer import Deferred
2935
30gobject.threads_init()36GObject.threads_init()
31dbus.mainloop.glib.threads_init()37dbus.mainloop.glib.threads_init()
32DBusGMainLoop(set_as_default=True)38DBusGMainLoop(set_as_default=True)
3339
3440
=== modified file 'ubuntu_sso/utils/ui.py'
--- ubuntu_sso/utils/ui.py 2011-07-22 20:12:20 +0000
+++ ubuntu_sso/utils/ui.py 2011-08-25 19:16:19 +0000
@@ -20,10 +20,10 @@
2020
21import os21import os
22import re22import re
23import xdg
24import gettext23import gettext
2524
26from ubuntu_sso.logger import setup_logging25from ubuntu_sso.logger import setup_logging
26from ubuntu_sso import xdg_base_directory
2727
28logger = setup_logging('ubuntu_sso.utils.ui')28logger = setup_logging('ubuntu_sso.utils.ui')
2929
@@ -35,6 +35,7 @@
35CAPTCHA_SOLUTION_ENTRY = _('Type the characters above')35CAPTCHA_SOLUTION_ENTRY = _('Type the characters above')
36CAPTCHA_LOAD_ERROR = _('There was a problem getting the captcha, '36CAPTCHA_LOAD_ERROR = _('There was a problem getting the captcha, '
37 'reloading...')37 'reloading...')
38CAPTCHA_REQUIRED_ERROR = _('The captcha is a required field')
38CONNECT_HELP_LABEL = _('To connect this computer to %(app_name)s ' \39CONNECT_HELP_LABEL = _('To connect this computer to %(app_name)s ' \
39 'enter your details below.')40 'enter your details below.')
40EMAIL1_ENTRY = _('Email address')41EMAIL1_ENTRY = _('Email address')
@@ -117,7 +118,7 @@
117 return result118 return result
118119
119 # no local data dir, looking within system data dirs120 # no local data dir, looking within system data dirs
120 data_dirs = xdg.BaseDirectory.xdg_data_dirs121 data_dirs = xdg_base_directory.xdg_data_dirs
121 for path in data_dirs:122 for path in data_dirs:
122 result = os.path.join(path, 'ubuntu-sso-client', 'data')123 result = os.path.join(path, 'ubuntu-sso-client', 'data')
123 result = os.path.abspath(result)124 result = os.path.abspath(result)
124125
=== added directory 'ubuntu_sso/xdg_base_directory'
=== added file 'ubuntu_sso/xdg_base_directory/__init__.py'
--- ubuntu_sso/xdg_base_directory/__init__.py 1970-01-01 00:00:00 +0000
+++ ubuntu_sso/xdg_base_directory/__init__.py 2011-08-25 19:16:19 +0000
@@ -0,0 +1,35 @@
1# Author: Natalia B. Bidart <natalia.bidart@canonical.com>
2#
3# Copyright 2011 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"""XDG multiplatform."""
18
19import sys
20
21# pylint: disable=C0103
22if sys.platform == "win32":
23 from ubuntu_sso.xdg_base_directory import windows
24 load_config_paths = windows.load_config_paths
25 save_config_path = windows.save_config_path
26 xdg_cache_home = windows.xdg_cache_home
27 xdg_data_home = windows.xdg_data_home
28 xdg_data_dirs = windows.xdg_data_dirs
29else:
30 import xdg.BaseDirectory
31 load_config_paths = xdg.BaseDirectory.load_config_paths
32 save_config_path = xdg.BaseDirectory.save_config_path
33 xdg_cache_home = xdg.BaseDirectory.xdg_cache_home
34 xdg_data_home = xdg.BaseDirectory.xdg_data_home
35 xdg_data_dirs = xdg.BaseDirectory.xdg_data_dirs
036
=== added directory 'ubuntu_sso/xdg_base_directory/tests'
=== added file 'ubuntu_sso/xdg_base_directory/tests/__init__.py'
--- ubuntu_sso/xdg_base_directory/tests/__init__.py 1970-01-01 00:00:00 +0000
+++ ubuntu_sso/xdg_base_directory/tests/__init__.py 2011-08-25 19:16:19 +0000
@@ -0,0 +1,16 @@
1# ubuntu_sso - Ubuntu Single Sign On client support for desktop apps
2#
3# Copyright 2009-2010 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"""XDG tests."""
017
=== added file 'ubuntu_sso/xdg_base_directory/tests/test_common.py'
--- ubuntu_sso/xdg_base_directory/tests/test_common.py 1970-01-01 00:00:00 +0000
+++ ubuntu_sso/xdg_base_directory/tests/test_common.py 2011-08-25 19:16:19 +0000
@@ -0,0 +1,49 @@
1# -*- coding: utf-8 -*-
2#
3# Authors: Natalia B. Bidart <natalia.bidart@canonical.com>
4#
5# Copyright 2011 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18
19"""Platform independent tests for the XDG constants."""
20
21import os
22
23from twisted.trial.unittest import TestCase
24
25from ubuntu_sso import xdg_base_directory
26
27
28class TestBaseDirectory(TestCase):
29 """Tests for the BaseDirectory module."""
30
31 def test_xdg_cache_home_is_bytes(self):
32 """The returned path is bytes."""
33 actual = xdg_base_directory.xdg_cache_home
34 self.assertIsInstance(actual, str)
35
36 def test_xdg_data_home_is_bytes(self):
37 """The returned path is bytes."""
38 actual = xdg_base_directory.xdg_data_home
39 self.assertIsInstance(actual, str)
40
41 def test_load_config_paths_filter(self):
42 """Since those folders don't exist, this should be empty."""
43 self.assertEqual(list(xdg_base_directory.load_config_paths("x")), [])
44
45 def test_save_config_path(self):
46 """The path should end with xdg_config/x (respecting the separator)."""
47 self.patch(os, "makedirs", lambda *args: None)
48 result = xdg_base_directory.save_config_path("x")
49 self.assertEqual(result.split(os.sep)[-2:], ['xdg_config', 'x'])
050
=== added file 'ubuntu_sso/xdg_base_directory/tests/test_windows.py'
--- ubuntu_sso/xdg_base_directory/tests/test_windows.py 1970-01-01 00:00:00 +0000
+++ ubuntu_sso/xdg_base_directory/tests/test_windows.py 2011-08-25 19:16:19 +0000
@@ -0,0 +1,112 @@
1# -*- coding: utf-8 -*-
2#
3# Authors: Natalia B. Bidart <natalia.bidart@canonical.com>
4#
5# Copyright 2011 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18
19"""Windows-specific tests for the XDG constants."""
20
21import os
22import sys
23
24from twisted.trial.unittest import TestCase
25from ubuntuone.devtools.testcase import skipIfOS
26
27
28# pylint: disable=E1101, E0611, F0401
29if sys.platform == "win32":
30 import win32com.shell
31 from ubuntu_sso.xdg_base_directory.windows import (
32 get_config_dirs,
33 get_data_dirs,
34 get_special_folders,
35 )
36
37
38# pylint: disable=C0103
39class FakeShellConModule(object):
40 """Override CSIDL_ constants."""
41 CSIDL_PROFILE = 0
42 CSIDL_LOCAL_APPDATA = 1
43 CSIDL_COMMON_APPDATA = 2
44
45
46class FakeShellModule(object):
47
48 """Fake Shell Module."""
49
50 def __init__(self):
51 """Set the proper mapping between CSIDL_ consts."""
52 self.values = {
53 0: u'c:\\path\\to\\users\\home',
54 1: u'c:\\path\\to\\users\\home\\appData\\local',
55 2: u'c:\\programData',
56 }
57
58 def SHGetFolderPath(self, dummy0, shellconValue, dummy2, dummy3):
59 """Override SHGetFolderPath functionality."""
60 return self.values[shellconValue]
61
62
63@skipIfOS('linux2', 'Windows-specific tests.')
64class TestBaseDirectoryWindows(TestCase):
65 """Tests for the BaseDirectory module."""
66
67 def test_get_special_folders(self):
68 """Make sure we can import the platform module."""
69
70 shellModule = FakeShellModule()
71 self.patch(win32com.shell, "shell", shellModule)
72 self.patch(win32com.shell, "shellcon", FakeShellConModule())
73 special_folders = get_special_folders()
74 self.assertTrue('Personal' in special_folders)
75 self.assertTrue('Local AppData' in special_folders)
76 self.assertTrue('AppData' in special_folders)
77 self.assertTrue('Common AppData' in special_folders)
78
79 self.assertTrue(special_folders['Personal'] == \
80 shellModule.values[FakeShellConModule.CSIDL_PROFILE])
81 self.assertTrue(special_folders['Local AppData'] == \
82 shellModule.values[FakeShellConModule.CSIDL_LOCAL_APPDATA])
83 self.assertTrue(special_folders['Local AppData'].startswith(
84 special_folders['AppData']))
85 self.assertTrue(special_folders['Common AppData'] == \
86 shellModule.values[FakeShellConModule.CSIDL_COMMON_APPDATA])
87
88 # Can't use assert_syncdaemon_path
89 for val in special_folders.itervalues():
90 self.assertIsInstance(val, str)
91 val.decode('utf8')
92 # Should not raise exceptions
93
94 def test_get_data_dirs(self):
95 """Check thet get_data_dirs uses pathsep correctly."""
96 bad_sep = filter(lambda x: x not in os.pathsep, ":;")
97 dir_list = ["A", "B", bad_sep, "C"]
98 self.patch(os, "environ",
99 dict(XDG_DATA_DIRS=os.pathsep.join(
100 dir_list)))
101 dirs = get_data_dirs()
102 self.assertEqual(dirs, dir_list)
103
104 def test_get_config_dirs(self):
105 """Check thet get_data_dirs uses pathsep correctly."""
106 bad_sep = filter(lambda x: x not in os.pathsep, ":;")
107 dir_list = ["A", "B", bad_sep, "C"]
108 self.patch(os, "environ",
109 dict(XDG_CONFIG_DIRS=os.pathsep.join(
110 dir_list)))
111 dirs = get_config_dirs()[1:]
112 self.assertEqual(dirs, dir_list)
0113
=== added file 'ubuntu_sso/xdg_base_directory/windows.py'
--- ubuntu_sso/xdg_base_directory/windows.py 1970-01-01 00:00:00 +0000
+++ ubuntu_sso/xdg_base_directory/windows.py 2011-08-25 19:16:19 +0000
@@ -0,0 +1,119 @@
1# Authors: Manuel de la Pena <manuel@canonical.com>
2# Diego Sarmentero <diego.sarmentero@canonical.com>
3#
4# Copyright 2011 Canonical Ltd.
5#
6# This program is free software: you can redistribute it and/or modify it
7# under the terms of the GNU General Public License version 3, as published
8# by the Free Software Foundation.
9#
10# This program is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranties of
12# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
13# PURPOSE. See the GNU General Public License for more details.
14#
15# 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/>.
17
18"""XDG helpers for windows."""
19
20import os
21
22
23# pylint: disable=C0103
24def get_special_folders():
25 """ Routine to grab all the Windows Special Folders locations.
26
27 If successful, returns dictionary
28 of shell folder locations indexed on Windows keyword for each;
29 otherwise, returns an empty dictionary.
30 """
31 # pylint: disable=W0621, F0401, E0611
32 special_folders = {}
33
34 from win32com.shell import shell, shellcon
35 # CSIDL_LOCAL_APPDATA = C:\Users\<username>\AppData\Local
36 # CSIDL_PROFILE = C:\Users\<username>
37 # CSIDL_COMMON_APPDATA = C:\ProgramData
38 # More information on these at
39 # http://msdn.microsoft.com/en-us/library/bb762494(v=vs.85).aspx
40 get_path = lambda name: shell.SHGetFolderPath(
41 0, getattr(shellcon, name), None, 0).encode('utf8')
42 special_folders['Personal'] = get_path("CSIDL_PROFILE")
43 special_folders['Local AppData'] = get_path("CSIDL_LOCAL_APPDATA")
44 special_folders['AppData'] = os.path.dirname(
45 special_folders['Local AppData'])
46 special_folders['Common AppData'] = get_path("CSIDL_COMMON_APPDATA")
47 return special_folders
48
49special_folders = get_special_folders()
50
51home_path = special_folders['Personal']
52app_local_data_path = special_folders['Local AppData']
53app_global_data_path = special_folders['Common AppData']
54
55# use the non roaming app data
56xdg_data_home = os.environ.get('XDG_DATA_HOME',
57 os.path.join(app_local_data_path, 'xdg'))
58
59
60def get_data_dirs():
61 """Returns XDG data directories."""
62 return os.environ.get('XDG_DATA_DIRS',
63 '{0}{1}{2}'.format(app_local_data_path, os.pathsep,
64 app_global_data_path)).split(os.pathsep)
65
66xdg_data_dirs = get_data_dirs()
67
68# we will return the roaming data wich is as close as we get in windows
69# regarding caching.
70xdg_cache_home = os.environ.get('XDG_CACHE_HOME',
71 os.path.join(xdg_data_home, 'cache'))
72
73# point to the not roaming app data for the user
74xdg_config_home = os.environ.get('XDG_CONFIG_HOME',
75 app_local_data_path)
76
77
78def get_config_dirs():
79 """Return XDG config directories."""
80 return [xdg_config_home] + \
81 os.environ.get('XDG_CONFIG_DIRS',
82 app_global_data_path,
83 ).split(os.pathsep)
84
85xdg_config_dirs = get_config_dirs()
86
87xdg_data_dirs = filter(lambda x: x, xdg_data_dirs)
88xdg_config_dirs = filter(lambda x: x, xdg_config_dirs)
89
90
91def load_config_paths(*resource):
92 """Iterator of configuration paths.
93
94 Return an iterator which gives each directory named 'resource' in
95 the configuration search path. Information provided by earlier
96 directories should take precedence over later ones (ie, the user's
97 config dir comes first).
98 """
99 resource = os.path.join(*resource)
100 for config_dir in xdg_config_dirs:
101 path = os.path.join(config_dir, resource)
102 if os.path.exists(path):
103 yield path
104
105
106def save_config_path(*resource):
107 """Path to save configuration.
108
109 Ensure $XDG_CONFIG_HOME/<resource>/ exists, and return its path.
110 'resource' should normally be the name of your application. Use this
111 when SAVING configuration settings. Use the xdg_config_dirs variable
112 for loading.
113 """
114 resource = os.path.join(*resource)
115 assert not resource.startswith('/')
116 path = os.path.join(xdg_config_home, resource)
117 if not os.path.isdir(path):
118 os.makedirs(path, 0700)
119 return path

Subscribers

People subscribed via source and target branches

to all changes: