Merge lp:~nataliabidart/ubuntu/natty/ubuntu-sso-client/ubuntu-sso-client-1.1.12 into lp:ubuntu/natty/ubuntu-sso-client

Proposed by Natalia Bidart
Status: Merged
Merged at revision: 27
Proposed branch: lp:~nataliabidart/ubuntu/natty/ubuntu-sso-client/ubuntu-sso-client-1.1.12
Merge into: lp:ubuntu/natty/ubuntu-sso-client
Diff against target: 6817 lines (+3866/-2670)
35 files modified
PKG-INFO (+1/-1)
debian/changelog (+19/-0)
run-tests (+1/-1)
run-tests.bat (+64/-0)
setup.py (+4/-2)
ubuntu_sso/account.py (+7/-4)
ubuntu_sso/gtk/gui.py (+8/-10)
ubuntu_sso/gtk/tests/test_gui.py (+1/-6)
ubuntu_sso/keyring.py (+0/-188)
ubuntu_sso/keyring/__init__.py (+90/-0)
ubuntu_sso/keyring/linux.py (+141/-0)
ubuntu_sso/keyring/tests/__init__.py (+17/-0)
ubuntu_sso/keyring/tests/test_linux.py (+259/-0)
ubuntu_sso/keyring/tests/test_windows.py (+62/-0)
ubuntu_sso/keyring/windows.py (+60/-0)
ubuntu_sso/main.py (+0/-604)
ubuntu_sso/main/__init__.py (+375/-0)
ubuntu_sso/main/linux.py (+486/-0)
ubuntu_sso/main/tests/__init__.py (+17/-0)
ubuntu_sso/main/tests/test_common.py (+245/-0)
ubuntu_sso/main/tests/test_linux.py (+1306/-0)
ubuntu_sso/main/tests/test_windows.py (+17/-0)
ubuntu_sso/main/windows.py (+17/-0)
ubuntu_sso/networkstate.py (+0/-108)
ubuntu_sso/networkstate/__init__.py (+40/-0)
ubuntu_sso/networkstate/linux.py (+108/-0)
ubuntu_sso/networkstate/tests/__init__.py (+17/-0)
ubuntu_sso/networkstate/tests/test_linux.py (+181/-0)
ubuntu_sso/networkstate/tests/test_windows.py (+119/-0)
ubuntu_sso/networkstate/windows.py (+199/-0)
ubuntu_sso/tests/bin/show_nm_state (+1/-1)
ubuntu_sso/tests/test_account.py (+4/-2)
ubuntu_sso/tests/test_keyring.py (+0/-258)
ubuntu_sso/tests/test_main.py (+0/-1304)
ubuntu_sso/tests/test_networkstate.py (+0/-181)
To merge this branch: bzr merge lp:~nataliabidart/ubuntu/natty/ubuntu-sso-client/ubuntu-sso-client-1.1.12
Reviewer Review Type Date Requested Status
Ubuntu Sponsors Pending
Review via email: mp+54531@code.launchpad.net

