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