Description of the change

  * New upstream release:
    [ Natalia B. Bidart <email address hidden> ]
      - Tests for a particular package are now inside that package.
      - Register now uses the 'displayname' field to pass it on to SSO as
        display name (LP: #709494).
    [ Manuel de la Pena <email address hidden> ]
      - Fix main issues.
      - First step of implementing the code in main on windows.
      - Fixed setup.py issues so that we can build .debs (LP: #735383).
      - Added the network status implementation for windows (LP: #727680).
      - Added an implementation of the keyring on windows (LP: #684967).
      - Added script to run tests on windows using u1trial (LP: #684988).
      - Added bat to run tests on windows (LP: #684988).

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
1=== modified file 'PKG-INFO'
2--- PKG-INFO 2011-02-03 15:46:51 +0000
3+++ PKG-INFO 2011-03-23 14:22:30 +0000
4@@ -1,6 +1,6 @@
5 Metadata-Version: 1.1
6 Name: ubuntu-sso-client
7-Version: 1.1.11
8+Version: 1.1.12
9 Summary: Ubuntu Single Sign-On client
10 Home-page: https://launchpad.net/ubuntu-sso-client
11 Author: Natalia Bidart
12
13=== modified file 'debian/changelog'
14--- debian/changelog 2011-02-04 08:41:02 +0000
15+++ debian/changelog 2011-03-23 14:22:30 +0000
16@@ -1,3 +1,22 @@
17+ubuntu-sso-client (1.1.12-0ubuntu1) UNRELEASED; urgency=low
18+
19+ * New upstream release:
20+
21+ [ Natalia B. Bidart <natalia.bidart@canonical.com> ]
22+ - Tests for a particular package are now inside that package.
23+ - Register now uses the 'displayname' field to pass it on to SSO as
24+ display name (LP: #709494).
25+ [ Manuel de la Pena <mandel@themacaque.com> ]
26+ - Fix main issues.
27+ - First step of implementing the code in main on windows.
28+ - Fixed setup.py issues so that we can build .debs (LP: #735383).
29+ - Added the network status implementation for windows (LP: #727680).
30+ - Added an implementation of the keyring on windows (LP: #684967).
31+ - Added script to run tests on windows using u1trial (LP: #684988).
32+ - Added bat to run tests on windows (LP: #684988).
33+
34+ -- Natalia Bidart (nessita) <nataliabidart@gmail.com> Tue, 22 Mar 2011 23:31:22 -0300
35+
36 ubuntu-sso-client (1.1.11-0ubuntu1) natty; urgency=low
37
38 * New upstream release:
39
40=== modified file 'run-tests'
41--- run-tests 2011-01-12 18:56:56 +0000
42+++ run-tests 2011-03-23 14:22:30 +0000
43@@ -33,5 +33,5 @@
44 }
45
46 echo "Running test suite for ""$MODULE"
47-`which xvfb-run` u1trial "$MODULE" && style_check
48+`which xvfb-run` u1trial "$MODULE" -i "test_windows.py" && style_check
49 rm -rf _trial_temp
50
51=== added file 'run-tests.bat'
52--- run-tests.bat 1970-01-01 00:00:00 +0000
53+++ run-tests.bat 2011-03-23 14:22:30 +0000
54@@ -0,0 +1,64 @@
55+:: Author: Manuel de la Pena <manuel@canonical.com>
56+::
57+:: Copyright 2010 Canonical Ltd.
58+::
59+:: This program is free software: you can redistribute it and/or modify it
60+:: under the terms of the GNU General Public License version 3, as published
61+:: by the Free Software Foundation.
62+::
63+:: This program is distributed in the hope that it will be useful, but
64+:: WITHOUT ANY WARRANTY; without even the implied warranties of
65+:: MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
66+:: PURPOSE. See the GNU General Public License for more details.
67+::
68+:: You should have received a copy of the GNU General Public License along
69+:: with this program. If not, see <http://www.gnu.org/licenses/>.
70+@ECHO off
71+:: We could have Python 2.6 or 2.7 on Windows. In order to check availability,
72+:: we should first check for 2.7, and run the tests, otherwise fall back to 2.6.
73+SET PYTHONPATH=""
74+:: This is very annoying; FOR /F will work differently depending on the output
75+:: of reg which is not consistent between OS versions (XP, 7). We must choose
76+:: the tokens according to OS version.
77+SET PYTHONPATHTOKENS=3
78+VER | FIND "XP" > nul
79+IF %ERRORLEVEL% == 0 SET PYTHONPATHTOKENS=4
80+ECHO Checking if python 2.7 is in the system
81+:: Look for python 2.7
82+FOR /F "tokens=%PYTHONPATHTOKENS%" %%A IN ('REG QUERY HKLM\Software\Python\PythonCore\2.7\InstallPath /ve') DO @SET PYTHONPATH=%%A
83+IF NOT %PYTHONPATH% == "" GOTO :PYTHONPRESENT
84+ECHO Checking if python 2.6 is in the system
85+:: we do not have python 2.7 in the system, try to find 2.6
86+FOR /F "tokens=%PYTHONPATHTOKENS%" %%A IN ('REG QUERY HKLM\Software\Python\PythonCore\2.6\InstallPath /ve') DO @SET PYTHONPATH=%%A
87+IF NOT %PYTHONPATH% == "" GOTO :PYTHONPRESENT
88+
89+:: we do not have python (2.6 or 2.7) this could hapen in the case that the
90+:: user installed the 32version in a 64 machine, let check if the software was installed in the wow key
91+
92+:: Look for python 2.7 in WoW64
93+ECHO Checking if python 2.7 32 is in the system
94+FOR /F "tokens=%PYTHONPATHTOKENS%" %%A IN ('REG QUERY HKLM\Software\Wow6432Node\Python\PythonCore\2.7\InstallPath /ve') DO @SET PYTHONPATH=%%A
95+IF NOT %PYTHONPATH% == "" GOTO :PYTHONPRESENT
96+ECHO Checking if python 2.6 32 is in the system
97+:: we do not have python 2.7 in the system, try to find 2.6
98+FOR /F "tokens=%PYTHONPATHTOKENS%" %%A IN ('REG QUERY HKLM\Software\Wow6432Node\Python\PythonCore\2.6\InstallPath /ve') DO @SET PYTHONPATH=%%A
99+IF NOT %PYTHONPATH% == "" GOTO :PYTHONPRESENT
100+
101+ECHO Please ensure you have python installed
102+GOTO :END
103+
104+
105+:PYTHONPRESENT
106+ECHO Python found, executing the tests...
107+:: execute the tests with a number of ignored linux only modules
108+"%PYTHONPATH%\python.exe" "%PYTHONPATH%\Scripts\u1trial" -c ubuntu_sso -i "test_gui.py, test_linux.py, test_txsecrets.py"
109+"%PYTHONPATH%\python.exe" "%PYTHONPATH%\Scripts\u1lint" ubuntu_sso
110+:: test for style if we can, if pep8 is not present, move to the end
111+IF EXIST "%PYTHONPATH%Scripts\pep8.exe"
112+"%PYTHONPATH%\Scripts\pep8.exe" --repeat ubuntu_sso
113+ELSE
114+ECHO Style checks were not done
115+:: Delete the temp folders
116+RMDIR /s /q _trial_temp
117+RMDIR /s /q .coverage
118+:END
119\ No newline at end of file
120
121=== modified file 'setup.py'
122--- setup.py 2011-02-03 15:46:51 +0000
123+++ setup.py 2011-03-23 14:22:30 +0000
124@@ -86,7 +86,7 @@
125
126 DistUtilsExtra.auto.setup(
127 name='ubuntu-sso-client',
128- version='1.1.11',
129+ version='1.1.12',
130 license='GPL v3',
131 author='Natalia Bidart',
132 author_email='natalia.bidart@canonical.com',
133@@ -94,7 +94,9 @@
134 long_description='Desktop service to allow applications to sign in' \
135 'to Ubuntu services via SSO',
136 url='https://launchpad.net/ubuntu-sso-client',
137- packages=['ubuntu_sso', 'ubuntu_sso.gtk', 'ubuntu_sso.utils'],
138+ packages=['ubuntu_sso', 'ubuntu_sso.gtk', 'ubuntu_sso.utils',
139+ 'ubuntu_sso.keyring', 'ubuntu_sso.networkstate',
140+ 'ubuntu_sso.main'],
141 data_files=[
142 ('share/dbus-1/services', ['data/com.ubuntu.sso.service']),
143 ('lib/ubuntu-sso-client', ['bin/ubuntu-sso-login']),
144
145=== modified file 'ubuntu_sso/account.py'
146--- ubuntu_sso/account.py 2010-12-16 16:31:34 +0000
147+++ ubuntu_sso/account.py 2011-03-23 14:22:30 +0000
148@@ -127,11 +127,12 @@
149
150 return captcha['captcha_id']
151
152- def register_user(self, email, password, captcha_id, captcha_solution):
153+ def register_user(self, email, password, displayname,
154+ captcha_id, captcha_solution):
155 """Register a new user with 'email' and 'password'."""
156 logger.debug('register_user: email: %r password: <hidden>, '
157- 'captcha_id: %r, captcha_solution: %r',
158- email, captcha_id, captcha_solution)
159+ 'displayname: %r, captcha_id: %r, captcha_solution: %r',
160+ email, displayname, captcha_id, captcha_solution)
161 sso_service = self.sso_service_class(None, self.service_url)
162 if not self._valid_email(email):
163 logger.error('register_user: InvalidEmailError for email: %r',
164@@ -142,7 +143,9 @@
165 raise InvalidPasswordError()
166
167 result = sso_service.registrations.register(
168- email=email, password=password, captcha_id=captcha_id,
169+ email=email, password=password,
170+ displayname=displayname,
171+ captcha_id=captcha_id,
172 captcha_solution=captcha_solution)
173 logger.info('register_user: email: %r result: %r', email, result)
174
175
176=== modified file 'ubuntu_sso/gtk/gui.py'
177--- ubuntu_sso/gtk/gui.py 2011-01-12 18:56:56 +0000
178+++ ubuntu_sso/gtk/gui.py 2011-03-23 14:22:30 +0000
179@@ -371,8 +371,6 @@
180 msg = 'UbuntuSSOClientGUI: failed set_transient_for win id %r'
181 logger.exception(msg, window_id)
182
183- # Hidding unused widgets to save some space (LP #627440).
184- self.name_entry.hide()
185 self.yes_to_updates_checkbutton.hide()
186
187 self.window.show()
188@@ -777,11 +775,10 @@
189
190 error = False
191
192- # Hidding unused widgets to save some space (LP #627440).
193- #name = self.name_entry.get_text()
194- #if not name:
195- # self.name_entry.set_warning(self.FIELD_REQUIRED)
196- # error = True
197+ name = self.name_entry.get_text()
198+ if not name:
199+ self.name_entry.set_warning(self.FIELD_REQUIRED)
200+ error = True
201
202 # check email
203 email1 = self.email1_entry.get_text()
204@@ -820,10 +817,11 @@
205 self.user_password = password1
206
207 logger.info('Calling register_user with email %r, password <hidden>,' \
208- ' captcha_id %r and captcha_solution %r.', email1,
209- self._captcha_id, captcha_solution)
210+ ' name %r, captcha_id %r and captcha_solution %r.', email1,
211+ name, self._captcha_id, captcha_solution)
212 f = self.backend.register_user
213- f(self.app_name, email1, password1, self._captcha_id, captcha_solution,
214+ f(self.app_name, email1, password1, name,
215+ self._captcha_id, captcha_solution,
216 reply_handler=NO_OP, error_handler=NO_OP)
217
218 def on_verify_token_button_clicked(self, *args, **kwargs):
219
220=== modified file 'ubuntu_sso/gtk/tests/test_gui.py'
221--- ubuntu_sso/gtk/tests/test_gui.py 2011-01-12 18:56:56 +0000
222+++ ubuntu_sso/gtk/tests/test_gui.py 2011-03-23 14:22:30 +0000
223@@ -713,7 +713,7 @@
224 expected = 'register_user'
225 self.assertIn(expected, self.ui.backend._called)
226 self.assertEqual(self.ui.backend._called[expected],
227- ((APP_NAME, EMAIL, PASSWORD, CAPTCHA_ID,
228+ ((APP_NAME, EMAIL, PASSWORD, NAME, CAPTCHA_ID,
229 CAPTCHA_SOLUTION),
230 dict(reply_handler=gui.NO_OP,
231 error_handler=gui.NO_OP)))
232@@ -1314,11 +1314,6 @@
233 self.ui.FIELD_REQUIRED)
234 self.assertNotIn('register_user', self.ui.backend._called)
235
236- # Unused variable 'skip'
237- # pylint: disable=W0612
238- test_warning_is_shown_if_name_empty.skip = \
239- 'Unused for now, will be hidden to save space (LP: #627440).'
240-
241 def test_warning_is_shown_if_empty_email(self):
242 """A warning message is shown if emails are empty."""
243 self.ui.email1_entry.set_text('')
244
245=== added directory 'ubuntu_sso/keyring'
246=== removed file 'ubuntu_sso/keyring.py'
247--- ubuntu_sso/keyring.py 2010-11-30 13:21:17 +0000
248+++ ubuntu_sso/keyring.py 1970-01-01 00:00:00 +0000
249@@ -1,188 +0,0 @@
250-# -*- coding: utf-8 -*-
251-#
252-# Copyright (C) 2010 Canonical
253-#
254-# Authors:
255-# Andrew Higginson
256-# Alejandro J. Cura <alecu@canonical.com>
257-#
258-# This program is free software; you can redistribute it and/or modify it under
259-# the terms of the GNU General Public License as published by the Free Software
260-# Foundation; version 3.
261-#
262-# This program is distributed in the hope that it will be useful, but WITHOUT
263-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
264-# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
265-# details.
266-#
267-# You should have received a copy of the GNU General Public License along with
268-# this program; if not, write to the Free Software Foundation, Inc.,
269-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
270-
271-"""Handle keys in the local kerying."""
272-
273-import socket
274-import urllib
275-import urlparse
276-
277-from twisted.internet.defer import inlineCallbacks, returnValue
278-
279-from ubuntu_sso.logger import setup_logging
280-from ubuntu_sso.utils.txsecrets import SecretService
281-
282-
283-logger = setup_logging("ubuntu_sso.keyring")
284-TOKEN_SEPARATOR = ' @ '
285-SEPARATOR_REPLACEMENT = ' AT '
286-
287-U1_APP_NAME = "Ubuntu One"
288-U1_KEY_NAME = "UbuntuOne token for https://ubuntuone.com"
289-U1_KEY_ATTR = {
290- "oauth-consumer-key": "ubuntuone",
291- "ubuntuone-realm": "https://ubuntuone.com",
292-}
293-
294-
295-def get_old_token_name(app_name):
296- """Build the token name (old style)."""
297- quoted_app_name = urllib.quote(app_name)
298- computer_name = socket.gethostname()
299- quoted_computer_name = urllib.quote(computer_name)
300- return "%s - %s" % (quoted_app_name, quoted_computer_name)
301-
302-
303-def get_token_name(app_name):
304- """Build the token name."""
305- computer_name = socket.gethostname()
306- computer_name = computer_name.replace(TOKEN_SEPARATOR,
307- SEPARATOR_REPLACEMENT)
308- return TOKEN_SEPARATOR.join((app_name, computer_name)).encode('utf-8')
309-
310-
311-class Keyring(object):
312- """A Keyring for a given application name."""
313-
314- def __init__(self):
315- """Initialize this instance."""
316- self.service = SecretService()
317-
318- @inlineCallbacks
319- def _find_keyring_item(self, app_name, attr=None):
320- """Return the keyring item or None if not found."""
321- if attr is None:
322- logger.debug("getting attr")
323- attr = self._get_keyring_attr(app_name)
324- logger.debug("finding all items")
325- items = yield self.service.search_items(attr)
326- if len(items) == 0:
327- # if no items found, return None
328- logger.debug("No items found")
329- returnValue(None)
330-
331- logger.debug("Returning first item found")
332- returnValue(items[0])
333-
334- def _get_keyring_attr(self, app_name):
335- """Build the keyring attributes for this credentials."""
336- attr = {"key-type": "Ubuntu SSO credentials",
337- "token-name": get_token_name(app_name)}
338- return attr
339-
340- @inlineCallbacks
341- def set_credentials(self, app_name, cred):
342- """Set the credentials of the Ubuntu SSO item."""
343- # Creates the secret from the credentials
344- secret = urllib.urlencode(cred)
345-
346- attr = self._get_keyring_attr(app_name)
347- # Add our SSO credentials to the keyring
348- yield self.service.open_session()
349- collection = yield self.service.get_default_collection()
350- yield collection.create_item(app_name, attr, secret, True)
351-
352- @inlineCallbacks
353- def _migrate_old_token_name(self, app_name):
354- """Migrate credentials with old name, store them with new name."""
355- logger.debug("getting keyring attr")
356- attr = self._get_keyring_attr(app_name)
357- logger.debug("getting old token name")
358- attr['token-name'] = get_old_token_name(app_name)
359- logger.debug("finding keyring item")
360- item = yield self._find_keyring_item(app_name, attr=attr)
361- if item is not None:
362- logger.debug("setting credentials")
363- yield self.set_credentials(app_name,
364- dict(urlparse.parse_qsl(item.secret)))
365- logger.debug("deleting old item")
366- yield item.delete()
367-
368- logger.debug("finding keyring item")
369- result = yield self._find_keyring_item(app_name)
370- logger.debug("returning result value")
371- returnValue(result)
372-
373- @inlineCallbacks
374- def get_credentials(self, app_name):
375- """A deferred with the secret of the SSO item in a dictionary."""
376- # If we have no attributes, return None
377- logger.debug("getting credentials")
378- yield self.service.open_session()
379- logger.debug("calling find item")
380- item = yield self._find_keyring_item(app_name)
381- if item is None:
382- logger.debug("migrating token")
383- item = yield self._migrate_old_token_name(app_name)
384-
385- if item is not None:
386- logger.debug("parsing secret")
387- secret = yield item.get_value()
388- returnValue(dict(urlparse.parse_qsl(secret)))
389- else:
390- # if no item found, try getting the old credentials
391- if app_name == U1_APP_NAME:
392- logger.debug("trying old credentials")
393- old_creds = yield try_old_credentials(app_name)
394- returnValue(old_creds)
395- # nothing was found
396- returnValue(None)
397-
398- @inlineCallbacks
399- def delete_credentials(self, app_name):
400- """Delete a set of credentials from the keyring."""
401- attr = self._get_keyring_attr(app_name)
402- # Add our SSO credentials to the keyring
403- yield self.service.open_session()
404- collection = yield self.service.get_default_collection()
405- yield collection.create_item(app_name, attr, "secret!", True)
406-
407- item = yield self._find_keyring_item(app_name)
408- if item is not None:
409- yield item.delete()
410-
411-
412-class UbuntuOneOAuthKeyring(Keyring):
413- """A particular Keyring for Ubuntu One."""
414-
415- def _get_keyring_attr(self, app_name):
416- """Build the keyring attributes for this credentials."""
417- return U1_KEY_ATTR
418-
419-
420-@inlineCallbacks
421-def try_old_credentials(app_name):
422- """Try to get old U1 credentials and format them as new."""
423- logger.debug('trying to get old credentials.')
424- old_creds = yield UbuntuOneOAuthKeyring().get_credentials(U1_KEY_NAME)
425- if old_creds is not None:
426- # Old creds found, build a new credentials dict with them
427- creds = {
428- 'consumer_key': "ubuntuone",
429- 'consumer_secret': "hammertime",
430- 'name': U1_KEY_NAME,
431- 'token': old_creds["oauth_token"],
432- 'token_secret': old_creds["oauth_token_secret"],
433- }
434- logger.debug('found old credentials')
435- returnValue(creds)
436- logger.debug('try_old_credentials: No old credentials for this app.')
437- returnValue(None)
438
439=== added file 'ubuntu_sso/keyring/__init__.py'
440--- ubuntu_sso/keyring/__init__.py 1970-01-01 00:00:00 +0000
441+++ ubuntu_sso/keyring/__init__.py 2011-03-23 14:22:30 +0000
442@@ -0,0 +1,90 @@
443+# -*- coding: utf-8 -*-
444+# Authors:
445+# Andrew Higginson
446+# Alejandro J. Cura <alecu@canonical.com>
447+# Manuel de la Pena <manuel@canonical.com>
448+#
449+# Copyright 2011 Canonical Ltd.
450+#
451+# This program is free software: you can redistribute it and/or modify it
452+# under the terms of the GNU General Public License version 3, as published
453+# by the Free Software Foundation.
454+#
455+# This program is distributed in the hope that it will be useful, but
456+# WITHOUT ANY WARRANTY; without even the implied warranties of
457+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
458+# PURPOSE. See the GNU General Public License for more details.
459+#
460+# You should have received a copy of the GNU General Public License along
461+# with this program. If not, see <http://www.gnu.org/licenses/>.
462+"""Implementations of different keyrings."""
463+
464+import socket
465+import sys
466+import urllib
467+
468+from twisted.internet.defer import inlineCallbacks, returnValue
469+
470+from ubuntu_sso.logger import setup_logging
471+
472+logger = setup_logging("ubuntu_sso.keyring")
473+
474+TOKEN_SEPARATOR = ' @ '
475+SEPARATOR_REPLACEMENT = ' AT '
476+
477+U1_APP_NAME = "Ubuntu One"
478+U1_KEY_NAME = "UbuntuOne token for https://ubuntuone.com"
479+U1_KEY_ATTR = {
480+ "oauth-consumer-key": "ubuntuone",
481+ "ubuntuone-realm": "https://ubuntuone.com",
482+}
483+
484+
485+def get_old_token_name(app_name):
486+ """Build the token name (old style)."""
487+ quoted_app_name = urllib.quote(app_name)
488+ computer_name = socket.gethostname()
489+ quoted_computer_name = urllib.quote(computer_name)
490+ return "%s - %s" % (quoted_app_name, quoted_computer_name)
491+
492+
493+def get_token_name(app_name):
494+ """Build the token name."""
495+ computer_name = socket.gethostname()
496+ computer_name = computer_name.replace(TOKEN_SEPARATOR,
497+ SEPARATOR_REPLACEMENT)
498+ return TOKEN_SEPARATOR.join((app_name, computer_name)).encode('utf-8')
499+
500+
501+@inlineCallbacks
502+def try_old_credentials(app_name):
503+ """Try to get old U1 credentials and format them as new."""
504+ logger.debug('trying to get old credentials.')
505+ old_creds = yield UbuntuOneOAuthKeyring().get_credentials(U1_KEY_NAME)
506+ if old_creds is not None:
507+ # Old creds found, build a new credentials dict with them
508+ creds = {
509+ 'consumer_key': "ubuntuone",
510+ 'consumer_secret': "hammertime",
511+ 'name': U1_KEY_NAME,
512+ 'token': old_creds["oauth_token"],
513+ 'token_secret': old_creds["oauth_token_secret"],
514+ }
515+ logger.debug('found old credentials')
516+ returnValue(creds)
517+ logger.debug('try_old_credentials: No old credentials for this app.')
518+ returnValue(None)
519+
520+
521+if sys.platform == 'win32':
522+ from ubuntu_sso.keyring.windows import Keyring
523+else:
524+ from ubuntu_sso.keyring.linux import Keyring
525+
526+
527+class UbuntuOneOAuthKeyring(Keyring):
528+ """A particular Keyring for Ubuntu One."""
529+
530+ def _get_keyring_attr(self, app_name):
531+ """Build the keyring attributes for this credentials."""
532+ return U1_KEY_ATTR
533
534=== added file 'ubuntu_sso/keyring/linux.py'
535--- ubuntu_sso/keyring/linux.py 1970-01-01 00:00:00 +0000
536+++ ubuntu_sso/keyring/linux.py 2011-03-23 14:22:30 +0000
537@@ -0,0 +1,141 @@
538+# -*- coding: utf-8 -*-
539+#
540+# Copyright (C) 2010 Canonical
541+#
542+# Authors:
543+# Andrew Higginson
544+# Alejandro J. Cura <alecu@canonical.com>
545+# Natalia B. Bidart <natalia.bidart@canonical.com>
546+# Manuel de la Pena <manuel@canonical.com>
547+#
548+# This program is free software; you can redistribute it and/or modify it under
549+# the terms of the GNU General Public License as published by the Free Software
550+# Foundation; version 3.
551+#
552+# This program is distributed in the hope that it will be useful, but WITHOUT
553+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
554+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
555+# details.
556+#
557+# You should have received a copy of the GNU General Public License along with
558+# this program; if not, write to the Free Software Foundation, Inc.,
559+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
560+
561+"""Handle keys in the local kerying."""
562+
563+import urllib
564+import urlparse
565+
566+from twisted.internet.defer import inlineCallbacks, returnValue
567+
568+from ubuntu_sso.logger import setup_logging
569+from ubuntu_sso.utils.txsecrets import SecretService
570+from ubuntu_sso.keyring import (
571+ get_token_name,
572+ get_old_token_name,
573+ U1_APP_NAME,
574+ try_old_credentials)
575+
576+
577+logger = setup_logging("ubuntu_sso.keyring")
578+
579+
580+class Keyring(object):
581+ """A Keyring for a given application name."""
582+
583+ def __init__(self):
584+ """Initialize this instance."""
585+ self.service = SecretService()
586+
587+ @inlineCallbacks
588+ def _find_keyring_item(self, app_name, attr=None):
589+ """Return the keyring item or None if not found."""
590+ if attr is None:
591+ logger.debug("getting attr")
592+ attr = self._get_keyring_attr(app_name)
593+ logger.debug("finding all items")
594+ items = yield self.service.search_items(attr)
595+ if len(items) == 0:
596+ # if no items found, return None
597+ logger.debug("No items found")
598+ returnValue(None)
599+
600+ logger.debug("Returning first item found")
601+ returnValue(items[0])
602+
603+ def _get_keyring_attr(self, app_name):
604+ """Build the keyring attributes for this credentials."""
605+ attr = {"key-type": "Ubuntu SSO credentials",
606+ "token-name": get_token_name(app_name)}
607+ return attr
608+
609+ @inlineCallbacks
610+ def set_credentials(self, app_name, cred):
611+ """Set the credentials of the Ubuntu SSO item."""
612+ # Creates the secret from the credentials
613+ secret = urllib.urlencode(cred)
614+
615+ attr = self._get_keyring_attr(app_name)
616+ # Add our SSO credentials to the keyring
617+ yield self.service.open_session()
618+ collection = yield self.service.get_default_collection()
619+ yield collection.create_item(app_name, attr, secret, True)
620+
621+ @inlineCallbacks
622+ def _migrate_old_token_name(self, app_name):
623+ """Migrate credentials with old name, store them with new name."""
624+ logger.debug("getting keyring attr")
625+ attr = self._get_keyring_attr(app_name)
626+ logger.debug("getting old token name")
627+ attr['token-name'] = get_old_token_name(app_name)
628+ logger.debug("finding keyring item")
629+ item = yield self._find_keyring_item(app_name, attr=attr)
630+ if item is not None:
631+ logger.debug("setting credentials")
632+ yield self.set_credentials(app_name,
633+ dict(urlparse.parse_qsl(item.secret)))
634+ logger.debug("deleting old item")
635+ yield item.delete()
636+
637+ logger.debug("finding keyring item")
638+ result = yield self._find_keyring_item(app_name)
639+ logger.debug("returning result value")
640+ returnValue(result)
641+
642+ @inlineCallbacks
643+ def get_credentials(self, app_name):
644+ """A deferred with the secret of the SSO item in a dictionary."""
645+ # If we have no attributes, return None
646+ logger.debug("getting credentials")
647+ yield self.service.open_session()
648+ logger.debug("calling find item")
649+ item = yield self._find_keyring_item(app_name)
650+ if item is None:
651+ logger.debug("migrating token")
652+ item = yield self._migrate_old_token_name(app_name)
653+
654+ if item is not None:
655+ logger.debug("parsing secret")
656+ secret = yield item.get_value()
657+ returnValue(dict(urlparse.parse_qsl(secret)))
658+ else:
659+ # if no item found, try getting the old credentials
660+ if app_name == U1_APP_NAME:
661+ logger.debug("trying old credentials")
662+ old_creds = yield try_old_credentials(app_name)
663+ returnValue(old_creds)
664+ # nothing was found
665+ returnValue(None)
666+
667+ @inlineCallbacks
668+ def delete_credentials(self, app_name):
669+ """Delete a set of credentials from the keyring."""
670+ attr = self._get_keyring_attr(app_name)
671+ # Add our SSO credentials to the keyring
672+ yield self.service.open_session()
673+ collection = yield self.service.get_default_collection()
674+ yield collection.create_item(app_name, attr, "secret!", True)
675+
676+ item = yield self._find_keyring_item(app_name)
677+ if item is not None:
678+ yield item.delete()
679
680=== added directory 'ubuntu_sso/keyring/tests'
681=== added file 'ubuntu_sso/keyring/tests/__init__.py'
682--- ubuntu_sso/keyring/tests/__init__.py 1970-01-01 00:00:00 +0000
683+++ ubuntu_sso/keyring/tests/__init__.py 2011-03-23 14:22:30 +0000
684@@ -0,0 +1,17 @@
685+# -*- coding: utf-8 -*-
686+# Author: Manuel de la Pena <manuel@canonical.com>
687+#
688+# Copyright 2011 Canonical Ltd.
689+#
690+# This program is free software: you can redistribute it and/or modify it
691+# under the terms of the GNU General Public License version 3, as published
692+# by the Free Software Foundation.
693+#
694+# This program is distributed in the hope that it will be useful, but
695+# WITHOUT ANY WARRANTY; without even the implied warranties of
696+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
697+# PURPOSE. See the GNU General Public License for more details.
698+#
699+# You should have received a copy of the GNU General Public License along
700+# with this program. If not, see <http://www.gnu.org/licenses/>.
701+"""Keyring tests."""
702
703=== added file 'ubuntu_sso/keyring/tests/test_linux.py'
704--- ubuntu_sso/keyring/tests/test_linux.py 1970-01-01 00:00:00 +0000
705+++ ubuntu_sso/keyring/tests/test_linux.py 2011-03-23 14:22:30 +0000
706@@ -0,0 +1,259 @@
707+# -*- coding: utf-8 -*-
708+#
709+# test_keyring - tests for ubuntu_sso.keyring
710+#
711+# Author: Alejandro J. Cura <alecu@canonical.com>
712+# Author: Natalia B. Bidart <natalia.bidart@canonical.com>
713+#
714+# Copyright 2010 Canonical Ltd.
715+#
716+# This program is free software: you can redistribute it and/or modify it
717+# under the terms of the GNU General Public License version 3, as published
718+# by the Free Software Foundation.
719+#
720+# This program is distributed in the hope that it will be useful, but
721+# WITHOUT ANY WARRANTY; without even the implied warranties of
722+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
723+# PURPOSE. See the GNU General Public License for more details.
724+#
725+# You should have received a copy of the GNU General Public License along
726+# with this program. If not, see <http://www.gnu.org/licenses/>.
727+"""Tests for the keyring.py module."""
728+
729+import socket
730+
731+from twisted.internet import defer
732+from twisted.internet.defer import inlineCallbacks
733+from twisted.trial.unittest import TestCase
734+
735+from ubuntu_sso import keyring as common_keyring
736+from ubuntu_sso.keyring import linux as keyring
737+from ubuntu_sso.tests import APP_NAME
738+
739+
740+def build_fake_gethostname(fake_hostname):
741+ """Return a fake hostname getter."""
742+ return lambda *a: fake_hostname
743+
744+
745+class MockItem(object):
746+ """An item contains a secret, lookup attributes and has a label."""
747+
748+ def __init__(self, label, collection, attr, value):
749+ """Initialize a new Item."""
750+ self.label = label
751+ self.collection = collection
752+ self.attributes = attr
753+ self.value = value
754+
755+ def get_value(self):
756+ """Retrieve the secret for this item."""
757+ return defer.succeed(self.value)
758+
759+ def delete(self):
760+ """Delete this item."""
761+ self.collection.items.remove(self)
762+ return defer.succeed(None)
763+
764+ def matches(self, search_attr):
765+ """See if this item matches a given search."""
766+ for k, val in search_attr.items():
767+ if k not in self.attributes:
768+ return False
769+ if self.attributes[k] != val:
770+ return False
771+ return True
772+
773+
774+class MockCollection(object):
775+ """A collection of items containing secrets."""
776+
777+ def __init__(self, label, service):
778+ """Initialize a new collection."""
779+ self.label = label
780+ self.service = service
781+ self.items = []
782+
783+ def create_item(self, label, attr, value, replace=True):
784+ """Create an item with the given attributes, secret and label."""
785+ item = MockItem(label, self, attr, value)
786+ self.items.append(item)
787+ return defer.succeed(item)
788+
789+
790+class MockSecretService(object):
791+ """A class that mocks txsecrets.SecretService."""
792+
793+ def __init__(self, *args, **kwargs):
794+ super(MockSecretService, self).__init__(*args, **kwargs)
795+ self.collections = {}
796+
797+ def open_session(self, window_id=0):
798+ """Open a unique session for the caller application."""
799+ return defer.succeed(self)
800+
801+ def search_items(self, attributes):
802+ """Find items in any collection."""
803+ results = []
804+ for collection in self.collections.values():
805+ for item in collection.items:
806+ if item.matches(attributes):
807+ results.append(item)
808+ return defer.succeed(results)
809+
810+ def create_collection(self, label):
811+ """Create a new collection with the specified properties."""
812+ collection = MockCollection(label, self)
813+ self.collections[label] = collection
814+ if "default" not in self.collections:
815+ self.collections["default"] = collection
816+ return defer.succeed(collection)
817+
818+ def get_default_collection(self):
819+ """The collection were default items should be created."""
820+ if len(self.collections) == 0:
821+ self.create_collection("default")
822+ return defer.succeed(self.collections["default"])
823+
824+
825+class TestTokenNameBuilder(TestCase):
826+ """Test the method that builds the token name."""
827+
828+ def test_get_simple_token_name(self):
829+ """A simple token name is built right."""
830+ sample_app_name = "UbuntuTwo"
831+ sample_hostname = "Darkstar"
832+ expected_result = "UbuntuTwo @ Darkstar"
833+
834+ fake_gethostname = build_fake_gethostname(sample_hostname)
835+ self.patch(socket, "gethostname", fake_gethostname)
836+ result = keyring.get_token_name(sample_app_name)
837+ self.assertEqual(result, expected_result)
838+
839+ def test_get_complex_token_name_for_app_name(self):
840+ """A complex token name is built right too."""
841+ sample_app_name = "Ubuntu @ Eleven"
842+ sample_hostname = "Mate+Cocido"
843+ expected_result = "Ubuntu @ Eleven @ Mate+Cocido"
844+
845+ fake_gethostname = build_fake_gethostname(sample_hostname)
846+ self.patch(socket, "gethostname", fake_gethostname)
847+ result = keyring.get_token_name(sample_app_name)
848+ self.assertEqual(result, expected_result)
849+
850+ def test_get_complex_token_name_for_hostname(self):
851+ """A complex token name is built right too."""
852+ sample_app_name = "Ubuntu Eleven"
853+ sample_hostname = "Mate @ Cocido"
854+ expected_result = "Ubuntu Eleven @ Mate AT Cocido"
855+
856+ fake_gethostname = build_fake_gethostname(sample_hostname)
857+ self.patch(socket, "gethostname", fake_gethostname)
858+ result = keyring.get_token_name(sample_app_name)
859+ self.assertEqual(result, expected_result)
860+
861+
862+class TestKeyring(TestCase):
863+ """Test the keyring related functions."""
864+
865+ timeout = 5
866+
867+ def setUp(self):
868+ """Initialize the mock used in these tests."""
869+ self.mock_service = None
870+ self.service = self.patch(keyring, "SecretService",
871+ self.get_mock_service)
872+ fake_gethostname = build_fake_gethostname("darkstar")
873+ self.patch(socket, "gethostname", fake_gethostname)
874+
875+ def get_mock_service(self):
876+ """Create only one instance of the mock service per test."""
877+ if self.mock_service == None:
878+ self.mock_service = MockSecretService()
879+ return self.mock_service
880+
881+ @inlineCallbacks
882+ def test_set_credentials(self):
883+ """Test that the set method does not erase previous keys."""
884+ sample_creds = {"name": "sample creds name"}
885+ sample_creds2 = {"name": "sample creds name 2"}
886+ kr = keyring.Keyring()
887+ yield kr.set_credentials("appname", sample_creds)
888+ yield kr.set_credentials("appname", sample_creds2)
889+
890+ # pylint: disable=E1101
891+ self.assertEqual(len(kr.service.collections["default"].items), 2)
892+
893+ @inlineCallbacks
894+ def test_delete_credentials(self):
895+ """Test that a given key is deleted."""
896+ sample_creds = {"name": "sample creds name"}
897+ kr = keyring.Keyring()
898+ yield kr.set_credentials("appname", sample_creds)
899+ yield kr.delete_credentials("appname")
900+
901+ # pylint: disable=E1101
902+ self.assertEqual(len(kr.service.collections["default"].items), 1)
903+
904+ @inlineCallbacks
905+ def test_get_credentials(self):
906+ """Test that credentials are properly retrieved."""
907+ sample_creds = {"name": "sample creds name"}
908+ kr = keyring.Keyring()
909+ yield kr.set_credentials("appname", sample_creds)
910+
911+ result = yield kr.get_credentials("appname")
912+ self.assertEqual(result, sample_creds)
913+
914+ @inlineCallbacks
915+ def test_get_credentials_migrating_token(self):
916+ """Test that credentials are properly retrieved and migrated."""
917+ sample_creds = {"name": "sample creds name"}
918+ kr = keyring.Keyring()
919+ self.patch(keyring, "get_token_name", keyring.get_old_token_name)
920+ yield kr.set_credentials(APP_NAME, sample_creds)
921+
922+ result = yield kr.get_credentials(APP_NAME)
923+ self.assertEqual(result, sample_creds)
924+
925+ @inlineCallbacks
926+ def test_get_old_cred_found(self):
927+ """The method returns a new set of creds if old creds are found."""
928+ sample_oauth_token = "sample oauth token"
929+ sample_oauth_secret = "sample oauth secret"
930+ old_creds = {
931+ "oauth_token": sample_oauth_token,
932+ "oauth_token_secret": sample_oauth_secret,
933+ }
934+ u1kr = common_keyring.UbuntuOneOAuthKeyring()
935+ yield u1kr.set_credentials(keyring.U1_APP_NAME, old_creds)
936+
937+ kr = keyring.Keyring()
938+ result = yield kr.get_credentials(keyring.U1_APP_NAME)
939+ self.assertIn("token", result)
940+ self.assertEqual(result["token"], sample_oauth_token)
941+ self.assertIn("token_secret", result)
942+ self.assertEqual(result["token_secret"], sample_oauth_secret)
943+
944+ @inlineCallbacks
945+ def test_get_old_cred_found_but_not_asked_for(self):
946+ """Returns None if old creds are present but the appname is not U1"""
947+ sample_oauth_token = "sample oauth token"
948+ sample_oauth_secret = "sample oauth secret"
949+ old_creds = {
950+ "oauth_token": sample_oauth_token,
951+ "oauth_token_secret": sample_oauth_secret,
952+ }
953+ u1kr = common_keyring.UbuntuOneOAuthKeyring()
954+ yield u1kr.set_credentials(keyring.U1_APP_NAME, old_creds)
955+
956+ kr = keyring.Keyring()
957+ result = yield kr.get_credentials("Software Center")
958+ self.assertEqual(result, None)
959+
960+ @inlineCallbacks
961+ def test_get_old_cred_not_found(self):
962+ """The method returns None if no old nor new credentials found."""
963+ kr = keyring.Keyring()
964+ result = yield kr.get_credentials(keyring.U1_APP_NAME)
965+ self.assertEqual(result, None)
966
967=== added file 'ubuntu_sso/keyring/tests/test_windows.py'
968--- ubuntu_sso/keyring/tests/test_windows.py 1970-01-01 00:00:00 +0000
969+++ ubuntu_sso/keyring/tests/test_windows.py 2011-03-23 14:22:30 +0000
970@@ -0,0 +1,62 @@
971+# -*- coding: utf-8 -*-
972+# Author: Manuel de la Pena <manuel@canonical.com>
973+#
974+# Copyright 2011 Canonical Ltd.
975+#
976+# This program is free software: you can redistribute it and/or modify it
977+# under the terms of the GNU General Public License version 3, as published
978+# by the Free Software Foundation.
979+#
980+# This program is distributed in the hope that it will be useful, but
981+# WITHOUT ANY WARRANTY; without even the implied warranties of
982+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
983+# PURPOSE. See the GNU General Public License for more details.
984+#
985+# You should have received a copy of the GNU General Public License along
986+# with this program. If not, see <http://www.gnu.org/licenses/>.
987+"""Test the windows keyring implementation."""
988+
989+from json import dumps
990+from mocker import MockerTestCase
991+from twisted.internet.defer import inlineCallbacks
992+
993+from ubuntu_sso.keyring.windows import Keyring, USERNAME
994+
995+
996+class TestWindowsKeyring(MockerTestCase):
997+ """Test the windows keyring implementation."""
998+
999+ def setUp(self):
1000+ """Setup tests."""
1001+ super(TestWindowsKeyring, self).setUp()
1002+ self.keyring_lib = self.mocker.mock()
1003+ self.keyring = Keyring(self.keyring_lib)
1004+
1005+ @inlineCallbacks
1006+ def test_set_credentials(self):
1007+ """Test setting the credentials."""
1008+ app_name = 'name'
1009+ password = dict(password='password')
1010+ self.keyring_lib.set_password(app_name, USERNAME, dumps(password))
1011+ self.mocker.replay()
1012+ yield self.keyring.set_credentials(app_name, password)
1013+
1014+ @inlineCallbacks
1015+ def test_get_credentials(self):
1016+ """Test deleting the credentials."""
1017+ app_name = 'name'
1018+ password = dict(password='password')
1019+ self.keyring_lib.get_password(app_name, USERNAME)
1020+ self.mocker.result(dumps(password))
1021+ self.mocker.replay()
1022+ result = yield self.keyring.get_credentials(app_name)
1023+ self.assertEqual(password, result)
1024+
1025+ @inlineCallbacks
1026+ def test_delete_credentials(self):
1027+ """Test deleting the credentials."""
1028+ app_name = 'name'
1029+ self.keyring_lib.delete_password(app_name, USERNAME)
1030+ self.mocker.replay()
1031+ result = yield self.keyring.delete_credentials(app_name)
1032+ self.assertTrue(result is None)
1033
1034=== added file 'ubuntu_sso/keyring/windows.py'
1035--- ubuntu_sso/keyring/windows.py 1970-01-01 00:00:00 +0000
1036+++ ubuntu_sso/keyring/windows.py 2011-03-23 14:22:30 +0000
1037@@ -0,0 +1,60 @@
1038+# -*- coding: utf-8 -*-
1039+# Author: Manuel de la Pena <manuel@canonical.com>
1040+#
1041+# Copyright 2011 Canonical Ltd.
1042+#
1043+# This program is free software: you can redistribute it and/or modify it
1044+# under the terms of the GNU General Public License version 3, as published
1045+# by the Free Software Foundation.
1046+#
1047+# This program is distributed in the hope that it will be useful, but
1048+# WITHOUT ANY WARRANTY; without even the implied warranties of
1049+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1050+# PURPOSE. See the GNU General Public License for more details.
1051+#
1052+# You should have received a copy of the GNU General Public License along
1053+# with this program. If not, see <http://www.gnu.org/licenses/>.
1054+"""Keyring implementation on Windows."""
1055+
1056+from json import loads, dumps
1057+
1058+from twisted.internet.threads import deferToThread
1059+
1060+USERNAME = 'ubuntu_sso'
1061+
1062+
1063+class Keyring(object):
1064+ """A Keyring for a given application name."""
1065+
1066+ def __init__(self, keyring=None):
1067+ """Create a new instance."""
1068+ if keyring is None:
1069+ import keyring as pykeyring
1070+ keyring = pykeyring
1071+ self.keyring = keyring
1072+
1073+ def set_credentials(self, app_name, cred):
1074+ """Set the credentials of the Ubuntu SSO item."""
1075+ # the windows keyring can only store a pair username-password
1076+ # so we store the data using ubuntu_sso as the user name. Then
1077+ # the cred will be stored as the string representation of the dict.
1078+ return deferToThread(self.keyring.set_password, app_name, USERNAME,
1079+ dumps(cred))
1080+
1081+ def _get_credentials_obj(self, app_name):
1082+ """A dict with the credentials."""
1083+ creds = self.keyring.get_password(app_name, USERNAME)
1084+ return loads(creds)
1085+
1086+ def get_credentials(self, app_name):
1087+ """A deferred with the secret of the SSO item in a dictionary."""
1088+ return deferToThread(self._get_credentials_obj, app_name)
1089+
1090+ def delete_credentials(self, app_name):
1091+ """Delete a set of credentials from the keyring."""
1092+ # this call depends on a patch I sent to pykeyring. The patch has
1093+ # not landed as of version 0.5.1. If you have that version you can
1094+ # clone my patch in the following way:
1095+ # hg clone https://bitbucket.org/mandel/pykeyring-delete-password
1096+ # pylint: disable=E1103
1097+ return deferToThread(self.keyring.delete_password, app_name, USERNAME)
1098
1099=== added directory 'ubuntu_sso/main'
1100=== removed file 'ubuntu_sso/main.py'
1101--- ubuntu_sso/main.py 2011-01-28 16:38:26 +0000
1102+++ ubuntu_sso/main.py 1970-01-01 00:00:00 +0000
1103@@ -1,604 +0,0 @@
1104-# -*- coding: utf-8 -*-
1105-#
1106-# ubuntu_sso.main - main login handling interface
1107-#
1108-# Author: Natalia Bidart <natalia.bidart@canonical.com>
1109-# Author: Alejandro J. Cura <alecu@canonical.com>
1110-#
1111-# Copyright 2009 Canonical Ltd.
1112-#
1113-# This program is free software: you can redistribute it and/or modify it
1114-# under the terms of the GNU General Public License version 3, as published
1115-# by the Free Software Foundation.
1116-#
1117-# This program is distributed in the hope that it will be useful, but
1118-# WITHOUT ANY WARRANTY; without even the implied warranties of
1119-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1120-# PURPOSE. See the GNU General Public License for more details.
1121-#
1122-# You should have received a copy of the GNU General Public License along
1123-# with this program. If not, see <http://www.gnu.org/licenses/>.
1124-"""Single Sign On login handler.
1125-
1126-An utility which accepts requests for Ubuntu Single Sign On login over D-Bus.
1127-
1128-The OAuth process is handled, including adding the OAuth access token to the
1129-local keyring.
1130-
1131-"""
1132-
1133-import os
1134-import threading
1135-import warnings
1136-
1137-import dbus.service
1138-
1139-from ubuntu_sso import (DBUS_ACCOUNT_PATH, DBUS_IFACE_USER_NAME,
1140- DBUS_IFACE_CRED_NAME, DBUS_CREDENTIALS_IFACE, NO_OP)
1141-from ubuntu_sso.account import Account
1142-from ubuntu_sso.credentials import (Credentials, HELP_TEXT_KEY, PING_URL_KEY,
1143- TC_URL_KEY, UI_CLASS_KEY, UI_MODULE_KEY, WINDOW_ID_KEY,
1144- SUCCESS_CB_KEY, ERROR_CB_KEY, DENIAL_CB_KEY,
1145- ERROR_KEY, ERROR_DETAIL_KEY)
1146-from ubuntu_sso.keyring import get_token_name, U1_APP_NAME, Keyring
1147-from ubuntu_sso.logger import setup_logging
1148-
1149-
1150-# Disable the invalid name warning, as we have a lot of DBus style names
1151-# pylint: disable=C0103
1152-
1153-
1154-logger = setup_logging("ubuntu_sso.main")
1155-U1_PING_URL = "https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/"
1156-TIMEOUT_INTERVAL = 10000 # 10 seconds
1157-
1158-
1159-class SSOLoginProcessor(Account):
1160- """Login and register users using the Ubuntu Single Sign On service.
1161-
1162- Alias classname to maintain backwards compatibility. DO NOT USE, use
1163- ubuntu_sso.account.Account instead.
1164- """
1165-
1166- def __init__(self, sso_service_class=None):
1167- """Create a new SSO Account manager."""
1168- msg = 'Use ubuntu_sso.account.Account instead.'
1169- warnings.warn(msg, DeprecationWarning)
1170- super(SSOLoginProcessor, self).__init__(sso_service_class)
1171-
1172-
1173-def except_to_errdict(e):
1174- """Turn an exception into a dictionary to return thru DBus."""
1175- result = {
1176- "errtype": e.__class__.__name__,
1177- }
1178- if len(e.args) == 0:
1179- result["message"] = e.__class__.__doc__
1180- elif isinstance(e.args[0], dict):
1181- result.update(e.args[0])
1182- elif isinstance(e.args[0], basestring):
1183- result["message"] = e.args[0]
1184-
1185- return result
1186-
1187-
1188-def blocking(f, app_name, result_cb, error_cb):
1189- """Run f in a thread; return or throw an exception thru the callbacks."""
1190- def _in_thread():
1191- """The part that runs inside the thread."""
1192- try:
1193- result_cb(app_name, f())
1194- except Exception, e: # pylint: disable=W0703
1195- msg = "Exception while running DBus blocking code in a thread:"
1196- logger.exception(msg)
1197- error_cb(app_name, except_to_errdict(e))
1198- threading.Thread(target=_in_thread).start()
1199-
1200-
1201-class SSOLogin(dbus.service.Object):
1202- """Login thru the Single Sign On service."""
1203-
1204- # Operator not preceded by a space (fails with dbus decorators)
1205- # pylint: disable=C0322
1206-
1207- def __init__(self, bus_name, object_path=DBUS_ACCOUNT_PATH,
1208- sso_login_processor_class=Account,
1209- sso_service_class=None):
1210- """Initiate the Login object."""
1211- dbus.service.Object.__init__(self, object_path=object_path,
1212- bus_name=bus_name)
1213- self.sso_login_processor_class = sso_login_processor_class
1214- self.processor = self.sso_login_processor_class(
1215- sso_service_class=sso_service_class)
1216-
1217- # generate_capcha signals
1218- @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
1219- def CaptchaGenerated(self, app_name, result):
1220- """Signal thrown after the captcha is generated."""
1221- logger.debug('SSOLogin: emitting CaptchaGenerated with app_name "%s" '
1222- 'and result %r', app_name, result)
1223-
1224- @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="sa{ss}")
1225- def CaptchaGenerationError(self, app_name, error):
1226- """Signal thrown when there's a problem generating the captcha."""
1227- logger.debug('SSOLogin: emitting CaptchaGenerationError with '
1228- 'app_name "%s" and error %r', app_name, error)
1229-
1230- @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME,
1231- in_signature='ss')
1232- def generate_captcha(self, app_name, filename):
1233- """Call the matching method in the processor."""
1234- def f():
1235- """Inner function that will be run in a thread."""
1236- return self.processor.generate_captcha(filename)
1237- blocking(f, app_name, self.CaptchaGenerated,
1238- self.CaptchaGenerationError)
1239-
1240- # register_user signals
1241- @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
1242- def UserRegistered(self, app_name, result):
1243- """Signal thrown when the user is registered."""
1244- logger.debug('SSOLogin: emitting UserRegistered with app_name "%s" '
1245- 'and result %r', app_name, result)
1246-
1247- @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="sa{ss}")
1248- def UserRegistrationError(self, app_name, error):
1249- """Signal thrown when there's a problem registering the user."""
1250- logger.debug('SSOLogin: emitting UserRegistrationError with '
1251- 'app_name "%s" and error %r', app_name, error)
1252-
1253- @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME,
1254- in_signature='sssss')
1255- def register_user(self, app_name, email, password,
1256- captcha_id, captcha_solution):
1257- """Call the matching method in the processor."""
1258- def f():
1259- """Inner function that will be run in a thread."""
1260- return self.processor.register_user(email, password,
1261- captcha_id, captcha_solution)
1262- blocking(f, app_name, self.UserRegistered, self.UserRegistrationError)
1263-
1264- # login signals
1265- @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
1266- def LoggedIn(self, app_name, result):
1267- """Signal thrown when the user is logged in."""
1268- logger.debug('SSOLogin: emitting LoggedIn with app_name "%s" '
1269- 'and result %r', app_name, result)
1270-
1271- @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="sa{ss}")
1272- def LoginError(self, app_name, error):
1273- """Signal thrown when there is a problem in the login."""
1274- logger.debug('SSOLogin: emitting LoginError with '
1275- 'app_name "%s" and error %r', app_name, error)
1276-
1277- @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
1278- def UserNotValidated(self, app_name, result):
1279- """Signal thrown when the user is not validated."""
1280- logger.debug('SSOLogin: emitting UserNotValidated with app_name "%s" '
1281- 'and result %r', app_name, result)
1282-
1283- @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME,
1284- in_signature='sss')
1285- def login(self, app_name, email, password):
1286- """Call the matching method in the processor."""
1287- def f():
1288- """Inner function that will be run in a thread."""
1289- token_name = get_token_name(app_name)
1290- logger.debug('login: token_name %r, email %r, password <hidden>.',
1291- token_name, email)
1292- credentials = self.processor.login(email, password, token_name)
1293- logger.debug('login returned not None credentials? %r.',
1294- credentials is not None)
1295- return credentials
1296-
1297- def success_cb(app_name, credentials):
1298- """Login finished successfull."""
1299- is_validated = self.processor.is_validated(credentials)
1300- logger.debug('user is validated? %r.', is_validated)
1301- if is_validated:
1302- # pylint: disable=E1101
1303- d = Keyring().set_credentials(app_name, credentials)
1304- d.addCallback(lambda _: self.LoggedIn(app_name, email))
1305- d.addErrback(lambda failure: \
1306- self.LoginError(app_name,
1307- except_to_errdict(failure.value)))
1308- else:
1309- self.UserNotValidated(app_name, email)
1310- blocking(f, app_name, success_cb, self.LoginError)
1311-
1312- # validate_email signals
1313- @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
1314- def EmailValidated(self, app_name, result):
1315- """Signal thrown after the email is validated."""
1316- logger.debug('SSOLogin: emitting EmailValidated with app_name "%s" '
1317- 'and result %r', app_name, result)
1318-
1319- @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="sa{ss}")
1320- def EmailValidationError(self, app_name, error):
1321- """Signal thrown when there's a problem validating the email."""
1322- logger.debug('SSOLogin: emitting EmailValidationError with '
1323- 'app_name "%s" and error %r', app_name, error)
1324-
1325- @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME,
1326- in_signature='ssss')
1327- def validate_email(self, app_name, email, password, email_token):
1328- """Call the matching method in the processor."""
1329-
1330- def f():
1331- """Inner function that will be run in a thread."""
1332- token_name = get_token_name(app_name)
1333- credentials = self.processor.validate_email(email, password,
1334- email_token, token_name)
1335- return credentials
1336-
1337- def success_cb(app_name, credentials):
1338- """Validation finished successfully."""
1339- # pylint: disable=E1101
1340- d = Keyring().set_credentials(app_name, credentials)
1341- d.addCallback(lambda _: self.EmailValidated(app_name, email))
1342- failure_cb = lambda f: self.EmailValidationError(app_name, f.value)
1343- d.addErrback(failure_cb)
1344-
1345- blocking(f, app_name, success_cb, self.EmailValidationError)
1346-
1347- # request_password_reset_token signals
1348- @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
1349- def PasswordResetTokenSent(self, app_name, result):
1350- """Signal thrown when the token is succesfully sent."""
1351- logger.debug('SSOLogin: emitting PasswordResetTokenSent with app_name '
1352- '"%s" and result %r', app_name, result)
1353-
1354- @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="sa{ss}")
1355- def PasswordResetError(self, app_name, error):
1356- """Signal thrown when there's a problem sending the token."""
1357- logger.debug('SSOLogin: emitting PasswordResetError with '
1358- 'app_name "%s" and error %r', app_name, error)
1359-
1360- @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME,
1361- in_signature='ss')
1362- def request_password_reset_token(self, app_name, email):
1363- """Call the matching method in the processor."""
1364- def f():
1365- """Inner function that will be run in a thread."""
1366- return self.processor.request_password_reset_token(email)
1367- blocking(f, app_name, self.PasswordResetTokenSent,
1368- self.PasswordResetError)
1369-
1370- # set_new_password signals
1371- @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
1372- def PasswordChanged(self, app_name, result):
1373- """Signal thrown when the token is succesfully sent."""
1374- logger.debug('SSOLogin: emitting PasswordChanged with app_name "%s" '
1375- 'and result %r', app_name, result)
1376-
1377- @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="sa{ss}")
1378- def PasswordChangeError(self, app_name, error):
1379- """Signal thrown when there's a problem sending the token."""
1380- logger.debug('SSOLogin: emitting PasswordChangeError with '
1381- 'app_name "%s" and error %r', app_name, error)
1382-
1383- @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME,
1384- in_signature='ssss')
1385- def set_new_password(self, app_name, email, token, new_password):
1386- """Call the matching method in the processor."""
1387- def f():
1388- """Inner function that will be run in a thread."""
1389- return self.processor.set_new_password(email, token,
1390- new_password)
1391- blocking(f, app_name, self.PasswordChanged, self.PasswordChangeError)
1392-
1393-
1394-class SSOCredentials(dbus.service.Object):
1395- """DBus object that gets credentials, and login/registers if needed."""
1396-
1397- # Operator not preceded by a space (fails with dbus decorators)
1398- # pylint: disable=C0322
1399-
1400- def __init__(self, *args, **kwargs):
1401- dbus.service.Object.__init__(self, *args, **kwargs)
1402- self.ping_url = os.environ.get('USSOC_PING_URL', U1_PING_URL)
1403-
1404- def _process_error(self, app_name, error_dict):
1405- """Process the 'error_dict' and emit CredentialsError."""
1406- msg = error_dict.get(ERROR_KEY, 'No error message given.')
1407- detail = error_dict.get(ERROR_DETAIL_KEY, 'No detailed error given.')
1408- self.CredentialsError(app_name, msg, detail)
1409-
1410- @dbus.service.signal(DBUS_IFACE_CRED_NAME, signature="s")
1411- def AuthorizationDenied(self, app_name):
1412- """Signal thrown when the user denies the authorization."""
1413- logger.info('SSOCredentials: emitting AuthorizationDenied with '
1414- 'app_name "%s"', app_name)
1415-
1416- @dbus.service.signal(DBUS_IFACE_CRED_NAME, signature="sa{ss}")
1417- def CredentialsFound(self, app_name, credentials):
1418- """Signal thrown when the credentials are found."""
1419- logger.info('SSOCredentials: emitting CredentialsFound with '
1420- 'app_name "%s"', app_name)
1421-
1422- @dbus.service.signal(DBUS_IFACE_CRED_NAME, signature="sss")
1423- def CredentialsError(self, app_name, error_message, detailed_error):
1424- """Signal thrown when there is a problem finding the credentials."""
1425- logger.error('SSOCredentials: emitting CredentialsError with app_name '
1426- '"%s" and error_message %r', app_name, error_message)
1427-
1428- @dbus.service.method(dbus_interface=DBUS_IFACE_CRED_NAME,
1429- in_signature="s", out_signature="a{ss}",
1430- async_callbacks=("callback", "errback"))
1431- def find_credentials(self, app_name, callback=NO_OP, errback=NO_OP):
1432- """Get the credentials from the keyring or {} if not there."""
1433-
1434- def log_result(result):
1435- """Log the result and continue."""
1436- logger.info('find_credentials: app_name "%s", result is {}? %s',
1437- app_name, result == {})
1438- return result
1439-
1440- d = Credentials(app_name=app_name).find_credentials()
1441- # pylint: disable=E1101
1442- d.addCallback(log_result)
1443- d.addCallbacks(callback, errback)
1444-
1445- @dbus.service.method(dbus_interface=DBUS_IFACE_CRED_NAME,
1446- in_signature="sssx", out_signature="")
1447- def login_or_register_to_get_credentials(self, app_name,
1448- terms_and_conditions_url,
1449- help_text, window_id):
1450- """Get credentials if found else prompt GUI to login or register.
1451-
1452- 'app_name' will be displayed in the GUI.
1453- 'terms_and_conditions_url' will be the URL pointing to T&C.
1454- 'help_text' is an explanatory text for the end-users, will be shown
1455- below the headers.
1456- 'window_id' is the id of the window which will be set as a parent of
1457- the GUI. If 0, no parent will be set.
1458-
1459- """
1460- ping_url = self.ping_url if app_name == U1_APP_NAME else None
1461- obj = Credentials(app_name=app_name, ping_url=ping_url,
1462- tc_url=terms_and_conditions_url,
1463- help_text=help_text, window_id=window_id,
1464- success_cb=self.CredentialsFound,
1465- error_cb=self._process_error,
1466- denial_cb=self.AuthorizationDenied)
1467- obj.register()
1468-
1469- @dbus.service.method(dbus_interface=DBUS_IFACE_CRED_NAME,
1470- in_signature="ssx", out_signature="")
1471- def login_to_get_credentials(self, app_name, help_text, window_id):
1472- """Get credentials if found else prompt GUI just to login
1473-
1474- 'app_name' will be displayed in the GUI.
1475- 'help_text' is an explanatory text for the end-users, will be shown
1476- before the login fields.
1477- 'window_id' is the id of the window which will be set as a parent of
1478- the GUI. If 0, no parent will be set.
1479-
1480- """
1481- ping_url = self.ping_url if app_name == U1_APP_NAME else None
1482- obj = Credentials(app_name=app_name, ping_url=ping_url, tc_url=None,
1483- help_text=help_text, window_id=window_id,
1484- success_cb=self.CredentialsFound,
1485- error_cb=self._process_error,
1486- denial_cb=self.AuthorizationDenied)
1487- obj.login()
1488-
1489- @dbus.service.method(dbus_interface=DBUS_IFACE_CRED_NAME,
1490- in_signature='s', out_signature='',
1491- async_callbacks=("callback", "errback"))
1492- def clear_token(self, app_name, callback=NO_OP, errback=NO_OP):
1493- """Clear the token for an application from the keyring.
1494-
1495- 'app_name' is the name of the application.
1496- """
1497- d = Credentials(app_name=app_name).clear_credentials()
1498- # pylint: disable=E1101
1499- d.addCallbacks(lambda _: callback(), errback)
1500-
1501-
1502-class CredentialsManagement(dbus.service.Object):
1503- """DBus object that manages credentials.
1504-
1505- Every exposed method in this class requires one mandatory argument:
1506-
1507- - 'app_name': the name of the application. Will be displayed in the
1508- GUI header, plus it will be used to find/build/clear tokens.
1509-
1510- And accepts another parameter named 'args', which is a dictionary that
1511- can contain the following:
1512-
1513- - 'help_text': an explanatory text for the end-users, will be
1514- shown below the header. This is an optional free text field.
1515-
1516- - 'ping_url': the url to open after successful token retrieval. If
1517- defined, the email will be attached to the url and will be pinged
1518- with a OAuth-signed request.
1519-
1520- - 'tc_url': the link to the Terms and Conditions page. If defined,
1521- the checkbox to agree to the terms will link to it.
1522-
1523- - 'window_id': the id of the window which will be set as a parent
1524- of the GUI. If not defined, no parent will be set.
1525-
1526- """
1527-
1528- def __init__(self, timeout_func, shutdown_func, *args, **kwargs):
1529- super(CredentialsManagement, self).__init__(*args, **kwargs)
1530- self._ref_count = 0
1531- self.timeout_func = timeout_func
1532- self.shutdown_func = shutdown_func
1533-
1534- # Operator not preceded by a space (fails with dbus decorators)
1535- # pylint: disable=C0322
1536-
1537- valid_keys = (HELP_TEXT_KEY, PING_URL_KEY, TC_URL_KEY,
1538- UI_CLASS_KEY, UI_MODULE_KEY, WINDOW_ID_KEY)
1539-
1540- def _parse_args(self, args):
1541- """Retrieve values from the generic param 'args'."""
1542- result = dict(i for i in args.iteritems() if i[0] in self.valid_keys)
1543- result[WINDOW_ID_KEY] = int(args.get(WINDOW_ID_KEY, 0))
1544- result[SUCCESS_CB_KEY] = self.CredentialsFound
1545- result[ERROR_CB_KEY] = self.CredentialsError
1546- result[DENIAL_CB_KEY] = self.AuthorizationDenied
1547- return result
1548-
1549- def _process_failure(self, failure, app_name):
1550- """Process the 'failure' and emit CredentialsError."""
1551- self.CredentialsError(app_name, except_to_errdict(failure.value))
1552-
1553- def _get_ref_count(self):
1554- """Get value of ref_count."""
1555- return self._ref_count
1556-
1557- def _set_ref_count(self, new_value):
1558- """Set a new value to ref_count."""
1559- logger.debug('ref_count is %r, changing value to %r.',
1560- self._ref_count, new_value)
1561- if new_value < 0:
1562- self._ref_count = 0
1563- msg = 'Attempting to decrease ref_count to a negative value (%r).'
1564- logger.warning(msg, new_value)
1565- else:
1566- self._ref_count = new_value
1567-
1568- if self._ref_count == 0:
1569- logger.debug('Setting up timer with %r (%r, %r).',
1570- self.timeout_func, TIMEOUT_INTERVAL, self.shutdown)
1571- self.timeout_func(TIMEOUT_INTERVAL, self.shutdown)
1572-
1573- ref_count = property(fget=_get_ref_count, fset=_set_ref_count)
1574-
1575- def shutdown(self):
1576- """If no ongoing requests, call self.shutdown_func."""
1577- logger.debug('shutdown!, ref_count is %r.', self._ref_count)
1578- if self._ref_count == 0:
1579- logger.info('Shutting down, calling %r.', self.shutdown_func)
1580- self.shutdown_func()
1581-
1582- @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')
1583- def AuthorizationDenied(self, app_name):
1584- """Signal thrown when the user denies the authorization."""
1585- self.ref_count -= 1
1586- logger.info('%s: emitting AuthorizationDenied with app_name "%s".',
1587- self.__class__.__name__, app_name)
1588-
1589- @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='sa{ss}')
1590- def CredentialsFound(self, app_name, credentials):
1591- """Signal thrown when the credentials are found."""
1592- self.ref_count -= 1
1593- logger.info('%s: emitting CredentialsFound with app_name "%s".',
1594- self.__class__.__name__, app_name)
1595-
1596- @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')
1597- def CredentialsNotFound(self, app_name):
1598- """Signal thrown when the credentials are not found."""
1599- self.ref_count -= 1
1600- logger.info('%s: emitting CredentialsNotFound with app_name "%s".',
1601- self.__class__.__name__, app_name)
1602-
1603- @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')
1604- def CredentialsCleared(self, app_name):
1605- """Signal thrown when the credentials were cleared."""
1606- self.ref_count -= 1
1607- logger.info('%s: emitting CredentialsCleared with app_name "%s".',
1608- self.__class__.__name__, app_name)
1609-
1610- @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')
1611- def CredentialsStored(self, app_name):
1612- """Signal thrown when the credentials were cleared."""
1613- self.ref_count -= 1
1614- logger.info('%s: emitting CredentialsStored with app_name "%s".',
1615- self.__class__.__name__, app_name)
1616-
1617- @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='sa{ss}')
1618- def CredentialsError(self, app_name, error_dict):
1619- """Signal thrown when there is a problem getting the credentials."""
1620- self.ref_count -= 1
1621- logger.error('%s: emitting CredentialsError with app_name "%s" and '
1622- 'error_dict %r.', self.__class__.__name__, app_name,
1623- error_dict)
1624-
1625- @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
1626- in_signature='sa{ss}', out_signature='')
1627- def find_credentials(self, app_name, args):
1628- """Look for the credentials for an application.
1629-
1630- - 'app_name': the name of the application which credentials are
1631- going to be removed.
1632-
1633- - 'args' is a dictionary, currently not used.
1634-
1635- """
1636- self.ref_count += 1
1637-
1638- def success_cb(credentials):
1639- """Find credentials and notify using signals."""
1640- if credentials is not None and len(credentials) > 0:
1641- self.CredentialsFound(app_name, credentials)
1642- else:
1643- self.CredentialsNotFound(app_name)
1644-
1645- obj = Credentials(app_name)
1646- d = obj.find_credentials()
1647- # pylint: disable=E1101
1648- d.addCallback(success_cb)
1649- d.addErrback(self._process_failure, app_name)
1650-
1651- @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
1652- in_signature='sa{ss}', out_signature='')
1653- def clear_credentials(self, app_name, args):
1654- """Clear the credentials for an application.
1655-
1656- - 'app_name': the name of the application which credentials are
1657- going to be removed.
1658-
1659- - 'args' is a dictionary, currently not used.
1660-
1661- """
1662- self.ref_count += 1
1663-
1664- obj = Credentials(app_name)
1665- d = obj.clear_credentials()
1666- # pylint: disable=E1101
1667- d.addCallback(lambda _: self.CredentialsCleared(app_name))
1668- d.addErrback(self._process_failure, app_name)
1669-
1670- @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
1671- in_signature='sa{ss}', out_signature='')
1672- def store_credentials(self, app_name, args):
1673- """Store the token for an application.
1674-
1675- - 'app_name': the name of the application which credentials are
1676- going to be stored.
1677-
1678- - 'args' is the dictionary holding the credentials. Needs to provide
1679- the following mandatory keys: 'token', 'token_key', 'consumer_key',
1680- 'consumer_secret'.
1681-
1682- """
1683- self.ref_count += 1
1684-
1685- obj = Credentials(app_name)
1686- d = obj.store_credentials(args)
1687- # pylint: disable=E1101
1688- d.addCallback(lambda _: self.CredentialsStored(app_name))
1689- d.addErrback(self._process_failure, app_name)
1690-
1691- @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
1692- in_signature='sa{ss}', out_signature='')
1693- def register(self, app_name, args):
1694- """Get credentials if found else prompt GUI to register."""
1695- self.ref_count += 1
1696-
1697- obj = Credentials(app_name, **self._parse_args(args))
1698- obj.register()
1699-
1700- @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
1701- in_signature='sa{ss}', out_signature='')
1702- def login(self, app_name, args):
1703- """Get credentials if found else prompt GUI to login."""
1704- self.ref_count += 1
1705-
1706- obj = Credentials(app_name, **self._parse_args(args))
1707- obj.login()
1708
1709=== added file 'ubuntu_sso/main/__init__.py'
1710--- ubuntu_sso/main/__init__.py 1970-01-01 00:00:00 +0000
1711+++ ubuntu_sso/main/__init__.py 2011-03-23 14:22:30 +0000
1712@@ -0,0 +1,375 @@
1713+# -*- coding: utf-8 -*-
1714+#
1715+# Author: Natalia Bidart <natalia.bidart@canonical.com>
1716+# Author: Alejandro J. Cura <alecu@canonical.com>
1717+# Author: Manuel de la Pena <manuel@canonical.com>
1718+#
1719+# Copyright 2011 Canonical Ltd.
1720+#
1721+# This program is free software: you can redistribute it and/or modify it
1722+# under the terms of the GNU General Public License version 3, as published
1723+# by the Free Software Foundation.
1724+#
1725+# This program is distributed in the hope that it will be useful, but
1726+# WITHOUT ANY WARRANTY; without even the implied warranties of
1727+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1728+# PURPOSE. See the GNU General Public License for more details.
1729+#
1730+# You should have received a copy of the GNU General Public License along
1731+# with this program. If not, see <http://www.gnu.org/licenses/>.
1732+"""Main object implementations."""
1733+
1734+import os
1735+import sys
1736+import warnings
1737+
1738+from ubuntu_sso import NO_OP
1739+from ubuntu_sso.account import Account
1740+from ubuntu_sso.credentials import (Credentials, HELP_TEXT_KEY, PING_URL_KEY,
1741+ TC_URL_KEY, UI_CLASS_KEY, UI_MODULE_KEY, WINDOW_ID_KEY,
1742+ SUCCESS_CB_KEY, ERROR_CB_KEY, DENIAL_CB_KEY)
1743+from ubuntu_sso.keyring import get_token_name, U1_APP_NAME, Keyring
1744+from ubuntu_sso.logger import setup_logging
1745+
1746+logger = setup_logging("ubuntu_sso.main")
1747+U1_PING_URL = "https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/"
1748+
1749+
1750+class SSOLoginProcessor(Account):
1751+ """Login and register users using the Ubuntu Single Sign On service.
1752+
1753+ Alias classname to maintain backwards compatibility. DO NOT USE, use
1754+ ubuntu_sso.account.Account instead.
1755+ """
1756+
1757+ def __init__(self, sso_service_class=None):
1758+ """Create a new SSO Account manager."""
1759+ msg = 'Use ubuntu_sso.account.Account instead.'
1760+ warnings.warn(msg, DeprecationWarning)
1761+ super(SSOLoginProcessor, self).__init__(sso_service_class)
1762+
1763+
1764+def except_to_errdict(e):
1765+ """Turn an exception into a dictionary to return thru DBus."""
1766+ result = {
1767+ "errtype": e.__class__.__name__,
1768+ }
1769+ if len(e.args) == 0:
1770+ result["message"] = e.__class__.__doc__
1771+ elif isinstance(e.args[0], dict):
1772+ result.update(e.args[0])
1773+ elif isinstance(e.args[0], basestring):
1774+ result["message"] = e.args[0]
1775+
1776+ return result
1777+
1778+
1779+class SSOLoginRoot(object):
1780+ """Login thru the Single Sign On service."""
1781+
1782+ def __init__(self, sso_login_processor_class=Account,
1783+ sso_service_class=None):
1784+ """Initiate the Login object."""
1785+ self.sso_login_processor_class = sso_login_processor_class
1786+ self.processor = self.sso_login_processor_class(
1787+ sso_service_class=sso_service_class)
1788+
1789+ def generate_captcha(self, app_name, filename, thread_execute, result_cb,
1790+ error_cb):
1791+ """Call the matching method in the processor."""
1792+ def f():
1793+ """Inner function that will be run in a thread."""
1794+ return self.processor.generate_captcha(filename)
1795+ thread_execute(f, app_name, result_cb, error_cb)
1796+
1797+ def register_user(self, app_name, email, password, name, captcha_id,
1798+ captcha_solution, thread_execute, result_cb, error_cb):
1799+ """Call the matching method in the processor."""
1800+ def f():
1801+ """Inner function that will be run in a thread."""
1802+ return self.processor.register_user(email, password, name,
1803+ captcha_id, captcha_solution)
1804+ thread_execute(f, app_name, result_cb, error_cb)
1805+
1806+ def login(self, app_name, email, password, thread_execute, result_cb,
1807+ error_cb, not_validated_cb):
1808+ """Call the matching method in the processor."""
1809+ def f():
1810+ """Inner function that will be run in a thread."""
1811+ token_name = get_token_name(app_name)
1812+ logger.debug('login: token_name %r, email %r, password <hidden>.',
1813+ token_name, email)
1814+ credentials = self.processor.login(email, password, token_name)
1815+ logger.debug('login returned not None credentials? %r.',
1816+ credentials is not None)
1817+ return credentials
1818+
1819+ def success_cb(app_name, credentials):
1820+ """Login finished successfull."""
1821+ is_validated = self.processor.is_validated(credentials)
1822+ logger.debug('user is validated? %r.', is_validated)
1823+ if is_validated:
1824+ # pylint: disable=E1101
1825+ d = Keyring().set_credentials(app_name, credentials)
1826+ d.addCallback(lambda _: result_cb(app_name, email))
1827+ d.addErrback(lambda failure: \
1828+ error_cb(app_name,
1829+ except_to_errdict(failure.value)))
1830+ else:
1831+ not_validated_cb(app_name, email)
1832+ thread_execute(f, app_name, success_cb, error_cb)
1833+
1834+ def validate_email(self, app_name, email, password, email_token,
1835+ thread_execute, result_cb, error_cb):
1836+ """Call the matching method in the processor."""
1837+
1838+ def f():
1839+ """Inner function that will be run in a thread."""
1840+ token_name = get_token_name(app_name)
1841+ credentials = self.processor.validate_email(email, password,
1842+ email_token, token_name)
1843+ return credentials
1844+
1845+ def success_cb(app_name, credentials):
1846+ """Validation finished successfully."""
1847+ # pylint: disable=E1101
1848+ d = Keyring().set_credentials(app_name, credentials)
1849+ d.addCallback(lambda _: result_cb(app_name, email))
1850+ failure_cb = lambda f: error_cb(app_name, f.value)
1851+ d.addErrback(failure_cb)
1852+
1853+ thread_execute(f, app_name, success_cb, error_cb)
1854+
1855+ def request_password_reset_token(self, app_name, email, thread_execute,
1856+ result_cb, error_cb):
1857+ """Call the matching method in the processor."""
1858+ def f():
1859+ """Inner function that will be run in a thread."""
1860+ return self.processor.request_password_reset_token(email)
1861+ thread_execute(f, app_name, result_cb, error_cb)
1862+
1863+ def set_new_password(self, app_name, email, token, new_password,
1864+ thread_execute, result_cb, error_cb):
1865+ """Call the matching method in the processor."""
1866+ def f():
1867+ """Inner function that will be run in a thread."""
1868+ return self.processor.set_new_password(email, token,
1869+ new_password)
1870+ thread_execute(f, app_name, result_cb, error_cb)
1871+
1872+
1873+class SSOCredentialsRoot(object):
1874+ """Object that gets credentials, and login/registers if needed."""
1875+
1876+ def __init__(self):
1877+ self.ping_url = os.environ.get('USSOC_PING_URL', U1_PING_URL)
1878+
1879+ def find_credentials(self, app_name, callback=NO_OP, errback=NO_OP):
1880+ """Get the credentials from the keyring or {} if not there."""
1881+
1882+ def log_result(result):
1883+ """Log the result and continue."""
1884+ logger.info('find_credentials: app_name "%s", result is {}? %s',
1885+ app_name, result == {})
1886+ return result
1887+
1888+ d = Credentials(app_name=app_name).find_credentials()
1889+ # pylint: disable=E1101
1890+ d.addCallback(log_result)
1891+ d.addCallbacks(callback, errback)
1892+
1893+ def login_or_register_to_get_credentials(self, app_name,
1894+ terms_and_conditions_url,
1895+ help_text, window_id,
1896+ success_cb, error_cb, denial_cb):
1897+ """Get credentials if found else prompt GUI to login or register.
1898+
1899+ 'app_name' will be displayed in the GUI.
1900+ 'terms_and_conditions_url' will be the URL pointing to T&C.
1901+ 'help_text' is an explanatory text for the end-users, will be shown
1902+ below the headers.
1903+ 'window_id' is the id of the window which will be set as a parent of
1904+ the GUI. If 0, no parent will be set.
1905+
1906+ """
1907+ ping_url = self.ping_url if app_name == U1_APP_NAME else None
1908+ obj = Credentials(app_name=app_name, ping_url=ping_url,
1909+ tc_url=terms_and_conditions_url,
1910+ help_text=help_text, window_id=window_id,
1911+ success_cb=success_cb, error_cb=error_cb,
1912+ denial_cb=denial_cb)
1913+ obj.register()
1914+
1915+ def login_to_get_credentials(self, app_name, help_text, window_id,
1916+ success_cb, error_cb, denial_cb):
1917+ """Get credentials if found else prompt GUI just to login
1918+
1919+ 'app_name' will be displayed in the GUI.
1920+ 'help_text' is an explanatory text for the end-users, will be shown
1921+ before the login fields.
1922+ 'window_id' is the id of the window which will be set as a parent of
1923+ the GUI. If 0, no parent will be set.
1924+
1925+ """
1926+ ping_url = self.ping_url if app_name == U1_APP_NAME else None
1927+ obj = Credentials(app_name=app_name, ping_url=ping_url, tc_url=None,
1928+ help_text=help_text, window_id=window_id,
1929+ success_cb=success_cb, error_cb=error_cb,
1930+ denial_cb=denial_cb)
1931+ obj.login()
1932+
1933+ def clear_token(self, app_name, callback=NO_OP, errback=NO_OP):
1934+ """Clear the token for an application from the keyring.
1935+
1936+ 'app_name' is the name of the application.
1937+ """
1938+ d = Credentials(app_name=app_name).clear_credentials()
1939+ # pylint: disable=E1101
1940+ d.addCallbacks(lambda _: callback(), errback)
1941+
1942+
1943+class CredentialsManagementRoot(object):
1944+ """Object that manages credentials.
1945+
1946+ Every exposed method in this class requires one mandatory argument:
1947+
1948+ - 'app_name': the name of the application. Will be displayed in the
1949+ GUI header, plus it will be used to find/build/clear tokens.
1950+
1951+ And accepts another parameter named 'args', which is a dictionary that
1952+ can contain the following:
1953+
1954+ - 'help_text': an explanatory text for the end-users, will be
1955+ shown below the header. This is an optional free text field.
1956+
1957+ - 'ping_url': the url to open after successful token retrieval. If
1958+ defined, the email will be attached to the url and will be pinged
1959+ with a OAuth-signed request.
1960+
1961+ - 'tc_url': the link to the Terms and Conditions page. If defined,
1962+ the checkbox to agree to the terms will link to it.
1963+
1964+ - 'window_id': the id of the window which will be set as a parent
1965+ of the GUI. If not defined, no parent will be set.
1966+
1967+ """
1968+
1969+ def __init__(self, found_cb, error_cb, denied_cb, *args, **kwargs):
1970+ """Create a new instance.
1971+
1972+ - 'found_cb' is a callback that will be executed when the credentials
1973+ were found.
1974+
1975+ - 'error_cb' is a callback that will be executed when there was an
1976+ error getting the credentials.
1977+
1978+ - 'denied_cb' is a callback that will be executed when the user denied
1979+ the use of the crendetials.
1980+
1981+ """
1982+ super(CredentialsManagementRoot, self).__init__(*args, **kwargs)
1983+ self.found_cb = found_cb
1984+ self.error_cb = error_cb
1985+ self.denied_cb = denied_cb
1986+
1987+ valid_keys = (HELP_TEXT_KEY, PING_URL_KEY, TC_URL_KEY,
1988+ UI_CLASS_KEY, UI_MODULE_KEY, WINDOW_ID_KEY)
1989+
1990+ def _parse_args(self, args):
1991+ """Retrieve values from the generic param 'args'."""
1992+ result = dict(i for i in args.iteritems() if i[0] in self.valid_keys)
1993+ result[WINDOW_ID_KEY] = int(args.get(WINDOW_ID_KEY, 0))
1994+ result[SUCCESS_CB_KEY] = self.found_cb
1995+ result[ERROR_CB_KEY] = self.error_cb
1996+ result[DENIAL_CB_KEY] = self.denied_cb
1997+ return result
1998+
1999+ def find_credentials(self, app_name, args, success_cb, error_cb):
2000+ """Look for the credentials for an application.
2001+
2002+ - 'app_name': the name of the application which credentials are
2003+ going to be removed.
2004+
2005+ - 'args' is a dictionary, currently not used.
2006+
2007+ - 'success_cb' is a callback that will be execute if the operation was
2008+ a success.
2009+
2010+ - 'error_cb' is a callback that will be executed if the operation had
2011+ an error.
2012+
2013+ """
2014+
2015+ obj = Credentials(app_name)
2016+ d = obj.find_credentials()
2017+ # pylint: disable=E1101
2018+ d.addCallback(success_cb)
2019+ d.addErrback(error_cb, app_name)
2020+
2021+ def clear_credentials(self, app_name, args, success_cb, error_cb):
2022+ """Clear the credentials for an application.
2023+
2024+ - 'app_name': the name of the application which credentials are
2025+ going to be removed.
2026+
2027+ - 'args' is a dictionary, currently not used.
2028+
2029+ - 'success_cb' is a callback that will be execute if the operation was
2030+ a success.
2031+
2032+ - 'error_cb' is a callback that will be executed if the operation had
2033+ an error.
2034+
2035+ """
2036+
2037+ obj = Credentials(app_name)
2038+ d = obj.clear_credentials()
2039+ # pylint: disable=E1101
2040+ d.addCallback(success_cb)
2041+ d.addErrback(error_cb, app_name)
2042+
2043+ def store_credentials(self, app_name, args, success_cb, error_cb):
2044+ """Store the token for an application.
2045+
2046+ - 'app_name': the name of the application which credentials are
2047+ going to be stored.
2048+
2049+ - 'args' is the dictionary holding the credentials. Needs to provide
2050+ the following mandatory keys: 'token', 'token_key', 'consumer_key',
2051+ 'consumer_secret'.
2052+
2053+ - 'success_cb' is a callback that will be execute if the operation was
2054+ a success.
2055+
2056+ - 'error_cb' is a callback that will be executed if the operation had
2057+ an error.
2058+ """
2059+
2060+ obj = Credentials(app_name)
2061+ d = obj.store_credentials(args)
2062+ # pylint: disable=E1101
2063+ d.addCallback(success_cb)
2064+ d.addErrback(error_cb, app_name)
2065+
2066+ def register(self, app_name, args):
2067+ """Get credentials if found else prompt GUI to register."""
2068+ obj = Credentials(app_name, **self._parse_args(args))
2069+ obj.register()
2070+
2071+ def login(self, app_name, args):
2072+ """Get credentials if found else prompt GUI to login."""
2073+ obj = Credentials(app_name, **self._parse_args(args))
2074+ obj.login()
2075+
2076+# pylint: disable=C0103
2077+SSOLogin = None
2078+SSOCredentials = None
2079+CredentialsManagement = None
2080+
2081+if sys.platform == 'win32':
2082+ pass
2083+else:
2084+ from ubuntu_sso.main import linux
2085+ SSOLogin = linux.SSOLogin
2086+ SSOCredentials = linux.SSOCredentials
2087+ CredentialsManagement = linux.CredentialsManagement
2088
2089=== added file 'ubuntu_sso/main/linux.py'
2090--- ubuntu_sso/main/linux.py 1970-01-01 00:00:00 +0000
2091+++ ubuntu_sso/main/linux.py 2011-03-23 14:22:30 +0000
2092@@ -0,0 +1,486 @@
2093+# -*- coding: utf-8 -*-
2094+#
2095+# ubuntu_sso.main - main login handling interface
2096+#
2097+# Author: Natalia Bidart <natalia.bidart@canonical.com>
2098+# Author: Alejandro J. Cura <alecu@canonical.com>
2099+#
2100+# Copyright 2009 Canonical Ltd.
2101+#
2102+# This program is free software: you can redistribute it and/or modify it
2103+# under the terms of the GNU General Public License version 3, as published
2104+# by the Free Software Foundation.
2105+#
2106+# This program is distributed in the hope that it will be useful, but
2107+# WITHOUT ANY WARRANTY; without even the implied warranties of
2108+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2109+# PURPOSE. See the GNU General Public License for more details.
2110+#
2111+# You should have received a copy of the GNU General Public License along
2112+# with this program. If not, see <http://www.gnu.org/licenses/>.
2113+"""Single Sign On login handler.
2114+
2115+An utility which accepts requests for Ubuntu Single Sign On login over D-Bus.
2116+
2117+The OAuth process is handled, including adding the OAuth access token to the
2118+local keyring.
2119+
2120+"""
2121+
2122+import threading
2123+
2124+import dbus.service
2125+
2126+from ubuntu_sso import (DBUS_ACCOUNT_PATH, DBUS_IFACE_USER_NAME,
2127+ DBUS_IFACE_CRED_NAME, DBUS_CREDENTIALS_IFACE, NO_OP)
2128+from ubuntu_sso.account import Account
2129+from ubuntu_sso.credentials import ERROR_KEY, ERROR_DETAIL_KEY
2130+from ubuntu_sso.logger import setup_logging
2131+from ubuntu_sso.main import (CredentialsManagementRoot, SSOLoginRoot,
2132+ SSOCredentialsRoot, except_to_errdict)
2133+
2134+
2135+# Disable the invalid name warning, as we have a lot of DBus style names
2136+# pylint: disable=C0103
2137+
2138+
2139+logger = setup_logging("ubuntu_sso.main")
2140+TIMEOUT_INTERVAL = 10000 # 10 seconds
2141+
2142+
2143+def blocking(f, app_name, result_cb, error_cb):
2144+ """Run f in a thread; return or throw an exception thru the callbacks."""
2145+ def _in_thread():
2146+ """The part that runs inside the thread."""
2147+ try:
2148+ result_cb(app_name, f())
2149+ except Exception, e: # pylint: disable=W0703
2150+ msg = "Exception while running DBus blocking code in a thread:"
2151+ logger.exception(msg)
2152+ error_cb(app_name, except_to_errdict(e))
2153+ threading.Thread(target=_in_thread).start()
2154+
2155+
2156+class SSOLogin(dbus.service.Object):
2157+ """Login thru the Single Sign On service."""
2158+
2159+ # Operator not preceded by a space (fails with dbus decorators)
2160+ # pylint: disable=C0322
2161+
2162+ def __init__(self, bus_name, object_path=DBUS_ACCOUNT_PATH,
2163+ sso_login_processor_class=Account,
2164+ sso_service_class=None):
2165+ """Initiate the Login object."""
2166+ dbus.service.Object.__init__(self, object_path=object_path,
2167+ bus_name=bus_name)
2168+ self.root = SSOLoginRoot(sso_login_processor_class, sso_service_class)
2169+
2170+ # generate_capcha signals
2171+ @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
2172+ def CaptchaGenerated(self, app_name, result):
2173+ """Signal thrown after the captcha is generated."""
2174+ logger.debug('SSOLogin: emitting CaptchaGenerated with app_name "%s" '
2175+ 'and result %r', app_name, result)
2176+
2177+ @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="sa{ss}")
2178+ def CaptchaGenerationError(self, app_name, error):
2179+ """Signal thrown when there's a problem generating the captcha."""
2180+ logger.debug('SSOLogin: emitting CaptchaGenerationError with '
2181+ 'app_name "%s" and error %r', app_name, error)
2182+
2183+ @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME,
2184+ in_signature='ss')
2185+ def generate_captcha(self, app_name, filename):
2186+ """Call the matching method in the processor."""
2187+ self.root.generate_captcha(app_name, filename, blocking,
2188+ self.CaptchaGenerated,
2189+ self.CaptchaGenerationError)
2190+
2191+ # register_user signals
2192+ @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
2193+ def UserRegistered(self, app_name, result):
2194+ """Signal thrown when the user is registered."""
2195+ logger.debug('SSOLogin: emitting UserRegistered with app_name "%s" '
2196+ 'and result %r', app_name, result)
2197+
2198+ @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="sa{ss}")
2199+ def UserRegistrationError(self, app_name, error):
2200+ """Signal thrown when there's a problem registering the user."""
2201+ logger.debug('SSOLogin: emitting UserRegistrationError with '
2202+ 'app_name "%s" and error %r', app_name, error)
2203+
2204+ @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME,
2205+ in_signature='ssssss')
2206+ def register_user(self, app_name, email, password, name,
2207+ captcha_id, captcha_solution):
2208+ """Call the matching method in the processor."""
2209+ self.root.register_user(app_name, email, password, name, captcha_id,
2210+ captcha_solution, blocking,
2211+ self.UserRegistered,
2212+ self.UserRegistrationError)
2213+
2214+ # login signals
2215+ @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
2216+ def LoggedIn(self, app_name, result):
2217+ """Signal thrown when the user is logged in."""
2218+ logger.debug('SSOLogin: emitting LoggedIn with app_name "%s" '
2219+ 'and result %r', app_name, result)
2220+
2221+ @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="sa{ss}")
2222+ def LoginError(self, app_name, error):
2223+ """Signal thrown when there is a problem in the login."""
2224+ logger.debug('SSOLogin: emitting LoginError with '
2225+ 'app_name "%s" and error %r', app_name, error)
2226+
2227+ @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
2228+ def UserNotValidated(self, app_name, result):
2229+ """Signal thrown when the user is not validated."""
2230+ logger.debug('SSOLogin: emitting UserNotValidated with app_name "%s" '
2231+ 'and result %r', app_name, result)
2232+
2233+ @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME,
2234+ in_signature='sss')
2235+ def login(self, app_name, email, password):
2236+ """Call the matching method in the processor."""
2237+ self.root.login(app_name, email, password, blocking, self.LoggedIn,
2238+ self.LoginError, self.UserNotValidated)
2239+
2240+ # validate_email signals
2241+ @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
2242+ def EmailValidated(self, app_name, result):
2243+ """Signal thrown after the email is validated."""
2244+ logger.debug('SSOLogin: emitting EmailValidated with app_name "%s" '
2245+ 'and result %r', app_name, result)
2246+
2247+ @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="sa{ss}")
2248+ def EmailValidationError(self, app_name, error):
2249+ """Signal thrown when there's a problem validating the email."""
2250+ logger.debug('SSOLogin: emitting EmailValidationError with '
2251+ 'app_name "%s" and error %r', app_name, error)
2252+
2253+ @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME,
2254+ in_signature='ssss')
2255+ def validate_email(self, app_name, email, password, email_token):
2256+ """Call the matching method in the processor."""
2257+ self.root.validate_email(app_name, email, password, email_token,
2258+ blocking, self.EmailValidated,
2259+ self.EmailValidationError)
2260+
2261+ # request_password_reset_token signals
2262+ @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
2263+ def PasswordResetTokenSent(self, app_name, result):
2264+ """Signal thrown when the token is succesfully sent."""
2265+ logger.debug('SSOLogin: emitting PasswordResetTokenSent with app_name '
2266+ '"%s" and result %r', app_name, result)
2267+
2268+ @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="sa{ss}")
2269+ def PasswordResetError(self, app_name, error):
2270+ """Signal thrown when there's a problem sending the token."""
2271+ logger.debug('SSOLogin: emitting PasswordResetError with '
2272+ 'app_name "%s" and error %r', app_name, error)
2273+
2274+ @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME,
2275+ in_signature='ss')
2276+ def request_password_reset_token(self, app_name, email):
2277+ """Call the matching method in the processor."""
2278+ self.root.request_password_reset_token(app_name, email, blocking,
2279+ self.PasswordResetTokenSent,
2280+ self.PasswordResetError)
2281+
2282+ # set_new_password signals
2283+ @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
2284+ def PasswordChanged(self, app_name, result):
2285+ """Signal thrown when the token is succesfully sent."""
2286+ logger.debug('SSOLogin: emitting PasswordChanged with app_name "%s" '
2287+ 'and result %r', app_name, result)
2288+
2289+ @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="sa{ss}")
2290+ def PasswordChangeError(self, app_name, error):
2291+ """Signal thrown when there's a problem sending the token."""
2292+ logger.debug('SSOLogin: emitting PasswordChangeError with '
2293+ 'app_name "%s" and error %r', app_name, error)
2294+
2295+ @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME,
2296+ in_signature='ssss')
2297+ def set_new_password(self, app_name, email, token, new_password):
2298+ """Call the matching method in the processor."""
2299+ self.root.set_new_password(app_name, email, token, new_password,
2300+ blocking, self.PasswordChanged,
2301+ self.PasswordChangeError)
2302+
2303+
2304+class SSOCredentials(dbus.service.Object):
2305+ """DBus object that gets credentials, and login/registers if needed."""
2306+
2307+ # Operator not preceded by a space (fails with dbus decorators)
2308+ # pylint: disable=C0322
2309+
2310+ def __init__(self, *args, **kwargs):
2311+ dbus.service.Object.__init__(self, *args, **kwargs)
2312+ self.root = SSOCredentialsRoot()
2313+
2314+ def _process_error(self, app_name, error_dict):
2315+ """Process the 'error_dict' and emit CredentialsError."""
2316+ msg = error_dict.get(ERROR_KEY, 'No error message given.')
2317+ detail = error_dict.get(ERROR_DETAIL_KEY, 'No detailed error given.')
2318+ self.CredentialsError(app_name, msg, detail)
2319+
2320+ @dbus.service.signal(DBUS_IFACE_CRED_NAME, signature="s")
2321+ def AuthorizationDenied(self, app_name):
2322+ """Signal thrown when the user denies the authorization."""
2323+ logger.info('SSOCredentials: emitting AuthorizationDenied with '
2324+ 'app_name "%s"', app_name)
2325+
2326+ @dbus.service.signal(DBUS_IFACE_CRED_NAME, signature="sa{ss}")
2327+ def CredentialsFound(self, app_name, credentials):
2328+ """Signal thrown when the credentials are found."""
2329+ logger.info('SSOCredentials: emitting CredentialsFound with '
2330+ 'app_name "%s"', app_name)
2331+
2332+ @dbus.service.signal(DBUS_IFACE_CRED_NAME, signature="sss")
2333+ def CredentialsError(self, app_name, error_message, detailed_error):
2334+ """Signal thrown when there is a problem finding the credentials."""
2335+ logger.error('SSOCredentials: emitting CredentialsError with app_name '
2336+ '"%s" and error_message %r', app_name, error_message)
2337+
2338+ @dbus.service.method(dbus_interface=DBUS_IFACE_CRED_NAME,
2339+ in_signature="s", out_signature="a{ss}",
2340+ async_callbacks=("callback", "errback"))
2341+ def find_credentials(self, app_name, callback=NO_OP, errback=NO_OP):
2342+ """Get the credentials from the keyring or {} if not there."""
2343+ self.root.find_credentials(app_name, callback, errback)
2344+
2345+ @dbus.service.method(dbus_interface=DBUS_IFACE_CRED_NAME,
2346+ in_signature="sssx", out_signature="")
2347+ def login_or_register_to_get_credentials(self, app_name,
2348+ terms_and_conditions_url,
2349+ help_text, window_id):
2350+ """Get credentials if found else prompt GUI to login or register.
2351+
2352+ 'app_name' will be displayed in the GUI.
2353+ 'terms_and_conditions_url' will be the URL pointing to T&C.
2354+ 'help_text' is an explanatory text for the end-users, will be shown
2355+ below the headers.
2356+ 'window_id' is the id of the window which will be set as a parent of
2357+ the GUI. If 0, no parent will be set.
2358+
2359+ """
2360+ self.root.login_or_register_to_get_credentials(app_name,
2361+ terms_and_conditions_url,
2362+ help_text, window_id,
2363+ self.CredentialsFound,
2364+ self._process_error,
2365+ self.AuthorizationDenied)
2366+
2367+ @dbus.service.method(dbus_interface=DBUS_IFACE_CRED_NAME,
2368+ in_signature="ssx", out_signature="")
2369+ def login_to_get_credentials(self, app_name, help_text, window_id):
2370+ """Get credentials if found else prompt GUI just to login
2371+
2372+ 'app_name' will be displayed in the GUI.
2373+ 'help_text' is an explanatory text for the end-users, will be shown
2374+ before the login fields.
2375+ 'window_id' is the id of the window which will be set as a parent of
2376+ the GUI. If 0, no parent will be set.
2377+
2378+ """
2379+ self.root.login_to_get_credentials(app_name, help_text, window_id,
2380+ self.CredentialsFound,
2381+ self._process_error,
2382+ self.AuthorizationDenied)
2383+
2384+ @dbus.service.method(dbus_interface=DBUS_IFACE_CRED_NAME,
2385+ in_signature='s', out_signature='',
2386+ async_callbacks=("callback", "errback"))
2387+ def clear_token(self, app_name, callback=NO_OP, errback=NO_OP):
2388+ """Clear the token for an application from the keyring.
2389+
2390+ 'app_name' is the name of the application.
2391+ """
2392+ self.root.clear_token(app_name, callback, errback)
2393+
2394+
2395+class CredentialsManagement(dbus.service.Object):
2396+ """DBus object that manages credentials.
2397+
2398+ Every exposed method in this class requires one mandatory argument:
2399+
2400+ - 'app_name': the name of the application. Will be displayed in the
2401+ GUI header, plus it will be used to find/build/clear tokens.
2402+
2403+ And accepts another parameter named 'args', which is a dictionary that
2404+ can contain the following:
2405+
2406+ - 'help_text': an explanatory text for the end-users, will be
2407+ shown below the header. This is an optional free text field.
2408+
2409+ - 'ping_url': the url to open after successful token retrieval. If
2410+ defined, the email will be attached to the url and will be pinged
2411+ with a OAuth-signed request.
2412+
2413+ - 'tc_url': the link to the Terms and Conditions page. If defined,
2414+ the checkbox to agree to the terms will link to it.
2415+
2416+ - 'window_id': the id of the window which will be set as a parent
2417+ of the GUI. If not defined, no parent will be set.
2418+
2419+ """
2420+
2421+ def __init__(self, timeout_func, shutdown_func, *args, **kwargs):
2422+ super(CredentialsManagement, self).__init__(*args, **kwargs)
2423+ self._ref_count = 0
2424+ self.timeout_func = timeout_func
2425+ self.shutdown_func = shutdown_func
2426+ self.root = CredentialsManagementRoot(self.CredentialsFound,
2427+ self.CredentialsError,
2428+ self.AuthorizationDenied)
2429+
2430+ # Operator not preceded by a space (fails with dbus decorators)
2431+ # pylint: disable=C0322
2432+
2433+ def _process_failure(self, failure, app_name):
2434+ """Process the 'failure' and emit CredentialsError."""
2435+ self.CredentialsError(app_name, except_to_errdict(failure.value))
2436+
2437+ def _get_ref_count(self):
2438+ """Get value of ref_count."""
2439+ return self._ref_count
2440+
2441+ def _set_ref_count(self, new_value):
2442+ """Set a new value to ref_count."""
2443+ logger.debug('ref_count is %r, changing value to %r.',
2444+ self._ref_count, new_value)
2445+ if new_value < 0:
2446+ self._ref_count = 0
2447+ msg = 'Attempting to decrease ref_count to a negative value (%r).'
2448+ logger.warning(msg, new_value)
2449+ else:
2450+ self._ref_count = new_value
2451+
2452+ if self._ref_count == 0:
2453+ logger.debug('Setting up timer with %r (%r, %r).',
2454+ self.timeout_func, TIMEOUT_INTERVAL, self.shutdown)
2455+ self.timeout_func(TIMEOUT_INTERVAL, self.shutdown)
2456+
2457+ ref_count = property(fget=_get_ref_count, fset=_set_ref_count)
2458+
2459+ def shutdown(self):
2460+ """If no ongoing requests, call self.shutdown_func."""
2461+ logger.debug('shutdown!, ref_count is %r.', self._ref_count)
2462+ if self._ref_count == 0:
2463+ logger.info('Shutting down, calling %r.', self.shutdown_func)
2464+ self.shutdown_func()
2465+
2466+ @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')
2467+ def AuthorizationDenied(self, app_name):
2468+ """Signal thrown when the user denies the authorization."""
2469+ self.ref_count -= 1
2470+ logger.info('%s: emitting AuthorizationDenied with app_name "%s".',
2471+ self.__class__.__name__, app_name)
2472+
2473+ @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='sa{ss}')
2474+ def CredentialsFound(self, app_name, credentials):
2475+ """Signal thrown when the credentials are found."""
2476+ self.ref_count -= 1
2477+ logger.info('%s: emitting CredentialsFound with app_name "%s".',
2478+ self.__class__.__name__, app_name)
2479+
2480+ @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')
2481+ def CredentialsNotFound(self, app_name):
2482+ """Signal thrown when the credentials are not found."""
2483+ self.ref_count -= 1
2484+ logger.info('%s: emitting CredentialsNotFound with app_name "%s".',
2485+ self.__class__.__name__, app_name)
2486+
2487+ @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')
2488+ def CredentialsCleared(self, app_name):
2489+ """Signal thrown when the credentials were cleared."""
2490+ self.ref_count -= 1
2491+ logger.info('%s: emitting CredentialsCleared with app_name "%s".',
2492+ self.__class__.__name__, app_name)
2493+
2494+ @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')
2495+ def CredentialsStored(self, app_name):
2496+ """Signal thrown when the credentials were cleared."""
2497+ self.ref_count -= 1
2498+ logger.info('%s: emitting CredentialsStored with app_name "%s".',
2499+ self.__class__.__name__, app_name)
2500+
2501+ @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='sa{ss}')
2502+ def CredentialsError(self, app_name, error_dict):
2503+ """Signal thrown when there is a problem getting the credentials."""
2504+ self.ref_count -= 1
2505+ logger.error('%s: emitting CredentialsError with app_name "%s" and '
2506+ 'error_dict %r.', self.__class__.__name__, app_name,
2507+ error_dict)
2508+
2509+ @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
2510+ in_signature='sa{ss}', out_signature='')
2511+ def find_credentials(self, app_name, args):
2512+ """Look for the credentials for an application.
2513+
2514+ - 'app_name': the name of the application which credentials are
2515+ going to be removed.
2516+
2517+ - 'args' is a dictionary, currently not used.
2518+
2519+ """
2520+ self.ref_count += 1
2521+
2522+ def success_cb(credentials):
2523+ """Find credentials and notify using signals."""
2524+ if credentials is not None and len(credentials) > 0:
2525+ self.CredentialsFound(app_name, credentials)
2526+ else:
2527+ self.CredentialsNotFound(app_name)
2528+
2529+ self.root.find_credentials(app_name, args, success_cb,
2530+ self._process_failure)
2531+
2532+ @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
2533+ in_signature='sa{ss}', out_signature='')
2534+ def clear_credentials(self, app_name, args):
2535+ """Clear the credentials for an application.
2536+
2537+ - 'app_name': the name of the application which credentials are
2538+ going to be removed.
2539+
2540+ - 'args' is a dictionary, currently not used.
2541+
2542+ """
2543+ self.ref_count += 1
2544+ self.root.clear_credentials(app_name, args,
2545+ lambda _: self.CredentialsCleared(app_name),
2546+ self._process_failure)
2547+
2548+ @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
2549+ in_signature='sa{ss}', out_signature='')
2550+ def store_credentials(self, app_name, args):
2551+ """Store the token for an application.
2552+
2553+ - 'app_name': the name of the application which credentials are
2554+ going to be stored.
2555+
2556+ - 'args' is the dictionary holding the credentials. Needs to provide
2557+ the following mandatory keys: 'token', 'token_key', 'consumer_key',
2558+ 'consumer_secret'.
2559+
2560+ """
2561+ self.ref_count += 1
2562+ self.root.store_credentials(app_name, args,
2563+ lambda _: self.CredentialsStored(app_name),
2564+ self._process_failure)
2565+
2566+ @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
2567+ in_signature='sa{ss}', out_signature='')
2568+ def register(self, app_name, args):
2569+ """Get credentials if found else prompt GUI to register."""
2570+ self.ref_count += 1
2571+ self.root.register(app_name, args)
2572+
2573+ @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
2574+ in_signature='sa{ss}', out_signature='')
2575+ def login(self, app_name, args):
2576+ """Get credentials if found else prompt GUI to login."""
2577+ self.ref_count += 1
2578+ self.root.login(app_name, args)
2579
2580=== added directory 'ubuntu_sso/main/tests'
2581=== added file 'ubuntu_sso/main/tests/__init__.py'
2582--- ubuntu_sso/main/tests/__init__.py 1970-01-01 00:00:00 +0000
2583+++ ubuntu_sso/main/tests/__init__.py 2011-03-23 14:22:30 +0000
2584@@ -0,0 +1,17 @@
2585+# -*- coding: utf-8 -*-
2586+# Author: Manuel de la Pena <manuel@canonical.com>
2587+#
2588+# Copyright 2011 Canonical Ltd.
2589+#
2590+# This program is free software: you can redistribute it and/or modify it
2591+# under the terms of the GNU General Public License version 3, as published
2592+# by the Free Software Foundation.
2593+#
2594+# This program is distributed in the hope that it will be useful, but
2595+# WITHOUT ANY WARRANTY; without even the implied warranties of
2596+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2597+# PURPOSE. See the GNU General Public License for more details.
2598+#
2599+# You should have received a copy of the GNU General Public License along
2600+# with this program. If not, see <http://www.gnu.org/licenses/>.
2601+"""Test the different main implementations."""
2602
2603=== added file 'ubuntu_sso/main/tests/test_common.py'
2604--- ubuntu_sso/main/tests/test_common.py 1970-01-01 00:00:00 +0000
2605+++ ubuntu_sso/main/tests/test_common.py 2011-03-23 14:22:30 +0000
2606@@ -0,0 +1,245 @@
2607+# -*- coding: utf-8 -*-
2608+#
2609+# test_main - tests for ubuntu_sso.main
2610+#
2611+# Author: Natalia Bidart <natalia.bidart@canonical.com>
2612+# Author: Alejandro J. Cura <alecu@canonical.com>
2613+# Author: Manuel de la Pena <manuel@canonical.com>
2614+#
2615+# Copyright 2009-2010 Canonical Ltd.
2616+#
2617+# This program is free software: you can redistribute it and/or modify it
2618+# under the terms of the GNU General Public License version 3, as published
2619+# by the Free Software Foundation.
2620+#
2621+# This program is distributed in the hope that it will be useful, but
2622+# WITHOUT ANY WARRANTY; without even the implied warranties of
2623+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2624+# PURPOSE. See the GNU General Public License for more details.
2625+#
2626+# You should have received a copy of the GNU General Public License along
2627+# with this program. If not, see <http://www.gnu.org/licenses/>.
2628+"""Tests share by diff platforms."""
2629+
2630+import os
2631+
2632+from unittest import TestCase
2633+from mocker import MockerTestCase, MATCH
2634+from ubuntu_sso.main import (
2635+ CredentialsManagement,
2636+ SSOCredentialsRoot,
2637+ SSOCredentials,
2638+ SSOLogin,
2639+ U1_PING_URL)
2640+
2641+
2642+class EnvironOverridesTestCase(TestCase):
2643+ """Some URLs can be set from the environment for testing/QA purposes."""
2644+
2645+ def test_override_ping_url(self):
2646+ """The ping url can be set from the environ via USSOC_PING_URL."""
2647+ fake_url = 'this is not really a URL'
2648+ old_url = os.environ.get('USSOC_PING_URL')
2649+ os.environ['USSOC_PING_URL'] = fake_url
2650+ try:
2651+ creds = SSOCredentialsRoot()
2652+ self.assertEqual(creds.ping_url, fake_url)
2653+ finally:
2654+ if old_url:
2655+ os.environ['USSOC_PING_URL'] = old_url
2656+ else:
2657+ del os.environ['USSOC_PING_URL']
2658+
2659+ def test_no_override_ping_url(self):
2660+ """If the environ is unset, the default ping url is used."""
2661+ creds = SSOCredentialsRoot()
2662+ self.assertEqual(creds.ping_url, U1_PING_URL)
2663+
2664+
2665+class SSOLoginMockedTestCase(MockerTestCase):
2666+ """Test that the call are relied correctly."""
2667+
2668+ def setUp(self):
2669+ """Setup tests."""
2670+ super(SSOLoginMockedTestCase, self).setUp()
2671+ self.root = self.mocker.mock()
2672+ mockbusname = self.mocker.mock()
2673+ mockbus = self.mocker.mock()
2674+ mockbusname.get_bus()
2675+ self.mocker.result(mockbus)
2676+ self.login = SSOLogin(mockbus)
2677+ self.login.root = self.root
2678+ self.mocker.reset()
2679+
2680+ def test_generate_captcha(self):
2681+ """Test that the call is relayed."""
2682+ app_name = 'app'
2683+ filename = 'file'
2684+ self.root.generate_captcha(app_name, filename, MATCH(callable),
2685+ MATCH(callable), MATCH(callable))
2686+ self.mocker.replay()
2687+ self.login.generate_captcha(app_name, filename)
2688+
2689+ def test_register_user(self):
2690+ """Test that the call is relayed."""
2691+ app_name = 'app'
2692+ email = 'email'
2693+ password = 'pwd'
2694+ name = 'display name'
2695+ captcha_id = 'id'
2696+ captcha_solution = 'hello'
2697+ self.root.register_user(app_name, email, password, name, captcha_id,
2698+ captcha_solution, MATCH(callable),
2699+ MATCH(callable), MATCH(callable))
2700+ self.mocker.replay()
2701+ self.login.register_user(app_name, email, password, name, captcha_id,
2702+ captcha_solution)
2703+
2704+ def test_login(self):
2705+ """Test that the call is relayed."""
2706+ app_name = 'app'
2707+ email = 'email'
2708+ password = 'password'
2709+ self.root.login(app_name, email, password, MATCH(callable),
2710+ MATCH(callable), MATCH(callable),
2711+ MATCH(callable))
2712+ self.mocker.mock()
2713+ self.mocker.replay()
2714+ self.login.login(app_name, email, password)
2715+
2716+ def test_validate_email(self):
2717+ """Test that the call is relayed."""
2718+ app_name = 'app'
2719+ email = 'email'
2720+ password = 'passwrd'
2721+ email_token = 'token'
2722+ self.root.validate_email(app_name, email, password, email_token,
2723+ MATCH(callable), MATCH(callable),
2724+ MATCH(callable))
2725+ self.mocker.replay()
2726+ self.login.validate_email(app_name, email, password, email_token)
2727+
2728+ def test_request_password_reset_tolen(self):
2729+ """Test that the call is relayed."""
2730+ app_name = 'app'
2731+ email = 'email'
2732+ self.root.request_password_reset_token(app_name, email,
2733+ MATCH(callable),
2734+ MATCH(callable),
2735+ MATCH(callable))
2736+ self.mocker.replay()
2737+ self.login.request_password_reset_token(app_name, email)
2738+
2739+ def test_set_new_password(self):
2740+ """Test that the call is relayed."""
2741+ app_name = 'app'
2742+ email = 'email'
2743+ token = 'token'
2744+ new_password = 'new'
2745+ self.root.set_new_password(app_name, email, token, new_password,
2746+ MATCH(callable), MATCH(callable),
2747+ MATCH(callable))
2748+ self.mocker.replay()
2749+ self.login.set_new_password(app_name, email, token, new_password)
2750+
2751+
2752+class SSOCredentialsMockedTestCase(MockerTestCase):
2753+ """Test that the call are relied correctly."""
2754+
2755+ def setUp(self):
2756+ """Setup tests."""
2757+ super(SSOCredentialsMockedTestCase, self).setUp()
2758+ self.root = self.mocker.mock()
2759+ mockbusname = self.mocker.mock()
2760+ mockbus = self.mocker.mock()
2761+ mockbusname.get_bus()
2762+ self.mocker.result(mockbus)
2763+ self.cred = SSOCredentials(mockbus)
2764+ self.cred.root = self.root
2765+ self.mocker.reset()
2766+
2767+ def test_find_credentials(self):
2768+ """Test that the call is relayed."""
2769+ app_name = 'app'
2770+ result_cb = error_cb = lambda: None
2771+ self.root.find_credentials(app_name, result_cb, error_cb)
2772+ self.mocker.mock()
2773+ self.mocker.replay()
2774+ self.cred.find_credentials(app_name, result_cb, error_cb)
2775+
2776+ def test_login_or_register_to_get_credentials(self):
2777+ """Test that the call is relayed."""
2778+ app_name = 'app'
2779+ terms = 'terms'
2780+ help_text = 'help'
2781+ window_id = 'id'
2782+ self.root.login_or_register_to_get_credentials(app_name, terms,
2783+ help_text, window_id,
2784+ MATCH(callable),
2785+ MATCH(callable),
2786+ MATCH(callable))
2787+ self.mocker.replay()
2788+ self.cred.login_or_register_to_get_credentials(app_name, terms,
2789+ help_text, window_id)
2790+
2791+ def test_clear_token(self):
2792+ """Test that the call is relayed."""
2793+ app_name = 'app'
2794+ result_cb = error_cb = lambda: None
2795+ self.root.clear_token(app_name, result_cb, error_cb)
2796+ self.mocker.replay()
2797+ self.cred.clear_token(app_name, result_cb, error_cb)
2798+
2799+
2800+class CredentialsManagementMockedTestCase(MockerTestCase):
2801+ """Test that the call are relied correctly."""
2802+
2803+ def setUp(self):
2804+ """Setup tests."""
2805+ super(CredentialsManagementMockedTestCase, self).setUp()
2806+ self.root = self.mocker.mock()
2807+ self.cred = CredentialsManagement(None, None)
2808+ self.cred.root = self.root
2809+
2810+ def test_find_credentials(self):
2811+ """Test that the call is relayed."""
2812+ app_name = 'app'
2813+ args = 'args'
2814+ self.root.find_credentials(app_name, args, MATCH(callable),
2815+ MATCH(callable))
2816+ self.mocker.replay()
2817+ self.cred.find_credentials(app_name, args)
2818+
2819+ def test_clear_credentials(self):
2820+ """Test that the call is relayed."""
2821+ app_name = 'app'
2822+ args = 'args'
2823+ self.root.clear_credentials(app_name, args, MATCH(callable),
2824+ MATCH(callable))
2825+ self.mocker.replay()
2826+ self.cred.clear_credentials(app_name, args)
2827+
2828+ def test_store_credentials(self):
2829+ """Test that the call is relayed."""
2830+ app_name = 'app'
2831+ args = 'args'
2832+ self.root.store_credentials(app_name, args, MATCH(callable),
2833+ MATCH(callable))
2834+ self.mocker.replay()
2835+ self.cred.store_credentials(app_name, args)
2836+
2837+ def test_register(self):
2838+ """Test that the call is relayed."""
2839+ app_name = 'app'
2840+ args = 'args'
2841+ self.root.register(app_name, args)
2842+ self.mocker.replay()
2843+ self.cred.register(app_name, args)
2844+
2845+ def test_login(self):
2846+ """Test that the call is relayed."""
2847+ app_name = 'app'
2848+ args = 'args'
2849+ self.root.login(app_name, args)
2850+ self.mocker.replay()
2851+ self.cred.login(app_name, args)
2852
2853=== added file 'ubuntu_sso/main/tests/test_linux.py'
2854--- ubuntu_sso/main/tests/test_linux.py 1970-01-01 00:00:00 +0000
2855+++ ubuntu_sso/main/tests/test_linux.py 2011-03-23 14:22:30 +0000
2856@@ -0,0 +1,1306 @@
2857+# -*- coding: utf-8 -*-
2858+#
2859+# test_main - tests for ubuntu_sso.main
2860+#
2861+# Author: Natalia Bidart <natalia.bidart@canonical.com>
2862+# Author: Alejandro J. Cura <alecu@canonical.com>
2863+#
2864+# Copyright 2009-2010 Canonical Ltd.
2865+#
2866+# This program is free software: you can redistribute it and/or modify it
2867+# under the terms of the GNU General Public License version 3, as published
2868+# by the Free Software Foundation.
2869+#
2870+# This program is distributed in the hope that it will be useful, but
2871+# WITHOUT ANY WARRANTY; without even the implied warranties of
2872+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2873+# PURPOSE. See the GNU General Public License for more details.
2874+#
2875+# You should have received a copy of the GNU General Public License along
2876+# with this program. If not, see <http://www.gnu.org/licenses/>.
2877+"""Tests for the main SSO client code."""
2878+
2879+import logging
2880+import os
2881+
2882+from mocker import Mocker, MockerTestCase, ARGS, KWARGS
2883+from twisted.internet import defer
2884+from twisted.internet.defer import Deferred, inlineCallbacks
2885+from twisted.trial.unittest import TestCase
2886+from ubuntuone.devtools.handlers import MementoHandler
2887+
2888+import ubuntu_sso.keyring
2889+import ubuntu_sso.main
2890+import ubuntu_sso.main.linux
2891+
2892+from ubuntu_sso import DBUS_CREDENTIALS_IFACE
2893+from ubuntu_sso.keyring import U1_APP_NAME
2894+from ubuntu_sso.main import (U1_PING_URL, except_to_errdict,
2895+ CredentialsManagement, SSOCredentials, SSOLogin)
2896+from ubuntu_sso.main.linux import TIMEOUT_INTERVAL, blocking
2897+from ubuntu_sso.main import (HELP_TEXT_KEY, PING_URL_KEY,
2898+ TC_URL_KEY, UI_CLASS_KEY, UI_MODULE_KEY, WINDOW_ID_KEY,
2899+ SUCCESS_CB_KEY, ERROR_CB_KEY, DENIAL_CB_KEY)
2900+from ubuntu_sso.tests import (APP_NAME, TC_URL, HELP_TEXT, CAPTCHA_ID,
2901+ CAPTCHA_SOLUTION, EMAIL, EMAIL_TOKEN, NAME, PASSWORD, PING_URL, TOKEN,
2902+ TOKEN_NAME, WINDOW_ID, TestCase)
2903+
2904+
2905+# Access to a protected member 'yyy' of a client class
2906+# pylint: disable=W0212
2907+
2908+
2909+class BlockingSampleException(Exception):
2910+ """The exception that will be thrown by the fake blocking."""
2911+
2912+
2913+def fake_ok_blocking(f, app, cb, eb):
2914+ """A fake blocking function that succeeds."""
2915+ cb(app, f())
2916+
2917+
2918+def fake_err_blocking(f, app, cb, eb):
2919+ """A fake blocking function that fails."""
2920+ try:
2921+ f()
2922+ except Exception, e: # pylint: disable=W0703
2923+ eb(app, except_to_errdict(e))
2924+ else:
2925+ eb(app, except_to_errdict(BlockingSampleException()))
2926+
2927+
2928+class SsoDbusTestCase(TestCase):
2929+ """Test the SSOLogin DBus interface."""
2930+
2931+ timeout = 2
2932+
2933+ def setUp(self):
2934+ """Create the mocking bus."""
2935+ self.mocker = Mocker()
2936+ self.mockbusname = self.mocker.mock()
2937+ mockbus = self.mocker.mock()
2938+ self.mockbusname.get_bus()
2939+ self.mocker.result(mockbus)
2940+ mockbus._register_object_path(ARGS)
2941+ self.mockprocessorclass = None
2942+
2943+ def ksc(keyring, k, val):
2944+ """Assert over token and app_name."""
2945+ self.assertEqual(k, APP_NAME)
2946+ self.assertEqual(val, TOKEN)
2947+ self.keyring_was_set = True
2948+ self.keyring_values = k, val
2949+ return defer.succeed(None)
2950+
2951+ self.patch(ubuntu_sso.main.Keyring, "set_credentials", ksc)
2952+ self.keyring_was_set = False
2953+ self.keyring_values = None
2954+
2955+ def tearDown(self):
2956+ """Verify the mocking bus and shut it down."""
2957+ self.mocker.verify()
2958+ self.mocker.restore()
2959+
2960+ def test_creation(self):
2961+ """Test that the object creation is successful."""
2962+ self.mocker.replay()
2963+ SSOLogin(self.mockbusname)
2964+
2965+ def create_mock_processor(self):
2966+ """Create a mock processor from a dummy processor class."""
2967+ self.mockprocessorclass = self.mocker.mock()
2968+ mockprocessor = self.mocker.mock()
2969+ self.mockprocessorclass(ARGS, KWARGS)
2970+ self.mocker.result(mockprocessor)
2971+ return mockprocessor
2972+
2973+ def test_generate_captcha(self):
2974+ """Test that the captcha method works ok."""
2975+ d = Deferred()
2976+ filename = "sample filename"
2977+ expected_result = "expected result"
2978+ self.create_mock_processor().generate_captcha(filename)
2979+ self.mocker.result(expected_result)
2980+ self.patch(ubuntu_sso.main.linux, "blocking", fake_ok_blocking)
2981+ self.mocker.replay()
2982+
2983+ def verify(app_name, result):
2984+ """The actual test."""
2985+ self.assertEqual(result, expected_result)
2986+ self.assertEqual(app_name, APP_NAME)
2987+ d.callback(result)
2988+
2989+ client = SSOLogin(self.mockbusname,
2990+ sso_login_processor_class=self.mockprocessorclass)
2991+ self.patch(client, "CaptchaGenerated", verify)
2992+ self.patch(client, "CaptchaGenerationError", d.errback)
2993+ client.generate_captcha(APP_NAME, filename)
2994+ return d
2995+
2996+ def test_generate_captcha_error(self):
2997+ """Test that the captcha method fails as expected."""
2998+ d = Deferred()
2999+ filename = "sample filename"
3000+ expected_result = "expected result"
3001+ self.create_mock_processor().generate_captcha(filename)
3002+ self.mocker.result(expected_result)
3003+ self.patch(ubuntu_sso.main.linux, "blocking", fake_err_blocking)
3004+ self.mocker.replay()
3005+
3006+ def verify(app_name, errdict):
3007+ """The actual test."""
3008+ self.assertEqual(errdict["errtype"], "BlockingSampleException")
3009+ self.assertEqual(app_name, APP_NAME)
3010+ d.callback("Ok")
3011+
3012+ client = SSOLogin(self.mockbusname,
3013+ sso_login_processor_class=self.mockprocessorclass)
3014+ self.patch(client, "CaptchaGenerated", d.errback)
3015+ self.patch(client, "CaptchaGenerationError", verify)
3016+ client.generate_captcha(APP_NAME, filename)
3017+ return d
3018+
3019+ def test_register_user(self):
3020+ """Test that the register_user method works ok."""
3021+ d = Deferred()
3022+ expected_result = "expected result"
3023+ self.create_mock_processor().register_user(EMAIL, PASSWORD, NAME,
3024+ CAPTCHA_ID, CAPTCHA_SOLUTION)
3025+ self.mocker.result(expected_result)
3026+ self.patch(ubuntu_sso.main.linux, "blocking", fake_ok_blocking)
3027+ self.mocker.replay()
3028+
3029+ def verify(app_name, result):
3030+ """The actual test."""
3031+ self.assertEqual(result, expected_result)
3032+ self.assertEqual(app_name, APP_NAME)
3033+ d.callback(result)
3034+
3035+ client = SSOLogin(self.mockbusname,
3036+ sso_login_processor_class=self.mockprocessorclass)
3037+ self.patch(client, "UserRegistered", verify)
3038+ self.patch(client, "UserRegistrationError", d.errback)
3039+ client.register_user(APP_NAME, EMAIL, PASSWORD, NAME, CAPTCHA_ID,
3040+ CAPTCHA_SOLUTION)
3041+ return d
3042+
3043+ def test_register_user_error(self):
3044+ """Test that the register_user method fails as expected."""
3045+ d = Deferred()
3046+ expected_result = "expected result"
3047+ self.create_mock_processor().register_user(EMAIL, PASSWORD, NAME,
3048+ CAPTCHA_ID, CAPTCHA_SOLUTION)
3049+ self.mocker.result(expected_result)
3050+ self.patch(ubuntu_sso.main.linux, "blocking", fake_err_blocking)
3051+ self.mocker.replay()
3052+
3053+ def verify(app_name, errdict):
3054+ """The actual test."""
3055+ self.assertEqual(errdict["errtype"], "BlockingSampleException")
3056+ self.assertEqual(app_name, APP_NAME)
3057+ d.callback("Ok")
3058+
3059+ client = SSOLogin(self.mockbusname,
3060+ sso_login_processor_class=self.mockprocessorclass)
3061+ self.patch(client, "UserRegistered", d.errback)
3062+ self.patch(client, "UserRegistrationError", verify)
3063+ client.register_user(APP_NAME, EMAIL, PASSWORD, NAME, CAPTCHA_ID,
3064+ CAPTCHA_SOLUTION)
3065+ return d
3066+
3067+ def test_login(self):
3068+ """Test that the login method works ok."""
3069+ d = Deferred()
3070+ processor = self.create_mock_processor()
3071+ processor.login(EMAIL, PASSWORD, TOKEN_NAME)
3072+ self.mocker.result(TOKEN)
3073+ processor.is_validated(TOKEN)
3074+ self.mocker.result(True)
3075+ self.patch(ubuntu_sso.main.linux, "blocking", fake_ok_blocking)
3076+ self.mocker.replay()
3077+
3078+ def verify(app_name, result):
3079+ """The actual test."""
3080+ self.assertEqual(result, EMAIL)
3081+ self.assertEqual(app_name, APP_NAME)
3082+ self.assertTrue(self.keyring_was_set, "The keyring should be set")
3083+ d.callback(result)
3084+
3085+ client = SSOLogin(self.mockbusname,
3086+ sso_login_processor_class=self.mockprocessorclass)
3087+ self.patch(client, "LoggedIn", verify)
3088+ self.patch(client, "LoginError", d.errback)
3089+ self.patch(client, "UserNotValidated", d.errback)
3090+ client.login(APP_NAME, EMAIL, PASSWORD)
3091+ return d
3092+
3093+ def test_login_user_not_validated(self):
3094+ """Test that the login sends EmailNotValidated signal."""
3095+ d = Deferred()
3096+ processor = self.create_mock_processor()
3097+ processor.login(EMAIL, PASSWORD, TOKEN_NAME)
3098+ self.mocker.result(TOKEN)
3099+ processor.is_validated(TOKEN)
3100+ self.mocker.result(False)
3101+ self.patch(ubuntu_sso.main.linux, "blocking", fake_ok_blocking)
3102+ self.mocker.replay()
3103+
3104+ def verify(app_name, email):
3105+ """The actual test."""
3106+ self.assertEqual(app_name, APP_NAME)
3107+ self.assertEqual(email, EMAIL)
3108+ self.assertFalse(self.keyring_was_set, "Keyring should not be set")
3109+ d.callback("Ok")
3110+
3111+ client = SSOLogin(self.mockbusname,
3112+ sso_login_processor_class=self.mockprocessorclass)
3113+ self.patch(client, "LoggedIn", d.errback)
3114+ self.patch(client, "LoginError", d.errback)
3115+ self.patch(client, "UserNotValidated", verify)
3116+ client.login(APP_NAME, EMAIL, PASSWORD)
3117+ return d
3118+
3119+ def test_login_error_get_token_name(self):
3120+ """The login method fails as expected when get_token_name fails."""
3121+ d = Deferred()
3122+ self.create_mock_processor()
3123+ self.patch(ubuntu_sso.main.linux, "blocking", fake_err_blocking)
3124+
3125+ def fake_gtn(*args):
3126+ """A fake get_token_name that fails."""
3127+ raise BlockingSampleException()
3128+
3129+ self.patch(ubuntu_sso.main, "get_token_name", fake_gtn)
3130+ self.mocker.replay()
3131+
3132+ def verify(app_name, errdict):
3133+ """The actual test."""
3134+ self.assertEqual(app_name, APP_NAME)
3135+ self.assertEqual(errdict["errtype"], "BlockingSampleException")
3136+ self.assertFalse(self.keyring_was_set, "Keyring should not be set")
3137+ d.callback("Ok")
3138+
3139+ client = SSOLogin(self.mockbusname,
3140+ sso_login_processor_class=self.mockprocessorclass)
3141+ self.patch(client, "LoggedIn", d.errback)
3142+ self.patch(client, "LoginError", verify)
3143+ self.patch(client, "UserNotValidated", d.errback)
3144+ client.login(APP_NAME, EMAIL, PASSWORD)
3145+ return d
3146+
3147+ def test_login_error_set_credentials(self):
3148+ """The login method fails as expected when set_credentials fails."""
3149+ d = Deferred()
3150+ processor = self.create_mock_processor()
3151+ processor.login(EMAIL, PASSWORD, TOKEN_NAME)
3152+ self.mocker.result(TOKEN)
3153+ processor.is_validated(TOKEN)
3154+ self.mocker.result(True)
3155+
3156+ self.patch(ubuntu_sso.main.linux, "blocking", fake_ok_blocking)
3157+
3158+ def fake_set_creds(*args):
3159+ """A fake Keyring.set_credentials that fails."""
3160+ return defer.fail(BlockingSampleException())
3161+
3162+ self.patch(ubuntu_sso.main.Keyring, "set_credentials", fake_set_creds)
3163+ self.mocker.replay()
3164+
3165+ def verify(app_name, errdict):
3166+ """The actual test."""
3167+ self.assertEqual(app_name, APP_NAME)
3168+ self.assertEqual(errdict["errtype"], "BlockingSampleException")
3169+ self.assertFalse(self.keyring_was_set, "Keyring should not be set")
3170+ d.callback("Ok")
3171+
3172+ client = SSOLogin(self.mockbusname,
3173+ sso_login_processor_class=self.mockprocessorclass)
3174+ fail = lambda app, res: d.errback((app, res))
3175+ self.patch(client, "LoggedIn", fail)
3176+ self.patch(client, "LoginError", verify)
3177+ self.patch(client, "UserNotValidated", fail)
3178+ client.login(APP_NAME, EMAIL, PASSWORD)
3179+ return d
3180+
3181+ def test_validate_email(self):
3182+ """Test that the validate_email method works ok."""
3183+ d = Deferred()
3184+ self.create_mock_processor().validate_email(EMAIL, PASSWORD,
3185+ EMAIL_TOKEN, TOKEN_NAME)
3186+ self.mocker.result(TOKEN)
3187+ self.patch(ubuntu_sso.main.linux, "blocking", fake_ok_blocking)
3188+ self.mocker.replay()
3189+
3190+ def verify(app_name, result):
3191+ """The actual test."""
3192+ self.assertEqual(result, EMAIL)
3193+ self.assertEqual(app_name, APP_NAME)
3194+ self.assertTrue(self.keyring_was_set, "The keyring should be set")
3195+ d.callback(result)
3196+
3197+ client = SSOLogin(self.mockbusname,
3198+ sso_login_processor_class=self.mockprocessorclass)
3199+ self.patch(client, "EmailValidated", verify)
3200+ self.patch(client, "EmailValidationError", d.errback)
3201+ client.validate_email(APP_NAME, EMAIL, PASSWORD, EMAIL_TOKEN)
3202+ return d
3203+
3204+ def test_validate_email_error(self):
3205+ """Test that the validate_email method fails as expected."""
3206+ d = Deferred()
3207+ self.create_mock_processor()
3208+ self.patch(ubuntu_sso.main.linux, "blocking", fake_err_blocking)
3209+
3210+ def fake_gtn(*args):
3211+ """A fake get_token_name that fails."""
3212+ raise BlockingSampleException()
3213+
3214+ self.patch(ubuntu_sso.main, "get_token_name", fake_gtn)
3215+ self.mocker.replay()
3216+
3217+ def verify(app_name, errdict):
3218+ """The actual test."""
3219+ self.assertEqual(app_name, APP_NAME)
3220+ self.assertEqual(errdict["errtype"], "BlockingSampleException")
3221+ self.assertFalse(self.keyring_was_set, "Keyring should not be set")
3222+ d.callback("Ok")
3223+
3224+ client = SSOLogin(self.mockbusname,
3225+ sso_login_processor_class=self.mockprocessorclass)
3226+ self.patch(client, "EmailValidated", d.errback)
3227+ self.patch(client, "EmailValidationError", verify)
3228+ client.validate_email(APP_NAME, EMAIL, PASSWORD, EMAIL_TOKEN)
3229+ return d
3230+
3231+ def test_request_password_reset_token(self):
3232+ """Test that the request_password_reset_token method works ok."""
3233+ d = Deferred()
3234+ processor = self.create_mock_processor()
3235+ processor.request_password_reset_token(EMAIL)
3236+ self.patch(ubuntu_sso.main.linux, "blocking", fake_ok_blocking)
3237+ self.mocker.result(EMAIL)
3238+ self.mocker.replay()
3239+
3240+ def verify(app_name, result):
3241+ """The actual test."""
3242+ self.assertEqual(result, EMAIL)
3243+ self.assertEqual(app_name, APP_NAME)
3244+ d.callback(result)
3245+
3246+ client = SSOLogin(self.mockbusname,
3247+ sso_login_processor_class=self.mockprocessorclass)
3248+ self.patch(client, "PasswordResetTokenSent", verify)
3249+ self.patch(client, "PasswordResetError", d.errback)
3250+ client.request_password_reset_token(APP_NAME, EMAIL)
3251+ return d
3252+
3253+ def test_request_password_reset_token_error(self):
3254+ """Test the request_password_reset_token method fails as expected."""
3255+ d = Deferred()
3256+
3257+ self.create_mock_processor().request_password_reset_token(EMAIL)
3258+ self.mocker.result(EMAIL)
3259+ self.patch(ubuntu_sso.main.linux, "blocking", fake_err_blocking)
3260+ self.mocker.replay()
3261+
3262+ def verify(app_name, errdict):
3263+ """The actual test."""
3264+ self.assertEqual(errdict["errtype"], "BlockingSampleException")
3265+ self.assertEqual(app_name, APP_NAME)
3266+ d.callback("Ok")
3267+
3268+ client = SSOLogin(self.mockbusname,
3269+ sso_login_processor_class=self.mockprocessorclass)
3270+ self.patch(client, "PasswordResetTokenSent", d.errback)
3271+ self.patch(client, "PasswordResetError", verify)
3272+ client.request_password_reset_token(APP_NAME, EMAIL)
3273+ return d
3274+
3275+ def test_set_new_password(self):
3276+ """Test that the set_new_password method works ok."""
3277+ d = Deferred()
3278+ self.create_mock_processor().set_new_password(EMAIL, EMAIL_TOKEN,
3279+ PASSWORD)
3280+ self.mocker.result(EMAIL)
3281+ self.patch(ubuntu_sso.main.linux, "blocking", fake_ok_blocking)
3282+ self.mocker.replay()
3283+
3284+ def verify(app_name, result):
3285+ """The actual test."""
3286+ self.assertEqual(result, EMAIL)
3287+ self.assertEqual(app_name, APP_NAME)
3288+ d.callback(result)
3289+
3290+ client = SSOLogin(self.mockbusname,
3291+ sso_login_processor_class=self.mockprocessorclass)
3292+ self.patch(client, "PasswordChanged", verify)
3293+ self.patch(client, "PasswordChangeError", d.errback)
3294+ client.set_new_password(APP_NAME, EMAIL, EMAIL_TOKEN, PASSWORD)
3295+ return d
3296+
3297+ def test_set_new_password_error(self):
3298+ """Test that the set_new_password method fails as expected."""
3299+ d = Deferred()
3300+ expected_result = "expected result"
3301+
3302+ self.create_mock_processor().set_new_password(EMAIL, EMAIL_TOKEN,
3303+ PASSWORD)
3304+ self.mocker.result(expected_result)
3305+ self.patch(ubuntu_sso.main.linux, "blocking", fake_err_blocking)
3306+ self.mocker.replay()
3307+
3308+ def verify(app_name, errdict):
3309+ """The actual test."""
3310+ self.assertEqual(errdict["errtype"], "BlockingSampleException")
3311+ self.assertEqual(app_name, APP_NAME)
3312+ d.callback("Ok")
3313+
3314+ client = SSOLogin(self.mockbusname,
3315+ sso_login_processor_class=self.mockprocessorclass)
3316+ self.patch(client, "PasswordChanged", d.errback)
3317+ self.patch(client, "PasswordChangeError", verify)
3318+ client.set_new_password(APP_NAME, EMAIL, EMAIL_TOKEN, PASSWORD)
3319+ return d
3320+
3321+
3322+class BlockingFunctionTestCase(TestCase):
3323+ """Tests for the "blocking" function."""
3324+
3325+ timeout = 5
3326+
3327+ def test_blocking(self):
3328+ """Test the normal behaviour."""
3329+ d = Deferred()
3330+ expected_result = "expected result"
3331+
3332+ def f():
3333+ """No failure."""
3334+ return expected_result
3335+
3336+ def verify(app_name, result):
3337+ """The actual test."""
3338+ self.assertEqual(result, expected_result)
3339+ self.assertEqual(app_name, APP_NAME)
3340+ d.callback(result)
3341+
3342+ blocking(f, APP_NAME, verify, d.errback)
3343+ return d
3344+
3345+ def test_blocking_error(self):
3346+ """Test the behaviour when an Exception is raised."""
3347+ d = Deferred()
3348+ expected_error_message = "expected error message"
3349+
3350+ def f():
3351+ """Failure."""
3352+ raise BlockingSampleException(expected_error_message)
3353+
3354+ def verify(app_name, errdict):
3355+ """The actual test."""
3356+ self.assertEqual(app_name, APP_NAME)
3357+ self.assertEqual(errdict["errtype"], "BlockingSampleException")
3358+ self.assertEqual(errdict["message"], expected_error_message)
3359+ d.callback("Ok")
3360+
3361+ blocking(f, APP_NAME, d.errback, verify)
3362+ return d
3363+
3364+
3365+class TestExceptToErrdictException(Exception):
3366+ """A dummy exception for the following testcase."""
3367+
3368+
3369+class ExceptToErrdictTestCase(TestCase):
3370+ """Tests for the except_to_errdict function."""
3371+
3372+ def test_first_arg_is_dict(self):
3373+ """If the first arg is a dict, use it as the base dict."""
3374+ sample_dict = {
3375+ "errorcode1": "error message 1",
3376+ "errorcode2": "error message 2",
3377+ "errorcode3": "error message 3",
3378+ }
3379+ e = TestExceptToErrdictException(sample_dict)
3380+ result = except_to_errdict(e)
3381+
3382+ self.assertEqual(result["errtype"], e.__class__.__name__)
3383+ for k in sample_dict.keys():
3384+ self.assertIn(k, result)
3385+ self.assertEqual(result[k], sample_dict[k])
3386+
3387+ def test_first_arg_is_str(self):
3388+ """If the first arg is a str, use it as the message."""
3389+ sample_string = "a sample string"
3390+ e = TestExceptToErrdictException(sample_string)
3391+ result = except_to_errdict(e)
3392+ self.assertEqual(result["errtype"], e.__class__.__name__)
3393+ self.assertEqual(result["message"], sample_string)
3394+
3395+ def test_first_arg_is_unicode(self):
3396+ """If the first arg is a unicode, use it as the message."""
3397+ sample_string = u"a sample string"
3398+ e = TestExceptToErrdictException(sample_string)
3399+ result = except_to_errdict(e)
3400+ self.assertEqual(result["errtype"], e.__class__.__name__)
3401+ self.assertEqual(result["message"], sample_string)
3402+
3403+ def test_no_args_at_all(self):
3404+ """If there are no args, use the class docstring."""
3405+ e = TestExceptToErrdictException()
3406+ result = except_to_errdict(e)
3407+ self.assertEqual(result["errtype"], e.__class__.__name__)
3408+ self.assertEqual(result["message"], e.__class__.__doc__)
3409+
3410+ def test_some_other_thing_as_first_arg(self):
3411+ """If first arg is not basestring nor dict, then repr all args."""
3412+ sample_args = (None, u"unicode2\ufffd", "errorcode3")
3413+ e = TestExceptToErrdictException(*sample_args)
3414+ result = except_to_errdict(e)
3415+ self.assertEqual(result["errtype"], e.__class__.__name__)
3416+
3417+
3418+class RegisterSampleException(Exception):
3419+ """A mock exception thrown just when testing."""
3420+
3421+
3422+class ApplicationCredentialsTestCase(TestCase, MockerTestCase):
3423+ """Tests for the ApplicationCredentials related DBus methods."""
3424+
3425+ timeout = 5
3426+
3427+ def setUp(self):
3428+ MockerTestCase.setUp(self)
3429+
3430+ self.client = SSOCredentials(self.mocker.mock())
3431+
3432+ mock_class = self.mocker.replace("ubuntu_sso.credentials.Credentials")
3433+ mock_class(app_name=APP_NAME)
3434+ self.creds_obj = self.mocker.mock()
3435+ self.mocker.result(self.creds_obj)
3436+
3437+ @inlineCallbacks
3438+ def test_find_credentials(self):
3439+ """find_credentials immediately returns the token when found."""
3440+ expected_creds = "expected creds"
3441+ self.creds_obj.find_credentials()
3442+ self.mocker.result(defer.succeed(expected_creds))
3443+ self.mocker.replay()
3444+
3445+ d = Deferred()
3446+ self.client.find_credentials(APP_NAME, d.callback, d.errback)
3447+ creds = yield d
3448+ self.assertEqual(creds, expected_creds)
3449+
3450+ @inlineCallbacks
3451+ def test_credentials_not_found(self):
3452+ """find_credentials immediately returns {} when no creds found."""
3453+ expected_creds = {}
3454+ self.creds_obj.find_credentials()
3455+ self.mocker.result(defer.succeed(expected_creds))
3456+ self.mocker.replay()
3457+
3458+ d = Deferred()
3459+ self.client.find_credentials(APP_NAME, d.callback, d.errback)
3460+ creds = yield d
3461+ self.assertEqual(creds, expected_creds)
3462+
3463+
3464+class ApplicationCredentialsGUITestCase(TestCase, MockerTestCase):
3465+ """Tests for the ApplicationCredentials register/login DBus method."""
3466+
3467+ app_name = APP_NAME
3468+ ping_url = None
3469+
3470+ def setUp(self):
3471+ MockerTestCase.setUp(self)
3472+ self.client = SSOCredentials(self.mocker.mock())
3473+ self.args = {PING_URL_KEY: self.ping_url,
3474+ TC_URL_KEY: TC_URL, HELP_TEXT_KEY: HELP_TEXT,
3475+ WINDOW_ID_KEY: WINDOW_ID,
3476+ SUCCESS_CB_KEY: self.client.CredentialsFound,
3477+ ERROR_CB_KEY: self.client._process_error,
3478+ DENIAL_CB_KEY: self.client.AuthorizationDenied}
3479+
3480+ def test_login_or_register(self):
3481+ """login_or_register is correct."""
3482+ mock_class = self.mocker.replace("ubuntu_sso.credentials.Credentials")
3483+ mock_class(app_name=self.app_name, **self.args)
3484+ creds_obj = self.mocker.mock()
3485+ self.mocker.result(creds_obj)
3486+
3487+ creds_obj.register()
3488+ self.mocker.replay()
3489+
3490+ args = (self.app_name, TC_URL, HELP_TEXT, WINDOW_ID)
3491+ self.client.login_or_register_to_get_credentials(*args)
3492+
3493+ def test_login_only(self):
3494+ """login_or_register is correct."""
3495+ self.args[TC_URL_KEY] = None
3496+ mock_class = self.mocker.replace("ubuntu_sso.credentials.Credentials")
3497+ mock_class(app_name=self.app_name, **self.args)
3498+ creds_obj = self.mocker.mock()
3499+ self.mocker.result(creds_obj)
3500+
3501+ creds_obj.login()
3502+ self.mocker.replay()
3503+
3504+ args = (self.app_name, HELP_TEXT, WINDOW_ID)
3505+ self.client.login_to_get_credentials(*args)
3506+
3507+
3508+class ApplicationCredentialsU1TestCase(ApplicationCredentialsGUITestCase):
3509+ """Tests for the ApplicationCredentials register/login DBus method.
3510+
3511+ Specifically for APP_NAME == U1_APP_NAME.
3512+
3513+ """
3514+
3515+ app_name = U1_APP_NAME
3516+ ping_url = U1_PING_URL
3517+
3518+
3519+class ApplicationCredentialsClearTokenTestCase(TestCase, MockerTestCase):
3520+ """Tests for the ApplicationCredentials related DBus methods."""
3521+
3522+ def test_clear_token(self):
3523+ """Check that clear_token tries removing the correct token."""
3524+ mock_class = self.mocker.replace("ubuntu_sso.credentials.Credentials")
3525+ mock_class(app_name=APP_NAME)
3526+ creds_obj = self.mocker.mock()
3527+ self.mocker.result(creds_obj)
3528+
3529+ creds_obj.clear_credentials()
3530+ self.mocker.result(defer.succeed(None))
3531+ self.mocker.replay()
3532+
3533+ client = SSOCredentials(self.mocker.mock())
3534+ client.clear_token(APP_NAME)
3535+
3536+
3537+class EnvironOverridesTestCase(TestCase):
3538+ """Some URLs can be set from the environment for testing/QA purposes."""
3539+
3540+ def test_override_ping_url(self):
3541+ """The ping url can be set from the environ via USSOC_PING_URL."""
3542+ fake_url = 'this is not really a URL'
3543+ old_url = os.environ.get('USSOC_PING_URL')
3544+ os.environ['USSOC_PING_URL'] = fake_url
3545+ try:
3546+ creds = SSOCredentials(None)
3547+ self.assertEqual(creds.root.ping_url, fake_url)
3548+ finally:
3549+ if old_url:
3550+ os.environ['USSOC_PING_URL'] = old_url
3551+ else:
3552+ del os.environ['USSOC_PING_URL']
3553+
3554+ def test_no_override_ping_url(self):
3555+ """If the environ is unset, the default ping url is used."""
3556+ creds = SSOCredentials(None)
3557+ self.assertEqual(creds.root.ping_url, U1_PING_URL)
3558+
3559+
3560+class CredentialsManagementTestCase(TestCase):
3561+ """Tests for the CredentialsManagement DBus interface."""
3562+
3563+ timeout = 2
3564+ base_args = {HELP_TEXT_KEY: HELP_TEXT, PING_URL_KEY: PING_URL,
3565+ TC_URL_KEY: TC_URL, WINDOW_ID_KEY: WINDOW_ID,
3566+ UI_CLASS_KEY: 'SuperUI', UI_MODULE_KEY: 'foo.bar.baz',
3567+ }
3568+
3569+ def setUp(self):
3570+ super(CredentialsManagementTestCase, self).setUp()
3571+
3572+ self.mocker = Mocker()
3573+ self.client = CredentialsManagement(timeout_func=lambda *a: None,
3574+ shutdown_func=lambda *a: None)
3575+ self.args = {}
3576+ self.cred_args = {}
3577+
3578+ self.memento = MementoHandler()
3579+ self.memento.setLevel(logging.DEBUG)
3580+ ubuntu_sso.main.logger.addHandler(self.memento)
3581+
3582+ def tearDown(self):
3583+ """Verify the mocking stuff and shut it down."""
3584+ self.mocker.verify()
3585+ self.mocker.restore()
3586+ super(CredentialsManagementTestCase, self).tearDown()
3587+
3588+ def assert_dbus_method_correct(self, method):
3589+ """Check that 'method' is a dbus method with proper signatures."""
3590+ self.assertTrue(method._dbus_is_method)
3591+ self.assertEqual(method._dbus_interface, DBUS_CREDENTIALS_IFACE)
3592+ self.assertEqual(method._dbus_in_signature, 'sa{ss}')
3593+ self.assertEqual(method._dbus_out_signature, '')
3594+
3595+ def create_mock_backend(self):
3596+ """Create a mock backend."""
3597+ mock_class = self.mocker.replace("ubuntu_sso.credentials.Credentials")
3598+ mock_class(APP_NAME, **self.cred_args)
3599+ creds_obj = self.mocker.mock()
3600+ self.mocker.result(creds_obj)
3601+
3602+ return creds_obj
3603+
3604+ def test_is_dbus_object(self):
3605+ """CredentialsManagement is a Dbus object."""
3606+ self.assertIsInstance(self.client,
3607+ ubuntu_sso.main.linux.dbus.service.Object)
3608+
3609+
3610+class FakeCredentials(object):
3611+ """A very dummy Credentials object."""
3612+
3613+ def __init__(self, *a, **kw):
3614+ self.find_credentials = lambda *a: defer.succeed(TOKEN)
3615+ self.clear_credentials = lambda *a: defer.succeed(None)
3616+ self.store_credentials = lambda *a: defer.succeed(None)
3617+ self.login = self.register = lambda *a: None
3618+
3619+
3620+class CredentialsManagementRefCountingTestCase(CredentialsManagementTestCase):
3621+ """Tests for the CredentialsManagement ref counting."""
3622+
3623+ def setUp(self):
3624+ super(CredentialsManagementRefCountingTestCase, self).setUp()
3625+ self.patch(ubuntu_sso.main, 'Credentials', FakeCredentials)
3626+
3627+ def test_ref_counting(self):
3628+ """Ref counting is in place."""
3629+ self.assertEqual(self.client.ref_count, 0)
3630+
3631+ def test_find_credentials(self):
3632+ """Keep proper track of on going requests."""
3633+ d = Deferred()
3634+
3635+ def verify(*args):
3636+ """Make the check."""
3637+ self.assertEqual(self.client.ref_count, 1)
3638+ d.callback(True)
3639+
3640+ self.patch(self.client, 'CredentialsFound', verify)
3641+ self.client.find_credentials(APP_NAME, self.args)
3642+
3643+ return d
3644+
3645+ def test_clear_credentials(self):
3646+ """Keep proper track of on going requests."""
3647+ d = Deferred()
3648+
3649+ def verify(*args):
3650+ """Make the check."""
3651+ self.assertEqual(self.client.ref_count, 1)
3652+ d.callback(True)
3653+
3654+ self.patch(self.client, 'CredentialsCleared', verify)
3655+ self.client.clear_credentials(APP_NAME, self.args)
3656+
3657+ return d
3658+
3659+ def test_store_credentials(self):
3660+ """Keep proper track of on going requests."""
3661+ d = Deferred()
3662+
3663+ def verify(*args):
3664+ """Make the check."""
3665+ self.assertEqual(self.client.ref_count, 1)
3666+ d.callback(True)
3667+
3668+ self.patch(self.client, 'CredentialsStored', verify)
3669+ self.client.store_credentials(APP_NAME, self.args)
3670+
3671+ return d
3672+
3673+ def test_register(self):
3674+ """Keep proper track of on going requests."""
3675+ self.client.register(APP_NAME, self.args)
3676+
3677+ self.assertEqual(self.client.ref_count, 1)
3678+
3679+ def test_login(self):
3680+ """Keep proper track of on going requests."""
3681+ self.client.login(APP_NAME, self.args)
3682+
3683+ self.assertEqual(self.client.ref_count, 1)
3684+
3685+ def test_several_requests(self):
3686+ """Requests can be nested."""
3687+ self.client.login(APP_NAME, self.args)
3688+ self.client.register(APP_NAME, self.args)
3689+ self.client.login(APP_NAME, self.args)
3690+ self.client.register(APP_NAME, self.args)
3691+ self.client.register(APP_NAME, self.args)
3692+
3693+ self.assertEqual(self.client.ref_count, 5)
3694+
3695+ def test_credentials_found(self):
3696+ """Ref counter is decreased when a signal is sent."""
3697+ self.client.ref_count = 3
3698+ self.client.CredentialsFound(APP_NAME, TOKEN)
3699+
3700+ self.assertEqual(self.client.ref_count, 2)
3701+
3702+ def test_credentials_not_found(self):
3703+ """Ref counter is decreased when a signal is sent."""
3704+ self.client.ref_count = 3
3705+ self.client.CredentialsNotFound(APP_NAME)
3706+
3707+ self.assertEqual(self.client.ref_count, 2)
3708+
3709+ def test_credentials_cleared(self):
3710+ """Ref counter is decreased when a signal is sent."""
3711+ self.client.ref_count = 3
3712+ self.client.CredentialsCleared(APP_NAME)
3713+
3714+ self.assertEqual(self.client.ref_count, 2)
3715+
3716+ def test_credentials_stored(self):
3717+ """Ref counter is decreased when a signal is sent."""
3718+ self.client.ref_count = 3
3719+ self.client.CredentialsStored(APP_NAME)
3720+
3721+ self.assertEqual(self.client.ref_count, 2)
3722+
3723+ def test_credentials_error(self):
3724+ """Ref counter is decreased when a signal is sent."""
3725+ self.client.ref_count = 3
3726+ self.client.CredentialsError(APP_NAME, {'error_type': 'test'})
3727+
3728+ self.assertEqual(self.client.ref_count, 2)
3729+
3730+ def test_authorization_denied(self):
3731+ """Ref counter is decreased when a signal is sent."""
3732+ self.client.ref_count = 3
3733+ self.client.AuthorizationDenied(APP_NAME)
3734+
3735+ self.assertEqual(self.client.ref_count, 2)
3736+
3737+ def test_credentials_found_when_ref_count_is_not_positive(self):
3738+ """Ref counter is decreased when a signal is sent."""
3739+ self.client._ref_count = -3
3740+ self.client.CredentialsFound(APP_NAME, TOKEN)
3741+
3742+ self.assertEqual(self.client.ref_count, 0)
3743+ msg = 'Attempting to decrease ref_count to a negative value (-4).'
3744+ self.assertTrue(self.memento.check_warning(msg))
3745+
3746+ def test_credentials_not_found_when_ref_count_is_not_positive(self):
3747+ """Ref counter is decreased when a signal is sent."""
3748+ self.client._ref_count = -3
3749+ self.client.CredentialsNotFound(APP_NAME)
3750+
3751+ self.assertEqual(self.client.ref_count, 0)
3752+ msg = 'Attempting to decrease ref_count to a negative value (-4).'
3753+ self.assertTrue(self.memento.check_warning(msg))
3754+
3755+ def test_credentials_cleared_when_ref_count_is_not_positive(self):
3756+ """Ref counter is decreased when a signal is sent."""
3757+ self.client._ref_count = -3
3758+ self.client.CredentialsCleared(APP_NAME)
3759+
3760+ self.assertEqual(self.client.ref_count, 0)
3761+ msg = 'Attempting to decrease ref_count to a negative value (-4).'
3762+ self.assertTrue(self.memento.check_warning(msg))
3763+
3764+ def test_credentials_stored_when_ref_count_is_not_positive(self):
3765+ """Ref counter is decreased when a signal is sent."""
3766+ self.client._ref_count = -3
3767+ self.client.CredentialsStored(APP_NAME)
3768+
3769+ self.assertEqual(self.client.ref_count, 0)
3770+ msg = 'Attempting to decrease ref_count to a negative value (-4).'
3771+ self.assertTrue(self.memento.check_warning(msg))
3772+
3773+ def test_credentials_error_when_ref_count_is_not_positive(self):
3774+ """Ref counter is decreased when a signal is sent."""
3775+ self.client._ref_count = -3
3776+ self.client.CredentialsError(APP_NAME, {'error_type': 'test'})
3777+
3778+ self.assertEqual(self.client.ref_count, 0)
3779+ msg = 'Attempting to decrease ref_count to a negative value (-4).'
3780+ self.assertTrue(self.memento.check_warning(msg))
3781+
3782+ def test_autorization_denied_when_ref_count_is_not_positive(self):
3783+ """Ref counter is decreased when a signal is sent."""
3784+ self.client._ref_count = -3
3785+ self.client.AuthorizationDenied(APP_NAME)
3786+
3787+ self.assertEqual(self.client.ref_count, 0)
3788+ msg = 'Attempting to decrease ref_count to a negative value (-4).'
3789+ self.assertTrue(self.memento.check_warning(msg))
3790+
3791+ def test_on_zero_ref_count_shutdown(self):
3792+ """When ref count reaches 0, queue shutdown op."""
3793+ self.client.timeout_func = self._set_called
3794+ self.client.login(APP_NAME, self.args)
3795+ self.client.CredentialsFound(APP_NAME, TOKEN)
3796+
3797+ self.assertEqual(self._called,
3798+ ((TIMEOUT_INTERVAL, self.client.shutdown), {}))
3799+
3800+ def test_on_non_zero_ref_count_do_not_shutdown(self):
3801+ """If ref count is not 0, do not queue shutdown op."""
3802+ self.client.timeout_func = self._set_called
3803+ self.client.login(APP_NAME, self.args)
3804+
3805+ self.assertEqual(self._called, False)
3806+
3807+ def test_on_non_zero_ref_count_after_zero_do_not_shutdown(self):
3808+ """If the shutdown was queued, do not quit if counter is not zero."""
3809+
3810+ def fake_timeout_func(interval, func):
3811+ """Start a new request when the timer is started."""
3812+ self.client.register(APP_NAME, self.args)
3813+ assert self.client.ref_count > 0
3814+ func()
3815+
3816+ self.client.timeout_func = fake_timeout_func
3817+ self.client.shutdown_func = self._set_called
3818+
3819+ self.client.login(APP_NAME, self.args)
3820+ self.client.CredentialsFound(APP_NAME, TOKEN)
3821+ # counter reached 0, timeout_func was called
3822+
3823+ self.assertEqual(self._called, False, 'shutdown_func was not called')
3824+
3825+ def test_zero_ref_count_after_zero_do_shutdown(self):
3826+ """If the shutdown was queued, do quit if counter is zero."""
3827+
3828+ def fake_timeout_func(interval, func):
3829+ """Start a new request when the timer is started."""
3830+ assert self.client.ref_count == 0
3831+ func()
3832+
3833+ self.client.timeout_func = fake_timeout_func
3834+ self.client.shutdown_func = self._set_called
3835+
3836+ self.client.login(APP_NAME, self.args)
3837+ self.client.CredentialsFound(APP_NAME, TOKEN)
3838+ # counter reached 0, timeout_func was called
3839+
3840+ self.assertEqual(self._called, ((), {}), 'shutdown_func was called')
3841+
3842+
3843+class CredentialsManagementFindTestCase(CredentialsManagementTestCase):
3844+ """Tests for the CredentialsManagement find method."""
3845+
3846+ def test_find_credentials(self):
3847+ """The credentials are asked and returned in signals."""
3848+ self.create_mock_backend().find_credentials()
3849+ self.mocker.result(defer.succeed(None))
3850+ self.mocker.replay()
3851+
3852+ self.client.find_credentials(APP_NAME, self.args)
3853+ self.assert_dbus_method_correct(self.client.find_credentials)
3854+
3855+ def test_find_credentials_does_not_block_when_found(self):
3856+ """Calling find_credentials does not block but return thru signals.
3857+
3858+ If the creds are found, CredentialsFound is emitted.
3859+
3860+ """
3861+ d = Deferred()
3862+
3863+ def verify(app_name, creds):
3864+ """The actual test."""
3865+ try:
3866+ self.assertEqual(app_name, APP_NAME)
3867+ self.assertEqual(creds, TOKEN)
3868+ except Exception, e: # pylint: disable=W0703
3869+ d.errback(e)
3870+ else:
3871+ d.callback(creds)
3872+
3873+ self.patch(self.client, 'CredentialsFound', verify)
3874+ self.patch(self.client, 'CredentialsNotFound', d.errback)
3875+
3876+ self.create_mock_backend().find_credentials()
3877+ self.mocker.result(defer.succeed(TOKEN))
3878+ self.mocker.replay()
3879+
3880+ self.client.find_credentials(APP_NAME, self.args)
3881+ return d
3882+
3883+ def test_find_credentials_does_not_block_when_not_found(self):
3884+ """Calling find_credentials does not block but return thru signals.
3885+
3886+ If the creds are not found, CredentialsNotFound is emitted.
3887+
3888+ """
3889+ d = Deferred()
3890+
3891+ def verify(app_name):
3892+ """The actual test."""
3893+ try:
3894+ self.assertEqual(app_name, APP_NAME)
3895+ except Exception, e: # pylint: disable=W0703
3896+ d.errback(e)
3897+ else:
3898+ d.callback(app_name)
3899+
3900+ self.patch(self.client, 'CredentialsFound',
3901+ lambda app, creds: d.errback(app))
3902+ self.patch(self.client, 'CredentialsNotFound', verify)
3903+
3904+ self.create_mock_backend().find_credentials()
3905+ self.mocker.result(defer.succeed({}))
3906+ self.mocker.replay()
3907+
3908+ self.client.find_credentials(APP_NAME, self.args)
3909+ return d
3910+
3911+ def test_find_credentials_error(self):
3912+ """If find_credentials fails, CredentialsError is sent."""
3913+ d = Deferred()
3914+
3915+ def verify(app_name, errdict):
3916+ """The actual test."""
3917+ self.assertEqual(errdict["errtype"], "BlockingSampleException")
3918+ self.assertEqual(app_name, APP_NAME)
3919+ d.callback("Ok")
3920+
3921+ self.patch(self.client, 'CredentialsFound',
3922+ lambda app, creds: d.errback(app))
3923+ self.patch(self.client, 'CredentialsNotFound', d.errback)
3924+ self.patch(self.client, 'CredentialsError', verify)
3925+
3926+ self.create_mock_backend().find_credentials()
3927+ self.mocker.result(defer.fail(BlockingSampleException()))
3928+ self.mocker.replay()
3929+
3930+ self.client.find_credentials(APP_NAME, self.args)
3931+ return d
3932+
3933+
3934+class CredentialsManagementClearTestCase(CredentialsManagementTestCase):
3935+ """Tests for the CredentialsManagement clear method."""
3936+
3937+ def test_clear_credentials(self):
3938+ """The credentials are removed."""
3939+ self.create_mock_backend().clear_credentials()
3940+ self.mocker.result(defer.succeed(APP_NAME))
3941+ self.mocker.replay()
3942+
3943+ self.client.clear_credentials(APP_NAME, self.args)
3944+ self.assert_dbus_method_correct(self.client.clear_credentials)
3945+
3946+ def test_clear_credentials_does_not_block(self):
3947+ """Calling clear_credentials does not block but return thru signals."""
3948+ d = Deferred()
3949+
3950+ def verify(app_name):
3951+ """The actual test."""
3952+ try:
3953+ self.assertEqual(app_name, APP_NAME)
3954+ except Exception, e: # pylint: disable=W0703
3955+ d.errback(e)
3956+ else:
3957+ d.callback(app_name)
3958+
3959+ self.patch(self.client, 'CredentialsCleared', verify)
3960+ self.patch(self.client, 'CredentialsError',
3961+ lambda app, err: d.errback(app))
3962+
3963+ self.create_mock_backend().clear_credentials()
3964+ self.mocker.result(defer.succeed(APP_NAME))
3965+ self.mocker.replay()
3966+
3967+ self.client.clear_credentials(APP_NAME, self.args)
3968+ return d
3969+
3970+ def test_clear_credentials_error(self):
3971+ """If clear_credentials fails, CredentialsError is sent."""
3972+ d = Deferred()
3973+
3974+ def verify(app_name, errdict):
3975+ """The actual test."""
3976+ self.assertEqual(errdict["errtype"], "BlockingSampleException")
3977+ self.assertEqual(app_name, APP_NAME)
3978+ d.callback("Ok")
3979+
3980+ self.patch(self.client, 'CredentialsCleared', d.errback)
3981+ self.patch(self.client, 'CredentialsError', verify)
3982+
3983+ self.create_mock_backend().clear_credentials()
3984+ self.mocker.result(defer.fail(BlockingSampleException()))
3985+ self.mocker.replay()
3986+
3987+ self.client.clear_credentials(APP_NAME, self.args)
3988+ return d
3989+
3990+
3991+class CredentialsManagementStoreTestCase(CredentialsManagementTestCase):
3992+ """Tests for the CredentialsManagement store method."""
3993+
3994+ def test_store_credentials(self):
3995+ """The credentials are stored and the outcome is a signal."""
3996+ self.create_mock_backend().store_credentials(TOKEN)
3997+ self.mocker.result(defer.succeed(APP_NAME))
3998+ self.mocker.replay()
3999+
4000+ self.client.store_credentials(APP_NAME, TOKEN)
4001+ self.assert_dbus_method_correct(self.client.store_credentials)
4002+
4003+ def test_store_credentials_does_not_block(self):
4004+ """Calling store_credentials does not block but return thru signals.
4005+
4006+ If the creds are stored, CredentialsStored is emitted.
4007+
4008+ """
4009+ d = Deferred()
4010+
4011+ def verify(app_name):
4012+ """The actual test."""
4013+ try:
4014+ self.assertEqual(app_name, APP_NAME)
4015+ except Exception, e: # pylint: disable=W0703
4016+ d.errback(e)
4017+ else:
4018+ d.callback(app_name)
4019+
4020+ self.patch(self.client, 'CredentialsStored', verify)
4021+ self.patch(self.client, 'CredentialsError',
4022+ lambda app, err: d.errback(app))
4023+
4024+ self.create_mock_backend().store_credentials(TOKEN)
4025+ self.mocker.result(defer.succeed(APP_NAME))
4026+ self.mocker.replay()
4027+
4028+ self.client.store_credentials(APP_NAME, TOKEN)
4029+ return d
4030+
4031+ def test_store_credentials_error(self):
4032+ """If store_credentials fails, CredentialsError is sent."""
4033+ d = Deferred()
4034+
4035+ def verify(app_name, errdict):
4036+ """The actual test."""
4037+ self.assertEqual(errdict["errtype"], "BlockingSampleException")
4038+ self.assertEqual(app_name, APP_NAME)
4039+ d.callback("Ok")
4040+
4041+ self.patch(self.client, 'CredentialsStored', d.errback)
4042+ self.patch(self.client, 'CredentialsError', verify)
4043+
4044+ self.create_mock_backend().store_credentials(TOKEN)
4045+ self.mocker.result(defer.fail(BlockingSampleException()))
4046+ self.mocker.replay()
4047+
4048+ self.client.store_credentials(APP_NAME, TOKEN)
4049+ return d
4050+
4051+
4052+class CredentialsManagementOpsTestCase(CredentialsManagementTestCase):
4053+ """Tests for the CredentialsManagement login/register methods."""
4054+
4055+ def setUp(self):
4056+ super(CredentialsManagementOpsTestCase, self).setUp()
4057+ self.args = dict((k, str(v)) for k, v in self.base_args.iteritems())
4058+ self.cred_args = self.base_args.copy()
4059+ self.cred_args[SUCCESS_CB_KEY] = self.client.CredentialsFound
4060+ self.cred_args[ERROR_CB_KEY] = self.client.CredentialsError
4061+ self.cred_args[DENIAL_CB_KEY] = self.client.AuthorizationDenied
4062+
4063+ def test_register(self):
4064+ """The registration is correct."""
4065+ self.create_mock_backend().register()
4066+ self.mocker.replay()
4067+
4068+ self.client.register(APP_NAME, self.args)
4069+ self.assert_dbus_method_correct(self.client.register)
4070+
4071+ def test_login(self):
4072+ """The login is correct."""
4073+ self.create_mock_backend().login()
4074+ self.mocker.replay()
4075+
4076+ self.client.login(APP_NAME, self.args)
4077+ self.assert_dbus_method_correct(self.client.login)
4078+
4079+
4080+class CredentialsManagementParamsTestCase(CredentialsManagementOpsTestCase):
4081+ """Tests for the CredentialsManagement extra parameters handling."""
4082+
4083+ def setUp(self):
4084+ super(CredentialsManagementParamsTestCase, self).setUp()
4085+ self.args['dummy'] = 'nothing useful'
4086+
4087+
4088+class CredentialsManagementSignalsTestCase(TestCase):
4089+ """Tests for the CredentialsManagement DBus signals."""
4090+
4091+ def setUp(self):
4092+ self.client = CredentialsManagement(timeout_func=lambda *a: None,
4093+ shutdown_func=lambda *a: None)
4094+
4095+ self.memento = MementoHandler()
4096+ self.memento.setLevel(logging.DEBUG)
4097+ ubuntu_sso.main.logger.addHandler(self.memento)
4098+
4099+ def assert_dbus_signal_correct(self, signal, signature):
4100+ """Check that 'signal' is a dbus signal with proper 'signature'."""
4101+ self.assertTrue(signal._dbus_is_signal)
4102+ self.assertEqual(signal._dbus_interface, DBUS_CREDENTIALS_IFACE)
4103+ self.assertEqual(signal._dbus_signature, signature)
4104+
4105+ def test_credentials_found(self):
4106+ """The CredentialsFound signal."""
4107+ self.client.CredentialsFound(APP_NAME, TOKEN)
4108+ msgs = (self.client.__class__.__name__,
4109+ self.client.CredentialsFound.__name__, APP_NAME)
4110+ self.assertTrue(self.memento.check_info(*msgs))
4111+
4112+ msg = 'credentials must not be logged (found %r in log).'
4113+ for val in TOKEN.itervalues():
4114+ self.assertFalse(self.memento.check_info(val), msg % val)
4115+
4116+ self.assert_dbus_signal_correct(self.client.CredentialsFound, 'sa{ss}')
4117+
4118+ def test_credentials_not_found(self):
4119+ """The CredentialsNotFound signal."""
4120+ self.client.CredentialsNotFound(APP_NAME)
4121+ msgs = (self.client.__class__.__name__,
4122+ self.client.CredentialsNotFound.__name__, APP_NAME)
4123+ self.assertTrue(self.memento.check_info(*msgs))
4124+ self.assert_dbus_signal_correct(self.client.CredentialsNotFound, 's')
4125+
4126+ def test_credentials_cleared(self):
4127+ """The CredentialsCleared signal."""
4128+ self.client.CredentialsCleared(APP_NAME)
4129+ msgs = (self.client.__class__.__name__,
4130+ self.client.CredentialsCleared.__name__, APP_NAME)
4131+ self.assertTrue(self.memento.check_info(*msgs))
4132+
4133+ self.assert_dbus_signal_correct(self.client.CredentialsCleared, 's')
4134+
4135+ def test_credentials_stored(self):
4136+ """The CredentialsStored signal."""
4137+ self.client.CredentialsStored(APP_NAME)
4138+ msgs = (self.client.__class__.__name__,
4139+ self.client.CredentialsStored.__name__, APP_NAME)
4140+ self.assertTrue(self.memento.check_info(*msgs))
4141+
4142+ self.assert_dbus_signal_correct(self.client.CredentialsStored, 's')
4143+
4144+ def test_credentials_error(self):
4145+ """The CredentialsError signal."""
4146+ error = {'error_message': 'failed!', 'detailed error': 'yadda yadda'}
4147+ self.client.CredentialsError(APP_NAME, error)
4148+ msgs = (self.client.__class__.__name__,
4149+ self.client.CredentialsError.__name__,
4150+ APP_NAME, str(error))
4151+ self.assertTrue(self.memento.check_error(*msgs))
4152+
4153+ self.assert_dbus_signal_correct(self.client.CredentialsError, 'sa{ss}')
4154+
4155+ def test_authorization_denied(self):
4156+ """The AuthorizationDenied signal."""
4157+ self.client.AuthorizationDenied(APP_NAME)
4158+ msgs = (self.client.__class__.__name__,
4159+ self.client.AuthorizationDenied.__name__, APP_NAME)
4160+ self.assertTrue(self.memento.check_info(*msgs))
4161+
4162+ self.assert_dbus_signal_correct(self.client.AuthorizationDenied, 's')
4163
4164=== added file 'ubuntu_sso/main/tests/test_windows.py'
4165--- ubuntu_sso/main/tests/test_windows.py 1970-01-01 00:00:00 +0000
4166+++ ubuntu_sso/main/tests/test_windows.py 2011-03-23 14:22:30 +0000
4167@@ -0,0 +1,17 @@
4168+# -*- coding: utf-8 -*-
4169+# Author: Manuel de la Pena <manuel@canonical.com>
4170+#
4171+# Copyright 2011 Canonical Ltd.
4172+#
4173+# This program is free software: you can redistribute it and/or modify it
4174+# under the terms of the GNU General Public License version 3, as published
4175+# by the Free Software Foundation.
4176+#
4177+# This program is distributed in the hope that it will be useful, but
4178+# WITHOUT ANY WARRANTY; without even the implied warranties of
4179+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4180+# PURPOSE. See the GNU General Public License for more details.
4181+#
4182+# You should have received a copy of the GNU General Public License along
4183+# with this program. If not, see <http://www.gnu.org/licenses/>.
4184+"""Windows tests."""
4185
4186=== added file 'ubuntu_sso/main/windows.py'
4187--- ubuntu_sso/main/windows.py 1970-01-01 00:00:00 +0000
4188+++ ubuntu_sso/main/windows.py 2011-03-23 14:22:30 +0000
4189@@ -0,0 +1,17 @@
4190+# -*- coding: utf-8 -*-
4191+# Author: Manuel de la Pena <manuel@canonical.com>
4192+#
4193+# Copyright 2011 Canonical Ltd.
4194+#
4195+# This program is free software: you can redistribute it and/or modify it
4196+# under the terms of the GNU General Public License version 3, as published
4197+# by the Free Software Foundation.
4198+#
4199+# This program is distributed in the hope that it will be useful, but
4200+# WITHOUT ANY WARRANTY; without even the implied warranties of
4201+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4202+# PURPOSE. See the GNU General Public License for more details.
4203+#
4204+# You should have received a copy of the GNU General Public License along
4205+# with this program. If not, see <http://www.gnu.org/licenses/>.
4206+"""Main implementation on windows."""
4207
4208=== added directory 'ubuntu_sso/networkstate'
4209=== removed file 'ubuntu_sso/networkstate.py'
4210--- ubuntu_sso/networkstate.py 2010-10-11 13:22:16 +0000
4211+++ ubuntu_sso/networkstate.py 1970-01-01 00:00:00 +0000
4212@@ -1,108 +0,0 @@
4213-# -*- coding: utf-8 -*-
4214-#
4215-# networkstate - detect the current state of the network
4216-#
4217-# Author: Alejandro J. Cura <alecu@canonical.com>
4218-#
4219-# Copyright 2010 Canonical Ltd.
4220-#
4221-# This program is free software: you can redistribute it and/or modify it
4222-# under the terms of the GNU General Public License version 3, as published
4223-# by the Free Software Foundation.
4224-#
4225-# This program is distributed in the hope that it will be useful, but
4226-# WITHOUT ANY WARRANTY; without even the implied warranties of
4227-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4228-# PURPOSE. See the GNU General Public License for more details.
4229-#
4230-# You should have received a copy of the GNU General Public License along
4231-# with this program. If not, see <http://www.gnu.org/licenses/>.
4232-"""Implementation of network state detection."""
4233-
4234-import dbus
4235-
4236-from ubuntu_sso.logger import setup_logging
4237-logger = setup_logging("ubuntu_sso.networkstate")
4238-
4239-# Values returned by the callback
4240-ONLINE, OFFLINE, UNKNOWN = object(), object(), object()
4241-
4242-NM_STATE_NAMES = {
4243- ONLINE: "online",
4244- OFFLINE: "offline",
4245- UNKNOWN: "unknown",
4246-}
4247-
4248-# Internal NetworkManager State constants
4249-NM_STATE_UNKNOWN = 0
4250-NM_STATE_ASLEEP = 1
4251-NM_STATE_CONNECTING = 2
4252-NM_STATE_CONNECTED = 3
4253-NM_STATE_DISCONNECTED = 4
4254-
4255-NM_DBUS_INTERFACE = "org.freedesktop.NetworkManager"
4256-NM_DBUS_OBJECTPATH = "/org/freedesktop/NetworkManager"
4257-DBUS_UNKNOWN_SERVICE = "org.freedesktop.DBus.Error.ServiceUnknown"
4258-
4259-
4260-class NetworkManagerState(object):
4261- """Checks the state of NetworkManager thru DBus."""
4262-
4263- def __init__(self, result_cb, dbus_module=dbus):
4264- """Initialize this instance with a result and error callbacks."""
4265- self.result_cb = result_cb
4266- self.dbus = dbus_module
4267- self.state_signal = None
4268-
4269- def call_result_cb(self, state):
4270- """Return the state thru the result callback."""
4271- if self.state_signal:
4272- self.state_signal.remove()
4273- self.result_cb(state)
4274-
4275- def got_state(self, state):
4276- """Called by DBus when the state is retrieved from NM."""
4277- if state == NM_STATE_CONNECTED:
4278- self.call_result_cb(ONLINE)
4279- elif state == NM_STATE_CONNECTING:
4280- logger.debug("Currently connecting, waiting for signal")
4281- else:
4282- self.call_result_cb(OFFLINE)
4283-
4284- def got_error(self, error):
4285- """Called by DBus when the state is retrieved from NM."""
4286- if isinstance(error, self.dbus.exceptions.DBusException) and \
4287- error.get_dbus_name() == DBUS_UNKNOWN_SERVICE:
4288- logger.debug("Network Manager not present")
4289- self.call_result_cb(UNKNOWN)
4290- else:
4291- logger.error("Error contacting NetworkManager: %s" % \
4292- str(error))
4293- self.call_result_cb(UNKNOWN)
4294-
4295- def state_changed(self, state):
4296- """Called when a signal is emmited by Network Manager."""
4297- if int(state) == NM_STATE_CONNECTED:
4298- self.call_result_cb(ONLINE)
4299- elif int(state) == NM_STATE_DISCONNECTED:
4300- self.call_result_cb(OFFLINE)
4301- else:
4302- logger.debug("Not yet connected: continuing to wait")
4303-
4304- def find_online_state(self):
4305- """Get the network state and return it thru the set callback."""
4306- try:
4307- sysbus = self.dbus.SystemBus()
4308- nm_proxy = sysbus.get_object(NM_DBUS_INTERFACE,
4309- NM_DBUS_OBJECTPATH,
4310- follow_name_owner_changes=True)
4311- nm_if = self.dbus.Interface(nm_proxy, NM_DBUS_INTERFACE)
4312- self.state_signal = nm_if.connect_to_signal(
4313- signal_name="StateChanged",
4314- handler_function=self.state_changed,
4315- dbus_interface=NM_DBUS_INTERFACE)
4316- nm_proxy.Get(NM_DBUS_INTERFACE, "State",
4317- reply_handler=self.got_state,
4318- error_handler=self.got_error)
4319- except Exception, e: # pylint: disable=W0703
4320- self.got_error(e)
4321
4322=== added file 'ubuntu_sso/networkstate/__init__.py'
4323--- ubuntu_sso/networkstate/__init__.py 1970-01-01 00:00:00 +0000
4324+++ ubuntu_sso/networkstate/__init__.py 2011-03-23 14:22:30 +0000
4325@@ -0,0 +1,40 @@
4326+# -*- coding: utf-8 -*-
4327+# Author: Manuel de la Pena <manuel@canonical.com>
4328+#
4329+# Copyright 2011 Canonical Ltd.
4330+#
4331+# This program is free software: you can redistribute it and/or modify it
4332+# under the terms of the GNU General Public License version 3, as published
4333+# by the Free Software Foundation.
4334+#
4335+# This program is distributed in the hope that it will be useful, but
4336+# WITHOUT ANY WARRANTY; without even the implied warranties of
4337+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4338+# PURPOSE. See the GNU General Public License for more details.
4339+#
4340+# You should have received a copy of the GNU General Public License along
4341+# with this program. If not, see <http://www.gnu.org/licenses/>.
4342+"""Platform specific network status."""
4343+
4344+import sys
4345+
4346+# ignore global naming issues.
4347+# pylint: disable=C0103
4348+
4349+NetworkManagerState = None
4350+ONLINE = None
4351+OFFLINE = None
4352+UNKNOWN = None
4353+
4354+if sys.platform == 'win32':
4355+ from ubuntu_sso.networkstate import windows
4356+ NetworkManagerState = windows.NetworkManagerState
4357+ ONLINE = windows.ONLINE
4358+ OFFLINE = windows.OFFLINE
4359+ UNKNOWN = windows.UNKNOWN
4360+else:
4361+ from ubuntu_sso.networkstate import linux
4362+ NetworkManagerState = linux.NetworkManagerState
4363+ ONLINE = linux.ONLINE
4364+ OFFLINE = linux.OFFLINE
4365+ UNKNOWN = linux.UNKNOWN
4366
4367=== added file 'ubuntu_sso/networkstate/linux.py'
4368--- ubuntu_sso/networkstate/linux.py 1970-01-01 00:00:00 +0000
4369+++ ubuntu_sso/networkstate/linux.py 2011-03-23 14:22:30 +0000
4370@@ -0,0 +1,108 @@
4371+# -*- coding: utf-8 -*-
4372+#
4373+# networkstate - detect the current state of the network
4374+#
4375+# Author: Alejandro J. Cura <alecu@canonical.com>
4376+#
4377+# Copyright 2010 Canonical Ltd.
4378+#
4379+# This program is free software: you can redistribute it and/or modify it
4380+# under the terms of the GNU General Public License version 3, as published
4381+# by the Free Software Foundation.
4382+#
4383+# This program is distributed in the hope that it will be useful, but
4384+# WITHOUT ANY WARRANTY; without even the implied warranties of
4385+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4386+# PURPOSE. See the GNU General Public License for more details.
4387+#
4388+# You should have received a copy of the GNU General Public License along
4389+# with this program. If not, see <http://www.gnu.org/licenses/>.
4390+"""Implementation of network state detection."""
4391+
4392+import dbus
4393+
4394+from ubuntu_sso.logger import setup_logging
4395+logger = setup_logging("ubuntu_sso.networkstate")
4396+
4397+# Values returned by the callback
4398+ONLINE, OFFLINE, UNKNOWN = object(), object(), object()
4399+
4400+NM_STATE_NAMES = {
4401+ ONLINE: "online",
4402+ OFFLINE: "offline",
4403+ UNKNOWN: "unknown",
4404+}
4405+
4406+# Internal NetworkManager State constants
4407+NM_STATE_UNKNOWN = 0
4408+NM_STATE_ASLEEP = 1
4409+NM_STATE_CONNECTING = 2
4410+NM_STATE_CONNECTED = 3
4411+NM_STATE_DISCONNECTED = 4
4412+
4413+NM_DBUS_INTERFACE = "org.freedesktop.NetworkManager"
4414+NM_DBUS_OBJECTPATH = "/org/freedesktop/NetworkManager"
4415+DBUS_UNKNOWN_SERVICE = "org.freedesktop.DBus.Error.ServiceUnknown"
4416+
4417+
4418+class NetworkManagerState(object):
4419+ """Checks the state of NetworkManager thru DBus."""
4420+
4421+ def __init__(self, result_cb, dbus_module=dbus):
4422+ """Initialize this instance with a result and error callbacks."""
4423+ self.result_cb = result_cb
4424+ self.dbus = dbus_module
4425+ self.state_signal = None
4426+
4427+ def call_result_cb(self, state):
4428+ """Return the state thru the result callback."""
4429+ if self.state_signal:
4430+ self.state_signal.remove()
4431+ self.result_cb(state)
4432+
4433+ def got_state(self, state):
4434+ """Called by DBus when the state is retrieved from NM."""
4435+ if state == NM_STATE_CONNECTED:
4436+ self.call_result_cb(ONLINE)
4437+ elif state == NM_STATE_CONNECTING:
4438+ logger.debug("Currently connecting, waiting for signal")
4439+ else:
4440+ self.call_result_cb(OFFLINE)
4441+
4442+ def got_error(self, error):
4443+ """Called by DBus when the state is retrieved from NM."""
4444+ if isinstance(error, self.dbus.exceptions.DBusException) and \
4445+ error.get_dbus_name() == DBUS_UNKNOWN_SERVICE:
4446+ logger.debug("Network Manager not present")
4447+ self.call_result_cb(UNKNOWN)
4448+ else:
4449+ logger.error("Error contacting NetworkManager: %s" % \
4450+ str(error))
4451+ self.call_result_cb(UNKNOWN)
4452+
4453+ def state_changed(self, state):
4454+ """Called when a signal is emmited by Network Manager."""
4455+ if int(state) == NM_STATE_CONNECTED:
4456+ self.call_result_cb(ONLINE)
4457+ elif int(state) == NM_STATE_DISCONNECTED:
4458+ self.call_result_cb(OFFLINE)
4459+ else:
4460+ logger.debug("Not yet connected: continuing to wait")
4461+
4462+ def find_online_state(self):
4463+ """Get the network state and return it thru the set callback."""
4464+ try:
4465+ sysbus = self.dbus.SystemBus()
4466+ nm_proxy = sysbus.get_object(NM_DBUS_INTERFACE,
4467+ NM_DBUS_OBJECTPATH,
4468+ follow_name_owner_changes=True)
4469+ nm_if = self.dbus.Interface(nm_proxy, NM_DBUS_INTERFACE)
4470+ self.state_signal = nm_if.connect_to_signal(
4471+ signal_name="StateChanged",
4472+ handler_function=self.state_changed,
4473+ dbus_interface=NM_DBUS_INTERFACE)
4474+ nm_proxy.Get(NM_DBUS_INTERFACE, "State",
4475+ reply_handler=self.got_state,
4476+ error_handler=self.got_error)
4477+ except Exception, e: # pylint: disable=W0703
4478+ self.got_error(e)
4479
4480=== added directory 'ubuntu_sso/networkstate/tests'
4481=== added file 'ubuntu_sso/networkstate/tests/__init__.py'
4482--- ubuntu_sso/networkstate/tests/__init__.py 1970-01-01 00:00:00 +0000
4483+++ ubuntu_sso/networkstate/tests/__init__.py 2011-03-23 14:22:30 +0000
4484@@ -0,0 +1,17 @@
4485+# -*- coding: utf-8 -*-
4486+# Author: Manuel de la Pena <manuel@canonical.com>
4487+#
4488+# Copyright 2011 Canonical Ltd.
4489+#
4490+# This program is free software: you can redistribute it and/or modify it
4491+# under the terms of the GNU General Public License version 3, as published
4492+# by the Free Software Foundation.
4493+#
4494+# This program is distributed in the hope that it will be useful, but
4495+# WITHOUT ANY WARRANTY; without even the implied warranties of
4496+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4497+# PURPOSE. See the GNU General Public License for more details.
4498+#
4499+# You should have received a copy of the GNU General Public License along
4500+# with this program. If not, see <http://www.gnu.org/licenses/>.
4501+"""Test the different networkstatus implementations."""
4502
4503=== added file 'ubuntu_sso/networkstate/tests/test_linux.py'
4504--- ubuntu_sso/networkstate/tests/test_linux.py 1970-01-01 00:00:00 +0000
4505+++ ubuntu_sso/networkstate/tests/test_linux.py 2011-03-23 14:22:30 +0000
4506@@ -0,0 +1,181 @@
4507+# -*- coding: utf-8 -*-
4508+#
4509+# test_networkstate - tests for ubuntu_sso.networkstate
4510+#
4511+# Author: Alejandro J. Cura <alecu@canonical.com>
4512+#
4513+# Copyright 2010 Canonical Ltd.
4514+#
4515+# This program is free software: you can redistribute it and/or modify it
4516+# under the terms of the GNU General Public License version 3, as published
4517+# by the Free Software Foundation.
4518+#
4519+# This program is distributed in the hope that it will be useful, but
4520+# WITHOUT ANY WARRANTY; without even the implied warranties of
4521+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4522+# PURPOSE. See the GNU General Public License for more details.
4523+#
4524+# You should have received a copy of the GNU General Public License along
4525+# with this program. If not, see <http://www.gnu.org/licenses/>.
4526+"""Tests for the network state detection code."""
4527+
4528+from ubuntu_sso.networkstate import (NetworkManagerState,
4529+ ONLINE, OFFLINE, UNKNOWN)
4530+from ubuntu_sso.networkstate.linux import (DBUS_UNKNOWN_SERVICE,
4531+ NM_STATE_DISCONNECTED,
4532+ NM_STATE_CONNECTING,
4533+ NM_STATE_CONNECTED)
4534+
4535+from mocker import ARGS, KWARGS, ANY, MockerTestCase
4536+
4537+
4538+class TestException(Exception):
4539+ """An exception to test error conditions."""
4540+ def get_dbus_name(self):
4541+ """A fake dbus name for this exception."""
4542+ return "Test Exception Message"
4543+
4544+
4545+class TestNmNotAvailableException(Exception):
4546+ """An exception to test unavailability conditions."""
4547+ def get_dbus_name(self):
4548+ """The real name of the dbus error when NM is not running."""
4549+ return DBUS_UNKNOWN_SERVICE
4550+
4551+
4552+class NetworkManagerStateTestCase(MockerTestCase):
4553+ """Test NetworkManager state retrieval code."""
4554+
4555+ def setUp(self):
4556+ """Setup the mocker dbus object tree."""
4557+ self.dbusmock = self.mocker.mock()
4558+ self.dbusmock.SystemBus()
4559+ sysbusmock = self.mocker.mock()
4560+ self.mocker.result(sysbusmock)
4561+
4562+ sysbusmock.get_object(ARGS, KWARGS)
4563+ proxymock = self.mocker.mock()
4564+ self.mocker.result(proxymock)
4565+
4566+ self.dbusmock.Interface(proxymock, ANY)
4567+ ifmock = self.mocker.mock()
4568+ self.mocker.result(ifmock)
4569+
4570+ ifmock.connect_to_signal(ARGS, KWARGS)
4571+ signalmock = self.mocker.mock()
4572+ self.mocker.result(signalmock)
4573+
4574+ proxymock.Get(ARGS, KWARGS)
4575+ signalmock.remove()
4576+
4577+ self.mocker.replay()
4578+
4579+ def test_nm_online(self):
4580+ """Check the connected case."""
4581+
4582+ def got_state_cb(state):
4583+ """State was given."""
4584+ self.assertEquals(state, ONLINE)
4585+
4586+ nms = NetworkManagerState(got_state_cb, self.dbusmock)
4587+ nms.find_online_state()
4588+ nms.got_state(NM_STATE_CONNECTED)
4589+
4590+ def test_nm_offline(self):
4591+ """Check the disconnected case."""
4592+
4593+ def got_state_cb(state):
4594+ """State was given."""
4595+ self.assertEquals(state, OFFLINE)
4596+
4597+ nms = NetworkManagerState(got_state_cb, self.dbusmock)
4598+ nms.find_online_state()
4599+ nms.got_state(NM_STATE_DISCONNECTED)
4600+
4601+ def test_nm_connecting_then_online(self):
4602+ """Check the waiting for connection case."""
4603+
4604+ def got_state_cb(state):
4605+ """State was given."""
4606+ self.assertEquals(state, ONLINE)
4607+
4608+ nms = NetworkManagerState(got_state_cb, self.dbusmock)
4609+ nms.find_online_state()
4610+ nms.got_state(NM_STATE_CONNECTING)
4611+ nms.state_changed(NM_STATE_CONNECTED)
4612+
4613+ def test_nm_connecting_then_offline(self):
4614+ """Check the waiting but fail case."""
4615+
4616+ def got_state_cb(state):
4617+ """State was given."""
4618+ self.assertEquals(state, OFFLINE)
4619+
4620+ nms = NetworkManagerState(got_state_cb, self.dbusmock)
4621+ nms.find_online_state()
4622+ nms.got_state(NM_STATE_CONNECTING)
4623+ nms.state_changed(NM_STATE_DISCONNECTED)
4624+
4625+
4626+class NetworkManagerStateErrorsTestCase(MockerTestCase):
4627+ """Test NetworkManager state retrieval code."""
4628+
4629+ # Statement seems to have no effect
4630+ # pylint: disable=W0104
4631+
4632+ def setUp(self):
4633+ """Setup the mocker dbus object tree."""
4634+ self.dbusmock = self.mocker.mock()
4635+ self.dbusmock.SystemBus()
4636+ self.sysbusmock = self.mocker.mock()
4637+ self.mocker.result(self.sysbusmock)
4638+ self.sysbusmock.get_object(ARGS, KWARGS)
4639+
4640+ def mock_except_while_getting_proxy(self, exc):
4641+ """Simulate an exception while getting the DBus proxy object."""
4642+ self.mocker.throw(exc)
4643+ self.dbusmock.exceptions.DBusException
4644+ self.mocker.result(exc)
4645+ self.mocker.replay()
4646+
4647+ def mock_dbus_error_while_getting_state(self, exc):
4648+ """Simulate an exception while getting the State."""
4649+ proxymock = self.mocker.mock()
4650+ self.mocker.result(proxymock)
4651+
4652+ self.dbusmock.Interface(proxymock, ANY)
4653+ ifmock = self.mocker.mock()
4654+ self.mocker.result(ifmock)
4655+
4656+ ifmock.connect_to_signal(ARGS, KWARGS)
4657+ signalmock = self.mocker.mock()
4658+ self.mocker.result(signalmock)
4659+
4660+ proxymock.Get(ARGS, KWARGS)
4661+ self.dbusmock.exceptions.DBusException
4662+ self.mocker.result(exc)
4663+ signalmock.remove()
4664+ self.mocker.replay()
4665+
4666+ def test_nm_not_running(self):
4667+ """Check the case when NM is not running."""
4668+
4669+ def got_state_cb(state):
4670+ """State was given."""
4671+ self.assertEquals(state, UNKNOWN)
4672+
4673+ self.mock_dbus_error_while_getting_state(TestNmNotAvailableException)
4674+ nms = NetworkManagerState(got_state_cb, self.dbusmock)
4675+ nms.find_online_state()
4676+ nms.got_error(TestNmNotAvailableException())
4677+
4678+ def test_dbus_problem(self):
4679+ """Check the case when DBus throws some other exception."""
4680+
4681+ def got_state_cb(state):
4682+ """State was given."""
4683+ self.assertEquals(state, UNKNOWN)
4684+
4685+ self.mock_except_while_getting_proxy(TestException)
4686+ nms = NetworkManagerState(got_state_cb, self.dbusmock)
4687+ nms.find_online_state()
4688
4689=== added file 'ubuntu_sso/networkstate/tests/test_windows.py'
4690--- ubuntu_sso/networkstate/tests/test_windows.py 1970-01-01 00:00:00 +0000
4691+++ ubuntu_sso/networkstate/tests/test_windows.py 2011-03-23 14:22:30 +0000
4692@@ -0,0 +1,119 @@
4693+# -*- coding: utf-8 -*-
4694+#
4695+# Author: Manuel de la Pena<manuel@canonical.com>
4696+#
4697+# Copyright 2011 Canonical Ltd.
4698+#
4699+# This program is free software: you can redistribute it and/or modify it
4700+# under the terms of the GNU General Public License version 3, as published
4701+# by the Free Software Foundation.
4702+#
4703+# This program is distributed in the hope that it will be useful, but
4704+# WITHOUT ANY WARRANTY; without even the implied warranties of
4705+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4706+# PURPOSE. See the GNU General Public License for more details.
4707+#
4708+# You should have received a copy of the GNU General Public License along
4709+# with this program. If not, see <http://www.gnu.org/licenses/>.
4710+"""Tests for the network manager."""
4711+from mocker import MockerTestCase
4712+from ubuntu_sso.networkstate.windows import (
4713+ NetworkManager,
4714+ NetworkManagerState,
4715+ ONLINE,
4716+ OFFLINE)
4717+
4718+
4719+class TestNetworkManager(MockerTestCase):
4720+ """Test he Network Manager."""
4721+
4722+ def setUp(self):
4723+ super(TestNetworkManager, self).setUp()
4724+ self.connection_info = self.mocker.mock()
4725+ self.connection_no_info = self.mocker.mock()
4726+ self.disconnected = self.mocker.mock()
4727+ self.manager = NetworkManager(self.connection_no_info,
4728+ self.connection_info, self.disconnected)
4729+
4730+ def test_connection_made(self):
4731+ """Ensure db is called."""
4732+ self.connection_info()
4733+ self.mocker.replay()
4734+ self.manager.ConnectionMade()
4735+
4736+ def test_connection_made_no_cb(self):
4737+ """Ensure db is called."""
4738+ self.manager.connected_cb_info = None
4739+ self.mocker.replay()
4740+ self.manager.ConnectionMade()
4741+
4742+ def test_connection_made_no_info(self):
4743+ """Ensure db is called."""
4744+ self.connection_no_info()
4745+ self.mocker.replay()
4746+ self.manager.ConnectionMadeNoQOCInfo()
4747+
4748+ def test_connection_made_no_info_no_cb(self):
4749+ """Ensure db is called."""
4750+ self.manager.connected_cb = None
4751+ self.mocker.replay()
4752+ self.manager.ConnectionMadeNoQOCInfo()
4753+
4754+ def test_disconnection(self):
4755+ """Ensure db is called."""
4756+ self.disconnected()
4757+ self.mocker.replay()
4758+ self.manager.ConnectionLost()
4759+
4760+ def test_disconnection_no_cb(self):
4761+ """Ensure db is called."""
4762+ self.manager.disconnected_cb = None
4763+ self.mocker.replay()
4764+ self.manager.ConnectionLost()
4765+
4766+
4767+class TestNetworkManagerState(MockerTestCase):
4768+ """Test he Network Manager State."""
4769+
4770+ def setUp(self):
4771+ super(TestNetworkManagerState, self).setUp()
4772+ self.network_manager = self.mocker.mock()
4773+ self.is_connected = self.mocker.replace(
4774+ 'ubuntu_sso.networkstate.windows.is_machine_connected')
4775+ self.thread = self.mocker.mock()
4776+ self.cb = self.mocker.mock()
4777+ self.state = NetworkManagerState(self.cb)
4778+
4779+ def test_connection_made(self):
4780+ """Test that the cb is actually called."""
4781+ self.cb(ONLINE)
4782+ self.mocker.replay()
4783+ self.state.connection_made()
4784+
4785+ def test_connection_lost(self):
4786+ """Test that the cb is actually called."""
4787+ self.cb(OFFLINE)
4788+ self.mocker.replay()
4789+ self.state.connection_lost()
4790+
4791+ def test_find_online_state_not_connected(self):
4792+ """Test that we do find the online state correctly."""
4793+ self.is_connected()
4794+ self.mocker.result(False)
4795+ self.cb(OFFLINE)
4796+ self.mocker.result(self.thread)
4797+ self.thread.start()
4798+ self.mocker.replay()
4799+ self.state.find_online_state(listener=self.network_manager,
4800+ listener_thread=self.thread)
4801+
4802+ def test_find_online_state_connected(self):
4803+ """Test that we do find the online state correctly."""
4804+ self.is_connected()
4805+ self.mocker.result(ONLINE)
4806+ self.cb(ONLINE)
4807+ self.mocker.result(self.thread)
4808+ self.thread.start()
4809+ self.mocker.replay()
4810+ self.state.find_online_state(listener=self.network_manager,
4811+ listener_thread=self.thread)
4812
4813=== added file 'ubuntu_sso/networkstate/windows.py'
4814--- ubuntu_sso/networkstate/windows.py 1970-01-01 00:00:00 +0000
4815+++ ubuntu_sso/networkstate/windows.py 2011-03-23 14:22:30 +0000
4816@@ -0,0 +1,199 @@
4817+# -*- coding: utf-8 -*-
4818+# Author: Manuel de la Pena <manuel@canonical.com>
4819+#
4820+# Copyright 2011 Canonical Ltd.
4821+#
4822+# This program is free software: you can redistribute it and/or modify it
4823+# under the terms of the GNU General Public License version 3, as published
4824+# by the Free Software Foundation.
4825+#
4826+# This program is distributed in the hope that it will be useful, but
4827+# WITHOUT ANY WARRANTY; without even the implied warranties of
4828+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4829+# PURPOSE. See the GNU General Public License for more details.
4830+#
4831+# You should have received a copy of the GNU General Public License along
4832+# with this program. If not, see <http://www.gnu.org/licenses/>.
4833+"""Network status implementation on Windows."""
4834+
4835+
4836+# pylint: disable=F0401
4837+# distutils-extra: ignore-import=pythoncom,win32com.server.policy
4838+# distutils-extra: ignore-import=win32com.client
4839+import pythoncom
4840+# pylint: enable=F0401
4841+from ctypes import windll, byref
4842+from ctypes.wintypes import DWORD
4843+from threading import Thread
4844+# pylint: disable=F0401
4845+from win32com.server.policy import DesignatedWrapPolicy
4846+from win32com.client import Dispatch
4847+# pylint: enable=F0401
4848+
4849+from ubuntu_sso.logger import setup_logging
4850+
4851+logger = setup_logging("ubuntu_sso.networkstate")
4852+
4853+# naming errors are deliberated because we are following the COM naming to make
4854+# it clear for later developers.
4855+# pylint: disable=C0103
4856+
4857+# Values returned by the callback
4858+ONLINE, OFFLINE, UNKNOWN = object(), object(), object()
4859+
4860+## from EventSys.h
4861+PROGID_EventSystem = "EventSystem.EventSystem"
4862+PROGID_EventSubscription = "EventSystem.EventSubscription"
4863+
4864+# SENS (System Event Notification Service) values for the events,
4865+# this events contain the uuid of the event, the name of the event to be used
4866+# as well as the method name of the method in the ISesNetwork interface that
4867+# will be executed for the event.
4868+# For more info look at:
4869+# http://msdn.microsoft.com/en-us/library/aa377384(v=vs.85).aspx
4870+
4871+SUBSCRIPTION_NETALIVE = ('{cd1dcbd6-a14d-4823-a0d2-8473afde360f}',
4872+ 'UbuntuOne Network Alive',
4873+ 'ConnectionMade')
4874+
4875+SUBSCRIPTION_NETALIVE_NOQOC = ('{a82f0e80-1305-400c-ba56-375ae04264a1}',
4876+ 'UbuntuOne Net Alive No Info',
4877+ 'ConnectionMadeNoQOCInfo')
4878+
4879+SUBSCRIPTION_NETLOST = ('{45233130-b6c3-44fb-a6af-487c47cee611}',
4880+ 'UbuntuOne Network Lost',
4881+ 'ConnectionLost')
4882+
4883+SUBSCRIPTION_REACH = ('{4c6b2afa-3235-4185-8558-57a7a922ac7b}',
4884+ 'UbuntuOne Network Reach',
4885+ 'ConnectionMade')
4886+
4887+SUBSCRIPTION_REACH_NOQOC = ('{db62fa23-4c3e-47a3-aef2-b843016177cf}',
4888+ 'UbuntuOne Network Reach No Info',
4889+ 'ConnectionMadeNoQOCInfo')
4890+
4891+SUBSCRIPTION_REACH_NOQOC2 = ('{d4d8097a-60c6-440d-a6da-918b619ae4b7}',
4892+ 'UbuntuOne Network Reach No Info 2',
4893+ 'ConnectionMadeNoQOCInfo')
4894+
4895+SUBSCRIPTIONS = [SUBSCRIPTION_NETALIVE,
4896+ SUBSCRIPTION_NETALIVE_NOQOC,
4897+ SUBSCRIPTION_NETLOST,
4898+ SUBSCRIPTION_REACH,
4899+ SUBSCRIPTION_REACH_NOQOC,
4900+ SUBSCRIPTION_REACH_NOQOC2]
4901+
4902+SENSGUID_EVENTCLASS_NETWORK = '{d5978620-5b9f-11d1-8dd2-00aa004abd5e}'
4903+SENSGUID_PUBLISHER = "{5fee1bd6-5b9b-11d1-8dd2-00aa004abd5e}"
4904+
4905+# uuid of the implemented com interface
4906+IID_ISesNetwork = '{d597bab1-5b9f-11d1-8dd2-00aa004abd5e}'
4907+
4908+
4909+class NetworkManager(DesignatedWrapPolicy):
4910+ """Implement ISesNetwork to know about the network status."""
4911+
4912+ _com_interfaces_ = [IID_ISesNetwork]
4913+ _public_methods_ = ['ConnectionMade',
4914+ 'ConnectionMadeNoQOCInfo',
4915+ 'ConnectionLost']
4916+ _reg_clsid_ = '{41B032DA-86B5-4907-A7F7-958E59333010}'
4917+ _reg_progid_ = "UbuntuOne.NetworkManager"
4918+
4919+ def __init__(self, connected_cb=None, connected_cb_info=None,
4920+ disconnected_cb=None):
4921+ # pylint: disable=E1101
4922+ self._wrap_(self)
4923+ # pylint: enable=E1101
4924+ self.connected_cb = connected_cb
4925+ self.connected_cb_info = connected_cb_info
4926+ self.disconnected_cb = disconnected_cb
4927+
4928+ def ConnectionMade(self, *args):
4929+ """Tell that the connection is up again."""
4930+ logger.info('Connection was made.')
4931+ if self.connected_cb_info:
4932+ self.connected_cb_info()
4933+
4934+ def ConnectionMadeNoQOCInfo(self, *args):
4935+ """Tell that the connection is up again."""
4936+ logger.info('Connection was made no info.')
4937+ if self.connected_cb:
4938+ self.connected_cb()
4939+
4940+ def ConnectionLost(self, *args):
4941+ """Tell the connection was lost."""
4942+ logger.info('Connection was lost.')
4943+ if self.disconnected_cb:
4944+ self.disconnected_cb()
4945+
4946+ def register(self):
4947+ """Register to listen to network events."""
4948+ # call the CoInitialize to allow the registration to run in another
4949+ # thread
4950+ pythoncom.CoInitialize()
4951+ # interface to be used by com
4952+ manager_interface = pythoncom.WrapObject(self)
4953+ event_system = Dispatch(PROGID_EventSystem)
4954+ # register to listen to each of the events to make sure that
4955+ # the code will work on all platforms.
4956+ for current_event in SUBSCRIPTIONS:
4957+ # create an event subscription and add it to the event
4958+ # service
4959+ event_subscription = Dispatch(PROGID_EventSubscription)
4960+ event_subscription.EventClassId = SENSGUID_EVENTCLASS_NETWORK
4961+ event_subscription.PublisherID = SENSGUID_PUBLISHER
4962+ event_subscription.SubscriptionID = current_event[0]
4963+ event_subscription.SubscriptionName = current_event[1]
4964+ event_subscription.MethodName = current_event[2]
4965+ event_subscription.SubscriberInterface = manager_interface
4966+ event_subscription.PerUser = True
4967+ # store the event
4968+ try:
4969+ event_system.Store(PROGID_EventSubscription,
4970+ event_subscription)
4971+ except pythoncom.com_error as e:
4972+ logger.error(
4973+ 'Error registering %s to event %s', e, current_event[1])
4974+
4975+ pythoncom.PumpMessages()
4976+
4977+
4978+def is_machine_connected():
4979+ """Return if the machine is connected to the internet."""
4980+ wininet = windll.wininet
4981+ flags = DWORD()
4982+ connected = wininet.InternetGetConnectedState(byref(flags), None)
4983+ return connected == 1
4984+
4985+
4986+class NetworkManagerState(object):
4987+ """Check for status changed in the network on Windows."""
4988+
4989+ def __init__(self, result_cb, **kwargs):
4990+ """Initialize this instance with a result and error callbacks."""
4991+ self.result_cb = result_cb
4992+
4993+ def connection_made(self):
4994+ """Return the connection state over the call back."""
4995+ self.result_cb(ONLINE)
4996+
4997+ def connection_lost(self):
4998+ """Return the connection was lost over the call back."""
4999+ self.result_cb(OFFLINE)
5000+
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches